본문 바로가기
WEB

DTO, Entitiy간의 변환은 어디서 일어나야 할까? 프로젝트 구조에 대한 고찰.

by jinukix 2021. 12. 13.

프로젝트를 진행하면서 프로젝트 구조에 많은 생각을 했습니다.

먼저 레이어드 아키텍처에 대해 알아봅시다. 레이어드 아키텍처는 간단히 이야기하자면 책임과 성격이 다른 것을 구별해 레이어별로 분리하는 것을 이야기합니다. 각 계층은 자신의 계층의 책임에만 충실해야 합니다. 웹 애플리케이션을 만들 때 일반적으로 사용하는 3-계층 레이어는 다음과 같습니다. 

웹 애플리케이션을 만들 때 일반적으로 사용하는 3-레이어 아키텍처의 구분은 다음과 같습니다.

프레젠테이션 계층은 애플리케이션 앞단에서 Dispatcher의 역할을 수행합니다. Controller가 이 계층에 해당한다고 볼 수 있습니다.

서비스 계층은 비즈니스 로직을 처리하는 영역입니다. 프레젠테이션 계층과 데이터 액세스 계층 사이를 연결하고 두 계층이 직접적으로 통신하지 않게 분리하는 역할을 합니다.

데이터 액세스는 DB와의 연동 작업을 수행합니다. 저희 프로젝트는 MyBatis를 사용했습니다. Mapper 인터페이스를 작성해 데이터 액세스 계층을 구현했습니다.

UserService와 같은 서비스를 인터페이스로 생성하고 UserServiceImpl이라는 구현체를 생성해 사용하는 방식을 관습적인 추상화라고 합니다. 이러한 추상화 방식을 사용하면서 얻을 수 있는 장점은 구현체와 추상체를 분리함으로써 각자 독립적으로 변형이 가능하고 확장이 가능하다는 점입니다. 객체 지향의 원칙 중 하나인 OCP의 원칙을 실현해주는 설계 방식이라고도 볼 수 있습니다. 

저희 프로젝트의 경우 현재 서비스 인터페이스와 구현체 클래스 간 관계가 1:1로 구성되고 있습니다. 이러한 구조에서 관습적인 추상화를 통한 장점들은 무색해진다고 생각합니다. 이후 서비스 확장을 고려해 관습적인 추상화를 도입하는 방법도 고려해봤지만, 현재는 오히려 구조의 복잡화만 가지고 올 것이라 판단했습니다.

다음으로는 Entity와 DTO 클래스에 대해 알아보겠습니다.

Entity 클래스는 실제 DB 테이블과 매핑되는 클래스입니다. Entitiy 값을 변경할 여지를 최소화해야 한다고 생각해 Setter 클래스를 최소한으로 사용하기로 했습니다. DTO 클래스는 계층 간 데이터 교환을 위한 객체입니다. Entitiy가 있음에도 DTO를 사용하는 이유에 대해 생각해보겠습니다.

테이블과 매핑되는 Entity는 클래스가 변경된다면 여러 클래스에 영향이 가게 됩니다. Request, Response 데이터는 자주 변경됩니다. Entity를 요청/응답에 사용하는 경우 Model과 View에 강한 결합이 발생하게 됩니다. 따라서 Request, Response를 처리할 DTO 클래스가 필요했습니다. 그리고 도메인 객체를 응답으로 사용하는 경우 Entity의 모든 속성이 노출되게 됩니다.

그렇다면 요청과 응답을 처리하는 Controller에서는 DTO를 통해 처리할 것이고, Data Aceess 계층에서는 Entitiy를 통해 처리하면 되는데, 그렇다면 DTO에서 Entitiy 간의 변환 작업은 어디서 일어나는 것이 좋을까요?? 처음 제가 생각한 방법은 Controller에서 변환하는 방법입니다. 선택한 이유는 다음과 같습니다.

1. 서비스 레이어에는 비즈니스 로직만 작성돼야 한다고 생각합니다. 변환하는 작업은 비즈니스 로직이라기보다는 애플리케이션의 인터페이스의 역할을 수행하는 컨트롤러 레이어에서 하는 것이 좀 더 자연스럽다고 생각합니다.
2. 트랜잭션을 사용하는 서비스 계층에서 변환하는 작업이 일어난다면, 불필요한 작업이 트랜잭션에 포함되게 됩니다. 트랜잭션의 범위를 최소화해야 한다고 생각했습니다.

하지만 Controller에서 DTO를 변환하는 작업에서 다음과 같은 문제가 발생했습니다.

응답으르 보낼 Response 객체

위 Response 객체를 구성하기 위해선, 총 3개의 테이블(스터디 카페, 방, 예약)에 대한 정보가 필요했습니다. 그렇다면 총 3개의 엔티티 클래스를 서비스 레이어에서 컨트롤러 레이어로 넘겨야 하는 상황이 발생합니다. 이는 응답에 불필요한 데이터를 포함하고 있습니다.

그렇다면 컨트롤러에서 서비스로는 Entitiy 클래스로 보내지만 서비스에서 컨트롤러로 넘길 때는 DTO로 넘긴다? 그렇다면 이전에 진행했던 컨트롤러에서 변환하는 작업의 의미가 무색해지는 방법이라는 생각이 들었습니다.

다음은 스터디 카페를 등록하는 로직입니다. 먼저 컨트롤러 레이어에서 변환하는 경우입니다.

StudtCafeController
StudyCafeService

먼저 스터디 카페를 INSERT 하고 생성된 스터디 카페 레코드의 id를 받아와 다시 Room Entitiy에 등록해줘야 합니다. 물론 roomMapper.insertRoom 메서드 파라미터에 스터디 카페 id를 같이 넘겨서 처리하는 방법도 있습니다. Room Entity에 id 필드가 있음에도 불구하고 id 파라미터를 같이 넘기는 것은 좋은 방법은 아니라고 생각합니다.

뿐만 아니라 Entitiy 클래스인 Room에 스터디 카페 id를 등록하기 위해 Setter 메서드를 사용하게 되었습니다. 물론 Setter 메서드가 아닌 Build나 생성자를 이용해 새로 생성하는 방법도 존재합니다. 하지만 객체를 다시 생성하는 방식이라면, 컨트롤러에서 변환했던 작업의 의미가 있었을까라는 생각이 들었습니다.

다음은 서비스 레이어에서 변환하는 경우입니다.

StudyCafeController
StudyCafeService

다음과 같은 이유로 DTO와 Entity 간의 변환 작업은 서비스 레이어에서 처리하기로 했습니다.

정리하자면 구조, 방법을 정하는 것에는 정답은 없는 거 같습니다... 현재는 DTO에서 Entitiy로 변환하는 작업을 Service 레이어에서 처리하고 있지만 이후에 생각이 바뀔 수도 있다고 생각합니다. 하지만 어떤 방법을 선택하던 그 방법을 선택하는 과정, 선택하게 된 합리적인 이유가 중요하다고 생각합니다.

'WEB' 카테고리의 다른 글

Jenkins를 통한 CI & CD 구축하기.  (0) 2021.12.24
캐싱적용해서 읽기 작업 성능 개선하기.  (0) 2021.12.21
분산 환경에서 발생하는 세션 불일치 문제 해결하기.  (0) 2021.12.04
OAuth 2.0  (0) 2021.10.04
JWT  (0) 2021.10.04

댓글