DTO란?
DTO란 Data Transfer Object의 약자로, 계층 간 데이터 전송을 위해 도메인 모델 대신 사용되는 객체이다. 여기서 계층이란 Presentation(View, Controller), Business(Service), Persistence(DAO, Repository)를 의미한다.
DTO의 특징
- 데이터를 저장하는 용도로만 사용되어야 한다. 데이터에 대한 getter, setter 만을 가져야 한다.
- 저장, 조회를 제외한 어떠한 비즈니스 로직도 있어서는 안 된다.
- 하지만, 데이터 전송을 위해 직렬화, 역직렬화 메커니즘은 포함할 수 있다.
직렬화란 객체를 JSON, XML, 바이트스트림 등으로 변환하는 것을 의미한다. 역직렬화는 직렬화의 반대이다.
도메인 대신 DTO를 사용하는 이유
도메인 모델을 계층 간 전달에서 사용하면, 혹시나 도메인 모델의 메서드를 호출하여 상태를 변경시킬 위험이 있다. 또한, 글 쓰기와 글 수정같이 화면이 다른 곳에서 같은 도메인을 사용한다면 불필요한 속성을 가지고 있어야 하는 경우가 생긴다. 도메인 대신 DTO를 사용한다면 UI에 맞는 속성만 가질 수 있고 도메인 모델이 외부에 노출되어 발생할 수 있는 보안 문제를 방지할 수 있다. 즉, 도메인 모델을 캡슐화하여 보호할 수 있다.
또한, 도메인 모델을 계층 간 전송에 사용하면, 모델과 뷰가 강하게 결합될 수 있으며 뷰의 요구사항 변화로 도메인의 코드를 변경해야 할 일이 생길 수 있다. 이는 좋지 않은 일이며 이때 DTO를 사용하여 결합을 느슨하게 할 수 있다.
그렇다면? DTO의 사용 범위는?
결론부터 말하자면 영속 계층(Persistence Layer)까지 DTO가 전달되는 것은 지양하는 것 같다.
그럼, 컨트롤러와 서비스 레이어 중 어느 곳에서 DTO를 변환해야 할까?
A Service Layer defines an application’s boundary [Cockburn PloP] and its set of available operations from the perspective of interfacing client layers. It encapsulates the application’s business logic, controlling transactions and coor-dinating responses in the implementation of its operations. - 마틴 파울러 -
마틴파울러는 Service 레이어란 애플리케이션의 경계를 정의하고 비즈니스 로직 등 도메인을 캡슐화하는 역할이라고 정의한다.
Controller에서 처리하는 경우 View로부터 받아온 DTO를 Controller에서 Domain(Entity)으로 변환하고 Service 레이어에게 이를 전달하여 작업을 수행한다.
하지만, 이렇게 하면 여러 문제점이 발생한다.
- View에 반환할 필요가 없는 데이터까지 Domain 객체에 포함되어 Controller까지 넘어온다.
- Controller가 여러 Domain 객체들의 정보를 조합해서 DTO를 생성해야 하는 경우, Service(응용 계층) 로직이 Controller에 포함되게 된다.
- 예를 들어, 쇼핑몰 웹 사이트에서 특정 상품의 상세 정보를 보여주는 기능이 있을 때 해당 상품의 정보(Product Domain), 판매자의 정보(Seller Domain), 상품에 대한 리뷰 정보(Review Domain) 등 여러 가지 정보를 한 번에 보여주어야 한다.
- 이런 경우, 각각의 Domain 객체들을 따로 DTO로 변환해서 전달하면 클라이언트에서 이들을 다시 조합해야 하므로 비효율적일 수 있다. 따라서 서버에서 미리 여러 Domain을 조합해서 DTO를 만들고 이를 클라이언트에 전달하는 것이 더 나은 방법이다.
- 하지만, 이렇게 서버에서 여러 Domain을 조합하는 과정에서 비즈니스 로직을 포함할 수 있다. 판매자의 정보 중 어떤 정보를 포함할 것인지, 리뷰 정보 중 최근 리뷰만을 선택할 것인지 등의 결정은 서비스 계층에서 이루어져야 하는데 이러한 결정이 Controller로 이동한 것이므로, 다른 방법을 선택하는 것이 좋다.
- 여러 Domain 객체들을 조회해야 하기 때문에 하나의 Controller가 의존하는 Service의 개수가 많아진다.
- Service가 DTO를 반환하면 이런 문제들이 상쇄된다.
하지만, Service 레이어에 DTO가 들어오지 않아야 여러 종류의 컨트롤러에서 해당 Service를 사용할 수 있다. 그러나 현업에서는 여러 종류의 컨트롤러가 한 서비스를 사용하는 경우보다는, 한 종류의 컨트롤러가 서비스를 사용하기 때문에 엄격히 제안할 필요가 없다. 고 한다.
결론
Controller에서 DTO 변환은 몇몇 문제가 있기 때문에 Service에서 하는 것이 좋은 선택 같다.
주의할 점
- 잘못하면 DTO가 Repository까지 흘러가 아래의 문제를 발생시킬 수 있다.
-
- 비즈니스 로직의 캡슐화 위반
- 재사용성의 감소
- 테스트의 어려움
- DTO 변환 시 서비스 메서드 상위에서 DTO체크 및 도메인 변환을 해야 한다. 그리고 변환된 도메인을 사용해서 비즈니스 로직을 수행해야 한다.
public BoardDto createBoard(BoardDto BoardRequestDto) {
Board board = BoardRequestDto.toEntity();
//비즈니스 로직
return BoardDto.from(boardRepository.save(board));
}
참고
https://hudi.blog/data-transfer-object/
https://inpa.tistory.com/entry/JAVA-☕-직렬화Serializable-완벽-마스터하기
https://martinfowler.com/eaaCatalog/dataTransferObject.html
https://www.inflearn.com/questions/1091482/controller의-dto를-repository에서-사용할-수-없는-이유