스프링으로 백엔드를 포팅하는 과정에서, DDD에서 주로 쓰이는 VO와 기존에 쓰던 DTO의 차이, 그리고 DAO와 Repository같이 상당히 유사한 기능을 함에도 이름이 다르고 용처가 다른 것들에 대해서 그 이유가 무엇인지 궁금해졌고, 이에 대해 알아보려고 한다.
DTO(Data Transfer Object)
란, 프로세스 간에 데이터를 전달하는 객체다.
현재 웹 서비스에서는 주로 계층들, 특히 컨트롤러 - 서비스(표현 - 도메인) 단계에서 서로 주고 받는 데이터 양식이다. 주클라이언트에서 서버 쪽으로 전송하는 요청 데이터, 서버에서 클라이언트 쪽으로 전송하는 응답 데이터 형식으로 데이터가 전송된다. (Request && Response)
마틴 파울러의 정의에 따르면, 서비스 계층이란 어플리케이션의 비즈니스 로직 즉, 도메인을 보호하는 계층이다. 즉, 이 정의를 명확히 지키기 위해서는 응용 계층(컨트롤러)에 도메인을 노출해서는 안된다. 따라서 도메인은 서비스 계층에서 DTO로 변환되어 컨트롤러로 전달되어야 한다.
DTO는 전송해야 하는 데이터를 담는 컨테이너 역할을 하며, 한 계층에서 다른 계층으로 데이터를 매핑하는 역할을 담당한다. 이를 통해 계층 간의 결합을 줄이고 애플리케이션의 성능과 유지보수성을 개선할 수 있다. DTO는 어떠한 비즈니스 로직을 가져서는 안 되며, 저장, 검색, 직렬화, 역직렬화 로직만을 가져야 한다.
여러 호출에서 전송할 수 있는 데이터를 집계하지만 한 번의 호출로만 제공되는 객체(DTO)를 사용하면 비용을 줄일 수 있다. 또한, 필요한 정보들로만 래핑하기 때문에, 불필요한 정보들을 제공하지 않게끔 할 수 있다 - 데이터를 캡슐화 할 수 있다.
왼쪽의 HTML, Json ↔ UserDTO에서 데이터의 변환과정이 직렬화(DTO → HTML, JSON)와 역직렬화(HTML, JSON → DTO)다. 위 사진에서 중요한 것은 Mapper다. DTO Mapper는 데이터 전송 객체(DTO)와 도메인 객체(Domain) 간의 매핑을 담당하는 일종의 변환기다.
위 사진을 아래와 같은 예시로 생각해보자.
public class User {
private Long id;
private Roles role;
private String email;
// 생성자, getter && setter
}
public class UserDTO {
private Long id;
private Roles role;
// 생성자, getter && setter
}
위 방식으로 DTO를 설정한다면, 우리는 ‘email’이라는 데이터를 캡슐화 한 것이다. DTO는 getter와 setter를 가지므로, Mapper를 다음과 같이 작성해볼 수 있다.
public class UserMapper {
public static UserDTO toUserDTO(User user) {
UserDTO userDTO = new UserDTO();
userDTO.setId(user.getId());
userDTO.setName(user.getName());
return userDTO;
}
public static User toUser(UserDTO userDTO) {
User user = new User();
user.setId(userDTO.getId());
user.setName(userDTO.getName());
return user;
}
}
DAO(Data Access Object)
란, 영속성(Persistence) 계층에 인터페이스를 제공하는 패턴이다. DAO는 애플리케이션 호출을 영속성 계층에 매핑함으로써 DB 세부 정보를 노출하지 않고 데이터에 접근하게 해준다. 이러한 분리방식은 단일 책임 원칙(SRP)을 지킬 수 있게 해준다.
DAO 사용의 가장 큰 장점은 서로에 대해 알 필요가 없는 두 계층을 엄격하게 분리할 수 있다는 점이다. 비즈니스 로직은 지속적으로 DAO 인터페이스에 의존할 수 있으며, 이에 따라 영속성 로직 변경은 DAO를 사용하는 계층에 영향을 미치지 않는다.