티스토리 뷰

V1

    @PostMapping("/api/v1/members")
    public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member){
        //@RequestBody request로 받은 json 데이터를 객체에 넣어준다.

        Long id = memberService.join(member);

        return new CreateMemberResponse(id);
    }

 

entity

package jpabook2.jpashop2.domain;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
public class Member {

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

    @NotEmpty
    private String name;

    @Embedded // jpa 내장타입 일 수 있다는 표시
    private Address address;

    @OneToMany(mappedBy = "member") //하나의 member에서 여러개의 order이다. mappedBy를 연관관계의 주인을 지정한다. 주인인 order 클래스 쪽은 그대로 두고 아래의 무엇으로 지정이 되는지 필드명을 적어준다. 이곳의 값을 변경한다고 해서 fk값이 변경되지 않는다.
    private List<Order> orders = new ArrayList<>();

}

 

다음과 같은 코드는 문제점이 있다.

  • api를 만드는 팀에서 name이 아닌 username으로 변경할 경우 데이터를 받지 못한다. (그럴 일이 있을까 싶지만 그런 일이 있다고한다.
  • name을 NotEmpty valid하며 사용하는데, 만약 같은 객체를 여기저기서 사용할 때에 어떤 상황에서는 NotEmpty를 사용하지 않을 수도 있다.
  • 여러가지 가입의 경우 (소셜, 일반 등)에 하나의 Entity로 받을 경우 위험하다. 하나로 감당할 수 없다.

 

V2

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

 

V2의 장점

  • api 스펙은 바뀌지 않는다
  • username으로 바꿀 경우 어차피 개발단에서 에러가 나기에 (setName이 에러날 것이다) 바로 인지할 수 있고 바꿀 수 있다.
  • entity는 필요없는 address 등등 많은 값을 받는 것처럼 되어있다. api 스펙을 알기 힘들다. 하지만 CreateMemberRequest값을 보고 name만 받는 것으로 스펙을 바로 알 수가 있다. (유지보수 시에 얘만 보면되는구나, 라고 생각할 수 있다)

업데이트

- controller

    @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(findMember.getId(),findMember.getName());
    }
    
   @Data
    @AllArgsConstructor
    static class UpdateMemberResponse{
        private Long id;
        private String name;
    }

    @Data
    static class UpdateMemberRequest{
        private Long id;
        private String name;
    }

 

- service

    @Transactional
    public void update(Long id, String name) {
        Member member = memberRepository.findOne(id);
        member.setName(name);

        //member를 반환해도 되지만, 커맨드와 쿼리를 철저히 분리하는 편이다.(김영한) id 정도만 따로 보낸다거나,

    }

 

Controller부분을 살펴보면,findMember가 아닌 service에서 update 후 member를 리턴하면 같은 객체가 되지만, 이럴 경우 트랙잭션안에 있던(쿼리를 날리는 엔티티)를 그대로 반환하게 된다. 김영한 개발자님은 그 둘을 철저히 분리하여 나중에 나올 이슈를 방지하는 편이라한다. 내 생각에도 같은 엔티티를 보내게 되면 나중에 유지보수시에 여러 문제가 발생할 불안감이 있기도 하다.

 

조회

V1

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

 

간단하게 이렇게 작성하면 문제가 있다.

  • 이렇게 엔티티 그대로 받게 되면 필요없는 orders객체가 반환된다. (@JsonIgnore 어노테이션으로 반환시키지 않을 수 있다. 하지만 다른 api에서는 필요하다면..?)
  • name이 username으로 변경되면 또 오류가 있을 것이다.
  • 여기에 count를 추가해달라고 할 경우 entity가 깨져버린다.

V2


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

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

 

이렇게 DTO를 만들어 반환된 findMembers의 값을 모두 넣고 result 객체로 묶어 반환하면 위의 문제점이 모두 해소된다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
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 31
글 보관함