회원 조회 API

단순 조회기 때문에 데이터를 변경할 필요 없다. resources>application.xml>jpa ddl-auto : none으로 하면 테이블 자동 드랍하지 않는다. DB의 데이터를 계속해서 쓸 수 있다!

V1 : memberService findMembers() => MemberRepository findAll() select m from Member m : Member 테이블의 모든 데이터 조회‼️ => Member 클래스에 Json 라이브러리 Jackson의 @JsonIgnore을 조회하지 않는 필드에 붙여주면 해당 필드는 조회 시에 제외된다. (API마다 다르기 때문에 특정 API 설계할 때에 이 API에 맞게 엔티티를 직접 수정하는 것은 매우 안 좋은 방법)

//회원 조회 첫번째 버전 엔티티 노출
@GetMapping("/api/v1/members")
public List<Member> membersV1(){//json으로 변환, 반환
    return memberService.findMembers();
}

문제점 : 응답 값으로 엔티티를 직접 외부에 노출

  • 엔티티에 프레젠테이션 계층(화면 구성)을 위한 로직이 추가된다.

  • 기본적으로 엔티티의 모든 값이 노출된다.

  • 응답 스펙을 맞추기 위해 로직이 추가된다. (@JsonIgnore, 별도의 뷰 로직 등등)

  • 실무에서는 같은 엔티티에 대해 API가 용도에 따라 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 프레젠테이션 응답 로직을 담기는 어렵다.

  • 엔티티가 변경되면 API 스펙이 변한다. (장애 원인, 매우 중요‼️) 엔티티 필드를 name->username으로 수정하면 엔티티를 직접 반환하기 때문에 name으로 반환되던 것이 매핑이 되지 않는다!

  • 추가로 컬렉션을 직접 반환하면 항후 API 스펙을 변경하기 어렵다.(별도의 Result 클래스 생성으로 해결) 별도의 클래스를 생성하면 컬렉션 필드 뿐만 아니라 추가사항 같은 것들도 쉽게 확장 가능하고, API 스펙 변경 불필요하다!(실무에서 반드시 변경 요구사항이 존재할 수 있다! 그러면 유연성 있게 확장성 좋게 변경이 가능해야한다‼️)

V2 : 회원 조회 API 역시 DTO를 생성해서 Update 요청과 응답에 이용한다!

@GetMappingg("/api/v2/members")
public Result membersV2() {
    List<Member> findMembers = memberService.findMembers();
    //엔티티 -> DTO 변환
    List<MemberDto> collect = findMembers.stream()
                  .map(m -> new MemberDto(m.getName()))
                  .collect(Collectors.toList());
          return new Result(collect);
}

아래 코드처럼 합쳐도 된다.

@GetMappingg("/api/v2/members")
public Result membersV2() {
    //엔티티 -> DTO 변환
    List<MemberDto> collect = memberService.findMembers().stream()
                  .map(m -> new MemberDto(m.getName()))
                  .collect(Collectors.toList());
          return new Result(collect);
}

별도의 클래스를 생성해서 리스트 반환 타입을 오브젝트(객체) 타입으로 바꿔서 반환한다‼️ 오브젝트(Result) 타입으로 반환하기 때문에 한번 껍데기를 씌우고 데이터 필드의 값은 리스트가 나간다!

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

Java8 문법 람다식 표현(컬렉션 foreach문)

List.stream().map(변수 -> 엔티티로 만들 객체).collect(Collectors.toList()); 아래 코드 예시 설명 1. List<OrderQueryDTO> 컬렉션에서 각 객체를 변수 o로 받고, 각 객체(OrderQueryDTO)의 id값들을 조회. 2 .collect(Collectors.toList()) : 리스트로 받는다!

List<OrderQueryDTO> result = findOrders();
List<Long> orderIds = result.stream()
                .map(o -> o.getOrderId())
                .colleect(Colleectors.toList());

Postman 확인 GET /api/v2/members

정리

  • API 설계, 개발 시에는 절대 절대 절대 절대로 엔티티를 직접 사용하거나 반환하면 안 된다!

  • 필요한 데이터만 DTO로 만들어서 사용!

  • API 스펙과 DTO가 일대일로 매칭되기 때문에 유지보수도 편리하다.

  • 엔티티 변경되도 API 스펙 변하지 않는다.

  • 컬렉션 타입의 경우, 오브젝트로 한번 감싸기 때문에 변경/추가사항이 생겨도 해당 오브젝트에 변경/추가하면 되기 때문에 확정성, 유연성이 매우 좋다!

Last updated