2 minute read

회원 등록 API


@RestController // controller + responseBody
@RequiredArgsConstructor{
    private final MemberService memberservice;

    @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검증을 위한 로직이 들어간다.
  • 실무에서는 회원 엔티티를 위한 API가 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 모든 요구사항을 담기 어렵다
  • 엔티티가 변경되면 API 스펙이 변한다

결론: API요청 스펙에 맞추어 DTO를 파라미터로 받는다.


회원 수정API

@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 ndw UpdateMemberResponse(findMember.getId(), findMember.getName());
}

@Data
static class UpdateMemberRequest{
    private String name;
}

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

//MemberService에 추가//
@Transactional
public void update(Long id, String name){
    Member member = memberRepository.findOne(id);
    member.setName(name);
}


회원 조회API



@RestController
@RequiredArgsConstructor
public class MemberApiController{

private final MemberService memberService;

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

@GetMapping("/api/v2/members")
public Result memberV2(){

    List<Member> findmembers = memberService.findMembers();

    List<MemberDto> collect = finMembers.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;
      }
}

응답 값으로 엔티티를 직접 외부에 노출

문제점

  • 엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.
  • 기본적으로 엔티티의 모든 값이 노출된다.
  • 응답 스펙을 맞추기 위해 로직이 추가된다. (@JsonIgnore, 별도의 뷰 로직 등등)
  • 실무에서는 같은 엔티티에 대해 API가 용도에 따라 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 프레젠테이션 응답 로직을 담기는 어렵다.
  • 엔티티가 변경되면 API 스펙이 변한다.
  • 추가로 컬렉션을 직접 반환하면 항후 API 스펙을 변경하기 어렵다.(별도의 Result 클래스 생성으로 해결)

결론: API 응답 스펙에 맞추어 별도의 DTO를 반환한다.


API개발 고급 준비

조회용 샘플 데이터 입력

UserA

  • JPA1 BOOK
  • jPA2 BOOK

UserB

  • SPRING1 BOOK
  • SPRING2 BOOK
@Component
@RequiredArgsConstructor
public class InitDb {

      private final InitService initService;

      @PostConstruct
      public void init() {
          initService.dbInit1();
          initService.dbInit2();
      }

      @Component
      @Transactional
      @RequiredArgsConstructor
        static class InitService {
        private final EntityManager em;
        public void dbInit1() {

        Member member = createMember("userA", "서울", "1", "1111"); 
        em.persist(member);
          
        Book book1 = createBook("JPA1 BOOK", 10000, 100);
        em.persist(book1);

        Book book2 = createBook("JPA2 BOOK", 20000, 100);
        em.persist(book2);

        OrderItem orderItem1 = OrderItem.createOrderItem(book1, 10000, 1);

        OrderItem orderItem2 = OrderItem.createOrderItem(book2, 20000, 2);

        Order order = Order.createOrder(member, createDelivery(member),orderItem1,orderItem2);
        em.persist(order);
        }


        public void dbInit2() {

        Member member = createMember("userB", "진주", "2", "2222"); em.persist(member);
           
        Book book1 = createBook("SPRING1 BOOK", 20000, 200);
        em.persist(book1);
          
        Book book2 = createBook("SPRING2 BOOK", 40000, 300);
        em.persist(book2);
           
        Delivery delivery = createDelivery(member);
        OrderItem orderItem1 = OrderItem.
    
        createOrderItem(book1, 20000, 3);
        OrderItem orderItem2 = OrderItemcreateOrderItem(book2, 40000, 4);
        
        Order order = Order.createOrder(member, delivery, orderItem1,orderItem2);
        em.persist(order);
        }

        private Member createMember(String name, String city, String street, String zipcode) {
            Member member = new Member();
            member.setName(name);
            member.setAddress(new Address(city, street, zipcode));
            
            return member;
        }
        private Book createBook(String name, int price, int stockQuantity) {
            Book book = new Book();
            book.setName(name);
            book.setPrice(price);
            book.setStockQuantity(stockQuantity);
            
            return book;
        }
            private Delivery createDelivery(Member member) {
            Delivery delivery = new Delivery();
            delivery.setAddress(member.getAddress());
              
            return delivery;
            }
    }
}

댓글남기기