[JPA] DTO의 필요성
스프링은 3계층으로 나뉜다.
프레젠테이션 계층은 Client(화면)과 네트워크 통신을 하며 화면구현을 담당한다. 서비스 계층은 데이터를 처리하는 비즈니스 로직을 가지고 있다. 데이터 엑세스 계층은 DB와 통신하여 데이터를 CRUD하는 역할을 담당한다.
JPA의 엔티티(Entity)는 관계형DB의 테이블과 매핑되는 개념으로 데이터 엑세스 계층에서 사용하는 기본단위이다. 엔티티는 테이블 정보가 담긴 클래스로 매우 민감한 정보를 가지고 있다. 그러므로 엔티티가 외부로 노출되는 위험은 없어야 한다. 엔티티의 외부노출을 막기 위해 엔티티를 정제한 클래스를 하나 생성하는데, 그것이 DTO(Data Transfer Object)이다.
DTO(Data Transfer Object)
DTO는 프레젠테이션 계층에서 화면과 데이터를 주고 받기 위해 존재하는 객체이다.
DTO를 사용하는 이유는 크게 2가지이다.
1. 다양한 화면에 맞는 스펙을 제공할 수 있다.
화면에 필요하지 않은 데이터를 주고받으면 낭비가 된다. 예를들어, 책의 이름과 가격을 화면에 구현하려고 한다. 엔티티는 책의 이름,저자,출판사,가격,출판일 등등 다양한 데이터가 들어가 있다. 엔티티로 화면과 통신하면 화면에 필요없는 데이터까지 넘긴다. 화면에 맞게 최적화하려면 화면에 맞는 DTO를 생성하면 된다. 이름과 가격 필드만 있는 DTO를 생성하고 엔티티를 DTO로 변환하면 된다. 그리고 변환된 DTO를 화면에 넘기면 된다.
2. 엔티티의 외부 노출을 막을 수 있다.
화면과 프레젠테이션 계층이 통신은 JSON같은 방식으로 이루어진다. 통신되는 데이터는 일정한 구조를 이룬다. 만약 데이터가 엔티티라면 엔티티의 구조가 모두 외부로 노출되는 것이다. 엔티티는 테이블과 매핑된 데이터로 굉장히 민감한 정보이다. 엔티티는 내부에 숨기고 대신 DTO로 필요한 데이터만 화면에 넘기는 것이 올바르다.
DTO로 저장(save) API 구현하기
엔티티를 외부로 노출한 로직
@PostMapping("/api/v1/members")
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member){
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
Post방식으로 데이터를 받는 로직이다. RequestBody에 담긴 데이터는 바로 엔티티인 Member에 저장된다. 이런 방식은 엔티티 구조를 외부에 노출시키므로 위험하다.
DTO를 사용한 로직
@PostMapping("/api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request){
Member member = new Member();
member.setName(request.getName()); // DTO->엔티티
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
@Data //DTO 역할
static class CreateMemberRequest{
private String name;
}
CreateMemberRequest는 DTO이다. POST 방식으로 전송된 데이터는 DTO에 저장되고 프레젠테이션 계층에서 DTO->엔티티로 변환된다. 그리고 변환된 엔티티는 서비스계층으로 넘겨진다. DTO-엔티티 변화로직은 프레젠테이션 계층, 서비스 계층 중 적절한 곳에서 처리하면 된다. 어느 계층이 적절한지는 따로 포스팅을 만들어 다루어 보겠다.
DTO로 수정(update) API 구현하기
@PutMapping("/api/v2/members/{id}")
public UpdateMemberResponse updateMemberV2(
@PathVariable("id") Long id,
@RequestBody @Valid UpdateMemberRequest request){
memberService.update(id, request.getName()); // 수정할 엔티티 id와 수정 데이터 넘기기
Member findMember = memberService.findOne(id); // 수정된 엔티티 조회
return new UpdateMemberResponse(findMember.getId(),findMember.getName()); // 엔티티 -> DTO 변환
}
수정도 저장과 동일하다.
수정할 엔티티의 id와 수정될 데이터를 DTO로 받고 Service 계층의 수정 메소드를 호출한다. 수정 완료후, 수정된 엔티티를 조회하고 DTO로 변환하여 반환한다. DTO는 이렇듯, 엔티티를 은닉하고 다양한 화면에 맞는 데이터(Data)를 전송(Transfer)하기 위해 존재하는 객체(Object)이다.
참고자료