Spring/JPA

JPA - API 기본

jddng 2022. 3. 21. 19:31
728x90
반응형

 

 

 API에서는 하나만 기억하면 된다. 파라미터를 받거나 전송할 때 Entity를 사용하지 않고 DTO를 사용해야하는 것을 명심하자.

 

DTO 사용 시 이점

 

 

  • 엔티티 대신에 RequestBody와 ResponseBody에 각각 DTO를 매핑한다.
  • 엔티티와 프레젠테이션(화면) 계층을 위한 로직을 분리할 수 있다.
  • 엔티티와 API 스펙을 명확하게 분리할 수 있다.
  • 엔티티가 변해도 API 스펙이 변하지 않는다.
  • 실무에서는 엔티티를 API 스펙에 노출하면 안된다.

 

 

 

엔티티로 매핑시 문제점

 

  • 엔티티에 프레젠테이션 계층을 위한 로직이 추가된다
  • 엔티티에 API 검증을 위한 로직이 들어간다.(@NotEmpty 등)
  • 실무에서는 회원 엔티티를 위한 API가 다양하게 만들어지는데, 한 엔티티에
    각각의 API를 위한 모든 요청 요구사항을 담기는 어렵다.
  • 엔티티가 변경되면 API 스펙이 변한다.

 

 

등록 API

 

등록 DTO

@Data
class CreateMemberResponse {
    private Long id;

    public CreateMemberResponse(Long id) {
        this.id = id;
    }
}

@Data
class CreateMemberRequest {
    private String name;
}

 

@PostMapping("/api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {

    Member member = new Member(request.getName(), null);

    Long id = memberService.join(member);
    return new CreateMemberResponse(id);

}

 

/api/v1/members

요청

{
    "name" : "hello"
}
 
응답
{
    "id"3
}

 


 

수정 API

 

  • 수정 시 주의할 점은 변경 감지를 사용하여 데이터를 수정해야 한다.(merge 사용 금지)
  • PUT과 PATCH, POST의 사용
          - PUT : 전체 업데이트를 할 때 사용
          - PATCH, POST : 부분 업데이트를 할 때 사용

 

수정 DTO

@Data
class UpdateMemberRequest {
    private String name;
}

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

 

@PutMapping("/api/v2/members/{id}")
public UpdateMemberResponse updateMemberV2(@PathVariable("id") Long id,
                                           @RequestBody @Valid UpdateMemberRequest request) {
    memberService.update(id, request.getName());
    Member findMember = memberService.findMember(id);
    return new UpdateMemberResponse(findMember.getId(), findMember.getName());
}

 

/api/v1/members

요청

{
    "name" : "change hello"
}
 
응답
 
{
    "id"3,
    "name""change hello"
}

 

 

조회 API

 

  • 조회 API 역시 DTO를 사용해야 한다.

 

조회 DTO

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

    public Result(T data) {
        this.data = data;
    }
}

@Data
class MemberDto {
    private String name;

    public MemberDto(String name) {
        this.name = name;
    }
}

 

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

 

 여기서 중요한점은 Result 클래스로 컬렉션을 감싸서 전달한다는 점이다. 그 이유는 컬렉션을 전송하게 되면 배열로 전송이 되는데 배열로 전송이되면 필드를 추가 할 수 없게 된다. 따라서 컬렉션을 전달할 때는 향후 개별 필드를 추가할 수 있도록 클래스로 감싸서 전달하는게 좋다.

 

 

/api/v4/members

요청

 

응답

{
    "data": [
        {
            "name""user1"
        },
        {
            "name""user2"
        },
        {
            "name""change hello"
        }
    ]
}

 위 응답에서 보면 클래스로 감싸서 전달하였기 때문에 필드를 자유롭게 추가할 수 있다.

(컬렉션으로 전달된 data를 보면 배열로 전달된것을 볼 수 있다.)

 

728x90
반응형