본문 바로가기
IT 공부/자바와 웹 애플리케이션

[Model Mapper] - Model Mapper 라이브러리 및 DTO, VO가 분리되는 이유

by exdus3156 2024. 1. 16.

1. 웹 MVC 패턴

MVC 패턴은 웹에서 사용되는 기초적인 디자인 패턴으로, 사용자의 요청에 대해 컨트롤러가 주도적으로 요청을 받고, 그것을 해석해 모델에서 적절한 처리를 요청한뒤, 그 결과를 뷰에 전송하는 패턴을 말한다.

객체지향이 으레 그렇듯이, 이러한 분업의 원리는 각자 자신이 해야할 것을 독립적으로 분리하고 각자의 역할에 전담하기 위해서다. 뷰(view)는 데이터를 받아 적절한 데이터 구조 및 그래픽 모양에 알맞게 배치하는 역할을 담당하고, 컨트롤러(controller)는 사용자의 요청을 해석해 모델과 뷰를 이어주는 역할이다.

모델(model)은 다소 추상적인데, 컨트롤러와 뷰를 제외한 나머지가 모델로 불리는데다 내부적으로 다시 여러 계층의 합으로 구성되기 때문이다.

 

 

크게 보면 모델(Model)은 도메인 영역(Domain Module)레포지토리(Repository)로 구성된다. 도메인 영역은 개발자가 서비스를 만들기 위해 주로 힘을 쏟는 곳이며, 비즈니스 로직이 담기는 곳이다. 도메인 주도 개발이니, 객체지향 설계니, ... 할 때 그 설계와 알고리즘이 담기는 핵심적인 공간이다.

레포지토리(Repository)는 데이터베이스와 상호작용하는 인터페이스를 말하며, 내부적으로 보다 더 DB 로직에 가까운 DAO 객체를 포함해 구현될 수도 있거나, 혹은 DAO 객체가 직접 레포지토리 인터페이스 그대로 활용되기도 하지만, 이론적으로 레포지토리는 도메인 영역에 가깝게 설계된다.

도메인과 레포지토리가 합쳐지면 비로소 사용자에게 적절한 서비스를 제공해줄 수 있다. 이것을 추상화해서 간단한 인터페이스로 사용할 수 있게 만드는 것이 서비스 인터페이스(service interface)다.

 

 

2. 서비스 레이어와 웹 레이어 간 경계

서비스 레이어는 웹 영역과 도메인 로직 간의 경계선이 된다. 즉, 서비스 인터페이스는 웹 컨트롤러 레이어를 위한 추상화된 서비스를 인터페이스로 제공하면서도 내부적으로 도메인 모듈과 협력한다.

따라서 서비스를 경계로 위에 있는 컨트롤러(controller)는 도메인 내부 로직에 대해 전혀 모른다. 그리고 도메인 모듈과 레포지토리 또한 자신들이 웹 플랫폼에서 사용되고 있는지 전혀 관심이 없다.

현대 소프트웨어는 하나의 main 함수에서 시작하는 구조가 아니라 보통 개별적인 함수들을 조합한 구조로 설계된다. 웹이나 앱도 그렇고, 심지어 게임도 그렇다. 단일 알고리즘이 하니라 여러 개의 서비스들을 하나의 박스 안에 배치하는 형태다. 화면에서 무언가를 클릭하면 해당하는 서비스가 실행되는 것이다.

따라서 어떤 플랫폼에서 서비스될 것인가와는 별개로 적절하게 추상화된 서비스 레이어라면 어떤 플랫폼에서도 요청되고 동작할 수 있어야 한다. 이것이 웹에 기반한 컨트롤러(controller)와 웹과는 독립적인 도메인(domain module)으로 분리되는 이유다.

웹 기반의 컨트롤러는 서비스에게 적절한 책임을 요청한 뒤 그 결과로 데이터를 받고자 한다. 이때 데이터는 서비스가 어떤 도메인에 의존하는지 알 수 없어야 한다. 정보 은닉이 이루어져야 한다는 뜻이다. 도메인과 레포지토리는 로직을 수행하는 책임이 있는 것일뿐, 웹(web)과 같은 플랫폼 기술과는 독립되어야 한다.

따라서 서비스 레이어는 도메인의 내부 구현 사항을 숨긴다. 이런 저런 로직을 수행하고 나면 그 결과를 도메인과는 완전히 별개인 데이터로 반환해야 한다. 그래서 DTO(Data Transfer Object)로 전달한다. 즉, 로직을 수행한 결과 데이터를 박스에 포장해서 주겠다는 뜻 그 이상 그 이하도 아닌 것이다.

만약 이렇게 수행하지 않고 도메인에서 사용하는 객체(VO 객체 등..)를 그대로 외부로 전달해버리면? 

만약 도메인 로직이 변경되어 객체가 수정된다면 그대로 웹 플랫폼 설계에도 변경의 압력이 생겨버린다. 무엇보다 개념적으로 웹 플랫폼 기반의 설계에서 컨트롤러가 서비스 인터페이스를 통해 도메인 내부 구조에 대한 너무 많은 정보를 알게 되는 것이 장기적으로 위험해질 수 있다.

 

 

3. DTO와 VO의 변환

간단한 프로젝트를 만들다보면 DTO와 VO를 서비스 레이어에서 변환해야 하는 일이 자주 생긴다. 그런데 DTO나 VO나 사실상 내부 구조가 똑같기 때문에 왠지 DTO를 사용하고 싶지 않아진다. 하지만 지금까지 설명한대로 DTO와 VO는 서로 독립된 맥락에 속해 있기 때문에 반드시 분리되어야 한다.

이것을 쉽게 변환해주는 라이브러리가 바로 Model Mapper다. 

Model Mapper의 사용법은 정말 간단하고 편리하다. 추가해야 할 라이브러리는 위와 같이 설정한다.

Model Mapper는 하나의 객체만 있으면 충분하므로 전체 프로젝트에서 싱글톤으로 관리되어야 한다. 스프링을 사용하지 않으면 enum을 활용해 ModelMapper 객체를 생성한 후 여러 서비스(service) 객체에서 주입받아 사용하면 된다. 

아니면 위 그림과 같이 스프링을 사용하는 경우, @Configuration, @Bean 설정으로 아주 쉽게 ModelMapper를 스프링 Bean으로 관리할 수 있을 것이다. 필요한 service 객체에 ModelMapper를 DI(의존성 주입) 받아 사용하면 끝이다.

설정 자체는 자바 코드로 수행한다. 자바 라이브러리들은 초기화 설정이 필요한 경우, Log4j2처럼 클래스패스에 파일을 두거나, 혹은 HikariCP처럼 자바 코드로 설정을 수행하는데, Model Mapper의 경우 자바 코드로 설정하면 된다.

각각의 설정의 의미는 예를 들어, Matching Strategy를 STRICT로 설정한다는 것은 데이터 타입과 이름이 일치해야만 변환을 수행한다는 것이다. Field Access Level을 PRIVATE로 설정한다는 것은 private 제어자를 포함해 모든 데이터를 대상으로 변환을 수행한다는 것이다. 그러나 여차저차 모든 설정 상세 사항을 알 필요는 없다고 생각하므로 그냥 이 부분은 필요할 때 구글링을 통해 알고 넘어가자.

 

사용하는 방식은 위와 같다. 설정한 ModelMapper 객체를 주입받고 map 메소드를 통해 변환을 수행한다. 변환 대상들은 생성자가 있어야 한다.

 


핵심은 Model Mapper를 왜 사용해야 하는지, 즉 DTO와 VO를 왜 변환해야 하는지 그 의미를 이해하는 것이다.