Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

회원 등록, 수정, 조회 API 본문

인프런/[인프런] Springboot와 JPA활용 2

회원 등록, 수정, 조회 API

Seung__ 2021. 8. 21. 00:42

* @RestController = @Controller  + @ReponseBody

 

* @RequestBody : JSON 데이터를 Member로 바꿔줌.

 

1.회원 등록 : 엔티티를 @RequestBody에 직접 매핑


  • api에서 엔티티를 직접 사용하면 여러 문제점이 있다
    • 엔티티에 프레젠테이션 계층을 위한 로직이 추가됨
    • 엔티티에 API검증을 위한 로직이 들어감.(@NotEmpty 등)
    • 엔티티가 변경되면 API스펙이 변한다.
    • ㅜㅜ
  @PostMapping("/api/v1/members")
    public CreateMemberResponse  saveMemberV1(@RequestBody @Valid Member member){
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }
    
   @Data
    static class CreateMemberResponse{
        private Long id;

        public CreateMemberResponse(Long id) {
            this.id = id;
        }
    }
    
@Entity
@Getter@Setter
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @NotEmpty
    private String name;
}

만약 name을 userName으로 변경한다면 API spec이 변경된다. 클라이언트에서 name으로 사용하고 있었다면 장애가 발생하게 된다.  ㅜㅜ

 

따라서 api spec을 위한 별도의 dto가 필요해!

 

 

2. 회원 등록 : DTO 활용


  • 기존 Member를 대신에 DTO인 CreateMemberRequest를 활용.
  • 엔티티와 프레젠테이션 계층을 위한 로직을 분리함!
  • 엔티티와 API 스펙을 명확하게 분리 가능.
  • 따라서 엔티티가 변해도 API 스펙이 변하지 않는다.
  • api는 항상 요청이 들어오고 나가는것은 절대 엔티티 사용하지 않고 dto사용하자.
  • entity를 외부로 노출하지 말자
    @PostMapping("/api/v2/members")
    public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request){
        Member member = new Member();
        member.setName(request.name);

        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;
        }
    }

 

3. 회원 수정


  • put : 덮어쓰기
  • 사실 여기서는 patch나 post를 활용해야 함 ㅜ
   @PutMapping("/api/v2/members/{id}")
    public UpdateMemberResponse updateMemberV2(@PathVariable("id") Long id,
                                               @RequestBody @Valid UpdateMemberRequest request){

        memberService.update(id, request.getName());
        Member findMember = memberService.findOne(id);

        return new UpdateMemberResponse(id, findMember.getName());

    }
    

    @Data
    static class UpdateMemberRequest{
        private String name;
    }

    @Data
    @AllArgsConstructor
    static class UpdateMemberResponse{
        private Long id;
        private String name;

    }
   @Transactional
    public void update(Long id, String name) {
        Member member = memberRepository.findOne(id);
        member.setName(name);
    }
Hibernate: 
    update
        member 
    set
        city=?,
        street=?,
        zipcode=?,
        name=? 
    where
        member_id=?

 

4. 회원 조회 : 엔티티 직접 사용


 @GetMapping("/api/v1/members")
    public List<Member> membersV1(){
        return memberService.findMembers();
    }

[
    {
        "id": 1,
        "name": "1번",
        "address": {
            "city": "ㅇㅇ",
            "street": "ㅏㅏ",
            "zipcode": "55"
        },
        "orderList": []
    },
    {
        "id": 2,
        "name": "2",
        "address": {
            "city": "서",
            "street": "56",
            "zipcode": "2321"
        },
        "orderList": []
    }
]

 

  • 엔티티를 직접 노출하면 엔티티의 모든 정보가 노출됨.
  • @JsonIgnore을 통해 제한할 수 는 있다.
        @JsonIgnore
        @OneToMany(mappedBy = "member")
        private List<Order> orderList = new ArrayList<>();​
  • 하지만 회원과 관련된 조회 API는 하나는 아닐 것이다..
  • 따라서 엔티티에 요런게 들어가기 시작하면 답이 없다.
  • 또한 엔티티에 화면에 뿌리기 위한 로직이 추가되는 것..(프레젠테이션 계측을 위한 로직 추가됨)
  • 엔티티의 순수성이 깨짐.
  • 또한 엔티티 변경시 API 스펙이 변경됨 ㅜㅜㅜㅜ 굉장히 심각함
  • 기본적으로 엔티티의 모든 값이 노출된다. 응답 스펙을 맞추기 위해 로직이 추가된다.
    (@JsonIgnore, 별도의 뷰 로직 등등)
  • 실무에서는 같은 엔티티에 대해 API가 용도에 따라 다양하게 만들어지는데,
    한 엔티티에 각각의 API를 위한 프레젠테이션 응답 로직을 담기는 어렵다.
  • 엔티티가 변경되면 API 스펙이 변한다.
  • 추가로 컬렉션을 직접 반환하면 항후 API 스펙을 변경하기 어렵다.(별도의 Result 클래스 생성으로 해결)
    • 유연성이 떨어진다.
    • 컬렉션을 직접 반환하는 경우 json 배열로 나가기 때문에 다른 내용을 추가할 수 없음..

 

 

5. 회원 조회 : DTO 사용


  • 엔티티를 DTO로 변환해서 반환한다.
  • 엔티티가 변해도 API 스펙이 변경되지 않는다.
  • 추가로 Result 클래스로 컬렉션을 감싸서 향후 필요한 필드 추가 가능.
    • generic으로 data를 받았다.
    • result를 return하는 경우 json배열로 나가는 것이 아니라
    • json의 data안에 조회한 memberList가 json형태로 나가게 된다.
  @GetMapping("/api/v2/members")
    public Result memberV2(){
        List<Member> findMembers = memberService.findMembers();

        List<MemberDto> collect = findMembers.stream().map(m -> new MemberDto(m.getName()))
                .collect(Collectors.toList());

        return new Result(collect);
    }

    @Data
    @AllArgsConstructor
    static class Result<T>{
        private T data;
    }

    @Data
    @AllArgsConstructor
    static class MemberDto{
        private String name;
    }

필드를 추가해 보자 어떻게 될까?

    @GetMapping("/api/v2/members")
    public Result memberV2(){
        List<Member> findMembers = memberService.findMembers();

        List<MemberDto> collect = findMembers.stream().map(m -> new MemberDto(m.getName()))
                .collect(Collectors.toList());

        return new Result(collect.size(), collect);
    }

    @Data
    @AllArgsConstructor
    static class Result<T>{
        private int count;
        private T data;
    }

 

6. 참고) stream.map


    /**
     * Returns a stream consisting of the results of applying the given
     * function to the elements of this stream.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element
     * @return the new stream
     */
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

 

7. GitHub : 210820 DTO


 

GitHub - bsh6463/SpringBootJPA1

Contribute to bsh6463/SpringBootJPA1 development by creating an account on GitHub.

github.com

 

8. 요약

API 요청이 들어올때, 응답이 나갈 때 Entity를 외부에 노출하지 말고, DTO를 사용하자.
Comments