ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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 스펙이 변경되지 않는다. 

    - 또한 한번 감싸서 변환하기때문에 향후 필드를 추가할 수도 있다.

Designed by Tistory.