-
JPA (2) - API 개발 시 기본으로 지켜야 할 사항Web/JPA 2024. 1. 22. 10:47
1. 사용자의 요청과 응답에 대한 요구사항에 맞는 적절한 DTO를 만들어라!
@PostMapping("/api/v1/members") public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) { Long id = memberService.join(member); return new CreateMemberResponse(id); } @PostMapping("/api/v2/members") public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) { Member member = new Member(); member.setName(request.getName()); Long id = memberService.join(member); return new CreateMemberResponse(id); } @Data static class CreateMemberRequest { private String name; } @Data static class CreateMemberResponse { private Long id; public CreateMemberResponse(Long id) { this.id = id; } }
- V1은 엔티티를 Request Body에 직접 매핑했다.
> 문제점 : 엔티티에 프레젠테이션 계층 로직이 추가됨
엔티티에 API 검증 로직이 들어감 (@NotEmpty등)
실무에서는 회원 엔티티를 위한 API가 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 모든
요구사항에 맞는 엔티티 만들기 어려움
**엔티티가 변경되면 API 스펙이 변경됨
> 결론: API요청 스펙에 맞는 별도의 DTO를 파라미터로 받는다.
- 요청과 응답에 맞는 DTO 만들 시 장점 : 엔티티와 프레젠테이션 계층을 위한 로직을 분리할 수 있다.
엔티티와 API스펙을 명확하게 분리할 수 있다.
엔티티가 변해도 API 스펙이 변하지 않는다
요청에도 적절한 DTO를 사용하면, 값이 매핑되는 걸 한눈에 확인하기 쉽다.
* 절대 엔티티를 그대로 응답으로 담아선 안된다!
2. 커맨드와 쿼리를 분리해라!
@Transactional public void update(Long id, String name) { Member member = memberRepository.findOne(id); member.setName(name);
- 여기서 Member를 리턴할 수도 있지만, 여기서 리턴하는 Member는 준영속상태가된다.
- 만약 update쿼리 밖에서 리턴된 Member를 수정하려고해도, 쿼리가 날라가진 않을 것이다.
- 중요한점은 쿼리를 사용하고 싶으면 지정된 쿼리 메서드 안에서 해야한다. (변경 사항을 확실하게 알기 위해 필요)
3. 응답 값으로 절대 엔티티를 직접 외부에 노출하지 말아라
@GetMapping("/api/v1/members") public List<Member> membersV1() { return memberService.findMembers(); } }
- 일단 연관관계 때문에 JSON으로 바꾸려고할때 에러가 발생한다.
- 이걸 막으러면 엔티티안에서 @JosonIgnore를 통해 Json으로 맵핑시 제외될 값을 지정할 수 있다.
- 하지만 이는 굉장히 안좋다. 엔티티는 언제든 API에 따라 Json으로 변환될 수 있어야한다 (엔티티에 프리젠테이션 로직이 들어왔다)
- 또한 엔티티가 변경되면 API 스펙이 변한다.
- 또한 컬렉션을 직접 변환하면( [member :{}, member{}]) 향후 API스펙을 변경하기 어렵다 -> ( { //별도 스펙 , data: [ member:{}, member:{} ] }) 이런식으로 담아야 더 좋다.
@GetMapping("/api/v2/members") public Result membersV2() { List<Member> findMembers = memberService.findMembers(); //엔티티 -> DTO 변환 List<MemberDto> collect = findMembers.stream() .map(m -> new MemberDto(m.getName())) //MemberDto List로 변환 .collect(Collectors.toList()); return new Result(collect); // 다시 Dto를 감싸서 리턴 } @Data @AllArgsConstructor static class Result<T> { private T data; } @Data @AllArgsConstructor static class MemberDto { private String name; }
- 이렇게 별도의 DTO로 변환하자.
- 엔티티가 변해도 API 스펙이 변경되지 않는다.
- 또한 한번 감싸서 변환하기때문에 향후 필드를 추가할 수도 있다.
'Web > JPA' 카테고리의 다른 글
SpringDataJPA (1) - 공용 인터페이스, @Query (0) 2024.01.30 JPA(2) - OSIV (0) 2024.01.22 JPA (11) - 다형성 쿼리,엔티티 직접 사용,정적쿼리,벌크연산 (0) 2024.01.12 JPA - 프로젝트 생성 (0) 2024.01.11 JPA 활용 (1-3) - 엔티티 설계시 주의점 (0) 2024.01.04