Over the limit

[스프링 부트와 AWS로 혼자 구현하는 웹서비스] 등록/수정/조회 API 만들기 본문

Web

[스프링 부트와 AWS로 혼자 구현하는 웹서비스] 등록/수정/조회 API 만들기

ellapk 2022. 4. 9. 08:56

API를 만들기 위해 필요한 3개의 클래스

1. Request 데이터를 받을 Dto

2. API 요청을 받을 Controller

3. 트랜잭션, 도메인 기능 간의 순서를 보장하는 Service

 

Spring 웹 계층

Web Layer

  • 컨트롤러, JSP 등의 View 템플릿 영역
  • 이외에도 필터, 인터셉터 등 외부 요청과 응답에 대한 전반적인 영역 이야기

 

Service Layer

  • @Service에 사용되는 서비스 영역
  • Controller와 Dao의 중간 영역에서 사용된다.
  • @Transactional이 사용되어야 하는 영역

 

Repository Layer

  • 데이터 저장소에 접근하는 영역(ex. DB)

 

Dtos

  • 계층 간에 데이터 교환을 위한 객체 = Dto, 이들의 영역 = Dtos
  • View 템플릿 엔진에서 사용될 객체, Repository Later에서 결과로 넘겨준 객체 등

 

Domain Model

  • 도메인을 모든 사람이 동일한 관점에서 공유할 수 있게 단순화 시킨 것
  • @Entity도 도메인 모델이다, 그치만 이것이무조건 DB 테이블과 관계가 있어야하는 것은 아니다.

 

 

 

 

보통 평균적으로 코딩하던 기존의 모델들은 주로 트랜잭션 모델이라고 할 수 있다.

모든 로직이 서비스 클래스 내부에서 처리되기 때문에, '서비스 계층'이라는 것이 무의미하고 객체란 단순히 데이터 덩어리 역할만 한다.

 

하지만 도메인 모델은 이와 다르게 트랜잭션과 도메인 간의 순서만 보장해준다.

앞으로도 도메인 모델을 다루고 코드를 작성할 것이다.

 

 

직접 API 작성 후 조회해보기

import com.web.community.springboot.web.dto.PostsSaveRequestDto;
import com.web.community.springboot.web.dto.PostsResponseDto;
import com.web.community.springboot.web.dto.PostsUpdateRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RestController
public class PostsApiController {
    private final PostsService postsService;

    @PostMapping("/api/v1/posts")
    public Long save(@RequestBody PostsSaveRequestDto requestDto) {
        return postsService.save(requestDto);
    }


    @PutMapping("/api/v1/posts/{id}")
    public Long update(@PathVariable Long id, @RequestBody PostsUpdateRequestDto requestDto){
        return postsService.update(id, requestDto);
    }

    @GetMapping("/api/v1/posts/{id}")
    public PostsResponseDto findById(@PathVariable Long id){
        return postsService.findById(id);
    }
}

이번엔 api 테스트 코드도 작성해보자

 

 

package com.web.community.springboot.web;


import com.web.community.springboot.domain.posts.Posts;
import com.web.community.springboot.domain.posts.PostsRepository;
import com.web.community.springboot.web.dto.PostsSaveRequestDto;
import com.web.community.springboot.web.dto.PostsUpdateRequestDto;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;

import javax.swing.text.html.parser.Entity;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private PostsRepository postsRepository;

    @After
    public void tearDown() throws Exception{
        postsRepository.deleteAll();
    }

    @Test
    public void Posts_등록된다() throws Exception{
        //given
        String title = "title";
        String content = "content";
        PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
                .title(title)
                .content(content)
                .author("author")
                .build();

        String url="http://localhost:" + port + "/api/v1/posts";

        //when
        ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url,requestDto,Long.class);

        //then
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(responseEntity.getBody()).isGreaterThan(0L);

        List<Posts> all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(title);
        assertThat(all.get(0).getContent()).isEqualTo(content);

}

  @Test
    public void Posts_수정된다() throws Exception{
        //given
        Posts savedPosts = postsRepository.save(Posts.builder()
                .title("title")
                .content("content")
                .author("author").build());


        Long updateId = savedPosts.getId();
        String expectedTitle = "title2";
        String expectedContent = "content2";

        PostsUpdateRequestDto requestDto = PostsUpdateRequestDto.builder()
                .title(expectedTitle)
                .content(expectedContent)
                .build();

        String url = "http://localhost:" + port+ "/api/v1/posts/" + updateId;

        HttpEntity<PostsUpdateRequestDto> requestEntity = new HttpEntity<>(requestDto);

        //when
        ResponseEntity<Long> responseEntity = restTemplate.
                exchange(url, HttpMethod.PUT, requestEntity, Long.class);


        //then
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(responseEntity.getBody()).isGreaterThan(0L);

        List<Posts> all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(expectedTitle);
        assertThat(all.get(0).getContent()).isEqualTo(expectedContent);


    }
}

 

update 기능해서 DB에 쿼리를 날리는 부분이 없는데, 이는

JPA의 영속성, 즉 Entity를 영구 저장하는 환경

때문이다.

JPA 사용에 대하여 (tistory.com)

 

JPA 사용에 대하여

관계형 데이터베이스를 이용하는 프로젝트에서 객체 지향 프로그래밍을 어떻게 연동할까? ->JPA라는 자바 표준 ORM(Object Relational Maping)을 사용하자! 즉, JPA를 사용하여려면 객체 지향 프로그래밍

xean.tistory.com

 

 

 

 

application.properties에 다음 코드를 추가하여 H2 데이터베이스를 통한 조회를 진행하자.

spring.h2.console.enabled=true

Appliction 파일의 main 메소드를 실행한다.

 

 

이후 localhost:8008/h2-console로 가면 정상 접속됨을 확인할 수 있음!