[Spring] API 생성하기(1)

[Spring] API 생성하기(1)

이동욱 님의 스프링 부트와 AWS로 혼자 구현하는 웹 서비스책을 공부하며 정리한 내용입니다. 틀린 정보가 있을 수 있으니 주의하시고 댓글로 남겨주시길 바랍니다.

API를 만들기 위한 클래스

API를 만들기 위해선 아래 3개의 클래스가 필요합니다.

Request 데이터를 받을 DTO

API 요청을 받을 Container

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

Service클래스는 오직 트랜젝션, 도메인 간 순서 보장의 역할만 합니다.

게시물 등록 기능 만들기

우선 PostsApiController 를 web 패키지에, PostsSaveRequestDto 를 web.dto 패키지에, PostsService 를 service.posts 패키지에 생성합니다. 각 클래스의 코드는 아래와 같습니다.

PostsApiController

package com.hwanld.book.springboot.web; import com.hwanld.book.springboot.service.posts.PostsService; import com.hwanld.book.springboot.web.dto.PostsSaveRequestDto; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RestController public class PostsApiController { private final PostsService postsService; @PostMapping("/api/v1/posts") public Long save (@RequestBody PostsSaveRequestDto requestDto) { return postsService.save(requestDto); } }

PostsService

package com.hwanld.book.springboot.service.posts; import com.hwanld.book.springboot.domain.post.PostsRepository; import com.hwanld.book.springboot.web.dto.PostsSaveRequestDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service public class PostsService { private final PostsRepository postsRepository; @Transactional public Long save(PostsSaveRequestDto requestDto) { return postsRepository.save(requestDto.toEntity()).getId(); } }

스프링에서 Bean을 주입 받는 방식에는 총 3가지가 있습니다. @Autowired , setter , Constructor(생성자) 를 사용해서 Bean을 주입 받는 방식이 있습니다. (이는 Dependency Injection, 의존성 주입과 연관이 있습니다!)

위 코드에서는 @RequiredArgsConstructor 애노테이션 때문에 final이 선언된 모든 필드를 인자값으로 하는 생성자를 롬복의 @RequiredArgsConstructor가 대신 생성해 준 것 입니다.

PostsSaveRequestDto

package com.hwanld.book.springboot.web.dto; import com.hwanld.book.springboot.domain.post.Posts; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @NoArgsConstructor public class PostsSaveRequestDto { private String title; private String content; private String author; @Builder public PostsSaveRequestDto(String title, String content, String author) { this.title = title; this.content = content; this.author = author; } public Posts toEntity() { return Posts.builder() .title(title) .content(content) .author(author) .build(); } }

마치 Entity 클래스와 거의 유사한 것을 알 수 있는데 절때 Entity 클래스를 Request/Responce 클래스로 사용해서는 안됩니다! Entity 클래스는 데이터베이스와 맞닿은 핵심 클래스입니다. 사소한 로직을 해결하기 위해서 Entity클래스를 변경하게되면 Entity클래스를 중심으로 동작하는 수많은 서비스 클래스나 비즈니스 로직들에서 문제가 생길 수 있기 때문에 Entity클래스는 Entity클래스의 기능만 할 수 있도록 하고, 위와 같이 다른 클래스를 통해서 로직을 해결하는 것이 좋습니다.

위에 작성한 3개의 클래스가 정상적으로 작동하는지 점검하기 위해서 아래와 같은 테스트 코드를 작성합니다.

PostsApiControllerTest

package com.hwanld.book.springboot.web; import com.hwanld.book.springboot.domain.post.Posts; import com.hwanld.book.springboot.domain.post.PostsRepository; import com.hwanld.book.springboot.web.dto.PostsSaveRequestDto; 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.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; 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://localgost:" + port + "/api/v1/posts"; //when ResponseEntity responseEntity = restTemplate.postForEntity(url, requestDto, Long.class); //then assertThat(responseEntity.getStatusCode()) .isEqualTo(HttpStatus.OK); assertThat(responseEntity.getBody()) .isGreaterThan(0L); List all = postsRepository.findAll(); assertThat(all.get(0).getTitle()) .isEqualTo(title); assertThat(all.get(0).getContent()) .isEqualTo(content); } }

Api Controller 를 테스트하는데 HelloControler와는 다르게 @WebMVC 를 사용하지 않았습니다. @WebMvcTest 의 경우는 JPA 기능이 작동하지 않기 때문인데, JPA 기능까지 한번에 테스트할 때는 @SpringBootTest 와 TestRestTemplate 를 사용하면 됩니다.

from http://hwanld.tistory.com/7 by ccl(A) rewrite - 2021-12-30 00:01:20