스프링을 공부하다보면 DTO라는 개념이 등장한다.
데이터의 '전송용' 객체를 의미하는, Entity의 사본이라고 할 수 있다.
즉, 직접 DB에서 사용하는 Entity와 클라이언트에게 전송하기 위한 객체를 구분하기 위해 사용한다는 것이다.
[참고] Entity
DB에서 방금 꺼내 온 데이터 원본이거나, 연산이 끝나서 곧 DB로 들어가 원본이 될 객체
그렇다면 왜 이렇게 객체를 구분해야 할까?
먼저 DTO가 없다고 가정해보자.
DTO가 없다는 뜻은 하나의 객체 또는 데이터가 Repository까지 들어간다는 뜻이다.
만약 하나의 객체, 데이터가 Repository까지 들어가면?
장점
단점
스프링 3계층(C/S/R) 간의 이동 시 데이터가 변환될 위험이 존재한다.
클라이언트에게 굳이 전달되지 않아도 되는 데이터(ex. created_at, updated_at과 같은 시스템 활용 데이터)를 포함해서 네트워크 비용만 올라가고 보안 측면에서도 좋지 않다.
클라이언트가 필요한 데이터를 미포함 할 수 있다. (ex. 연관 관계는 없지만 화면에는 같이 뿌려져야 하는 경우, '생년월일 저장 -> 나이 출력'과 같이 연산이 필요한 경우)
단일 책임 원칙에 위배된다.
1) 클라이언트에 무슨 일이 생겼을 때 DB까지 바꿔야 될 수도 있다. 그런데 DB에 그대로 저장될 데이터는 아니면...?
2) DB에 무슨 일이 생겼을 때 클라이언트도 바꿔야 될 수도 있다. 예를 들어, DB 칼럼 이름이 바꼈을 때 클라이언트 호출도 바뀌어야 할 수 있다.
=> 데이터를 옮기는 객체가 하나일 경우, 여러 계층에서 그 객체에 수정을 가하면 단일 책임 원칙이 바로 깨진다.
위 장단점을 보면 DTO를 사용해야 하는 이유가 명확해진다.
먼저 Client에서 전송받은 데이터를 DB까지, 혹은 DB의 데이터를 클라이언트에게 전달하는 과정은 다음과 같다.
이 중 어디에서 Entity <-> DTO 변환이 이루어지는 것일까?
사실 이 문제에 대한 주장은 사람마다, 프로젝트마다 다르다.
내 입장은 Service에서 변환해야 한다는 입장이다.
Controller에 로직이 있다는 게 이상하고 Repository는 DB에 직접 접근하는 역할을 하는데 DTO 변환까지 책임지면 책임이 너무 커진다는 것이 그 이유이다.
하지만 Controller에서 변환해야 한다는 입장도 만만치 않다.
'DTO로 변환한다는 것은 결국 클라이언트에 전달해주기 위함인데, 그걸 왜 굳이 Service가 알아야 하냐?'라는 주장을 펼친다.
Service에서 변환한다는 것은 클라이언트가 어떤 필드가 필요한지 Service까지 알려줘야 한다는 뜻인데, 너무 비효율적이라는 입장이다.
실제로 많은 시니어 개발자들이 이 주장을 펼치고 있고, 그 분들의 경험에 반박할 생각은 없다.
하지만 이 주장에 의문이 드는 한 가지가 있다.
물론 서로 다른 도메인에서 같은 계층끼리 호출하는 경우는 없지만 (만약 그렇다면 설계가 잘못된 거라고 생각) Service가 다른 도메인의 Repository를 부르거나 Controller가 다른 도메인의 Service를 부르는 일은 많이 있다. 그리고 실제로 이런 경우에는 모듈이 독립적으로 잘 설계되었다고 말한다.
그렇다면 이런 경우에 Controller에서 entity를 DTO로 변환하면 너무 복잡해지지 않을까? Controller를 주장하는 사람들의 입장에서 이 질문에 대한 답을 들어보고 싶다.