이전 글에서 이어지는 글입니다.
등록/수정/조회 API
해당 API를 만들기 위해 3개의 클래스가 필요하다.
- Request 데이터를 받을 DTO
- API 요청을 받을 Controller
- 트랜잭션, 도메인 기능 간의 순서를 보장하는 Service
서비스 계층에서 비즈니스 로직을 처리해야 한다는 생각은 오해다. 서비스는 트랜잭션, 도메인 간 순서만 보장한다.
- DTOs
- DTO(Data Transfer Object)는 계층 간 데이터 교환을 위한 객체를 말한다.
- Domain Model
- 도메인이라 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있고, 공유할 수 있도록 단순화시킨 것을 도메인 모델이라고 한다.
- 택시 앱이라면 배차,탑승,요금 등 모두 도메인이 될 수 있다.
- @entity가 사용되는 영역 역시 도메인 모델이다.
- 무조건 테이블과 관계가 있어야 하는 것은 아니다.
- VO처럼 값 객체도 이 영역에 해당한다.
위 다섯 개의 레이어 중, 도메인 모델이 비지니스를 처리한다. 기존에 서비스 레이어에서 처리하는 방식을 트랜잭션 스크립트, 라고 한다. 모든 로직이 서비스 클래스에서 처리되면, 서비스 계층은 무의미해지고 객체는 단순히 데이터 덩어리 역할을 한다. 따라서 서비스는 도메인 - 트랜잭션 간 순서만 보장하고 도메인 모델로 비즈니스 로직 처리를 한다.
등록
PostsApiController
@RequiredArgsConstructor
@RestController
public class PostsApiController {
private final PostsService postsService;
//등록
@PostMapping("/api/v1/posts")
public Long save(@RequestBody PostsSaveRequestDto requestDto) {
return postsService.save(requestDto);
}
}
PostService
@RequiredArgsConstructor
@Service
public class PostsService {
private final PostsRepository postsRepository;
@Transactional
public Long save(PostsSaveRequestDto requestDto) {
return postsRepository.save(requestDto.toEntity()).getId();
}
}
의존성 주입의 방식에 대해서는 해당 글 참고 링크
PostSaveRequestDto
@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;
}
//DTO to Entity
public Posts toEntity() {
return Posts.builder()
.title(title)
.content(content)
.author(author)
.build();
}
}
Entity와 DTO
Entity는 데이터베이스에 딱 붙은 핵심 클래스다. 변경사항이 있을 때마다 Entity클래스를 건드리는 것은 상당히 위험하다. 수많은 비즈니스 로직이 엔티티 클래스를 기준으로 동작하기 때문이다. 따라서 Entity를 Request나 Response하는 용도로 사용해서는 안 된다.
DTO는 화면과 데이터를 주고 받기 위한(Request/Response) 케이스다. 변경이 잦고, 그렇게 해도 큰 문제가 없다. Entity클래스와 Controller에서 사용하는 DTO클래스는 반드시 구분해주어야 한다.
등록 테스트
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private PostsRepository postsRepository;
@AfterEach
public void tearDown() throws Exception {
postsRepository.deleteAll();
}
//등록 테스트
@Test
public void testPostRegister() throws Exception {
//저장
String title = "title";
String content = "content";
PostsSaveRequestDto requestDto = PostsSaveRequestDto
.builder()
.title(title)
.content(content)
.author("author")
.build();
//호출할 URL
String url = "http://localhost:" + port + "/api/v1/posts";
//POST방식으로 dto 전송.
ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, requestDto, Long.class);
//성공적으로 전송했는지 test
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);
}
}
@WebMvcTest는 JPA 기능이 작동하지 않는다. JPA를 테스트할 때는 @SpringBootTest와 TestRestTemplate를 이용한다.
'Spring' 카테고리의 다른 글
[Security] 현재 로그인한 사용자 정보 가져오기 (0) | 2021.01.23 |
---|---|
머스태치Mustache (0) | 2020.09.16 |
JPA Auditing으로 생성 시간/수정시간 자동화 (0) | 2020.09.15 |
Spring Boot 게시판 만들기 (0) | 2020.09.14 |
Spring Data JPA (0) | 2020.09.14 |