8. 경계 간 매핑하기
앞서 웹, 애플리케이션, 도메인, 영속성 계층의 역할에 대해선 이야기 했으니 각 계층의 모델을 매핑하는 것에 대해서 이야기해보자.
매핑을 하지 않으면 양 계층에서 같은 모델을 사용하게 되면서 두 계층이 강하게 결합된다.
하지만 보일러플레이트 코드를 너무 많이 만들게 되는 단점이 발생하기도 한다.
매핑하기는 크게 4가지 전략이 있다.
- 매핑하지 않기 전략
계층들을 연결하는 포트 인터페이스가 도메인 모델을 입출력 모델로 사용하여 모든 계층이 같은 모델을 사용한다면 계층간 매핑이 전혀 필요없다.
하지만 이러한 전략은 특정 계층에 특별한 요구사항이 발생할 경우 그와 상관없는 모든 계층의 모델에 새로운 필드나 어노테이션이 추가될 수 있다.
도메인 모델이 다른 계층들과 관련된 이유로 변경되야 하므로 단일 책임 원칙을 위반한다.
하지만 간단한 CRUD 유스케이스같은 경우는 같은 필드를 가진 웹 모델을 도메인 모델로, 혹은 도메인 모델을 영속성 모델로 매핑할 필요가 없을 것이다.
모든 계층이 정확히 같은 구조의, 정확히 같은 정보를 필요로 한다면 매핑하지 않기 전략은 좋은 선택지가 될 수 있다.
- 양방향 매핑 전략
각 계층이 전용 모델을 가진 매핑 전략을 말한다.
각 어댑터가 전용 모델을 가지고 있어서 해당 모델을 도메인 모델로, 도메인 모델을 해당 모델로 매핑할 책임을 가지고 있다.
웹 계층의 컨트롤러는 웹 모델을 서비스와 연결되는 포트 인터페이스에서 도메인 모델로 매핑하고 반환된 도메인 객체를 다시 웹 모델로 매핑한다. 영속성 계층도 이와 유사하게 구현한다.
각 계층은 전용 모델을 가지고 있으므로 각 계층이 전용 모델을 변경하더라도 다른 계층에는 영향이 없다.
각 모델은 계층에 필요한 데이터, 유스케이스를 제일 잘 구현할 수 있고 도메인 모델은 웹이나 영속성의 관심사로 오염되지 않는 깨끗한 도메인 모델로 이어져서 단일 책임 원칙을 만족한다.
개념적으로 매핑하지 않기 다음으로 간단하여 매핑 책임이 명확한데 매핑보다 도메인 로직에 집중할 수 있다.
다만, 너무 많은 보일러플레이트 코드가 생기는데 두 모델 간 매핑을 구현하는 데 꽤 시간이 들고 매핑 프레임워크를 사용하여 내부 동작을 제네릭과 리플렉션 뒤로 숨길 경우 디버깅하기도 쉽지 않다.
또다른 단점으로 도메인 모델이 계층 경계를 넘어서 통신하는데 사용된다는 것이다.
인커밍, 아웃고잉 포트는 도메인 객체를 입력 파라미터와 반환값으로 사용하면서 바깥쪽 계층의 요구에 따라 변경에 취약해질 수 있다.
- 완전 매핑 전략
각 연산마다 모두 별도의 입출력 모델을 사용할 수 있다. 계층 경계를 넘어 통신할 때 도메인 모델을 사용하는게 아니라 각 작업에 특화된 전용 모델을 만드는 것이다.
당연히 한 계층을 다른 여러 개의 커맨드로 연결하게 되면 하나의 웹 모델과 도메인 모델 간의 매핑보다 더 많은 코드가 필요하지만 여러 유스케이스의 요구사항을 함께 다뤄야 하는 매핑에 비해 구현하고 유지보수 하기가 훨씬 쉽다.
완전 매핑 전략은 전역적으로 적용하기 보다는 웹 계층과 애플리케이션 계층 사이에 상태 변경 유스케이스의 경계를 명확하게 할 때 가장 좋다.
애플리케이션 계층과 영속성 계층 사이에는 매핑 오버헤드 때문에 추천하지 않는다.
- 단방향 매핑 전략
모든 계층의 모델들이 같은 인터페이스를 구현한다. 이 인터페이스는 관련있는 모든 필드에 대한 getter 메서드를 제공하여 도메인 모델의 상태를 캡슐화 한다.
웹, 영속성 계층으로 도메인 객체를 전달하고 싶으면 별도의 매핑 없이 할수 있다. 모든 계층이 바라보는 상태 인터페이스를 사용할지, 전용 모델로 매핑할지는 바깥 계층에서 정할 수 있다.
행동을 변경하는 것이 상태 인터페이스에 의해 노출돼 있지 않기 때문에 실수로 도메인 객체의 상태를 변경하는 일은 발생하지 않는다.
각 매핑 전략은 저마다의 장단점이 있때문에 한 전략을 전역적으로 사용하기 보다는 그때그때 상황에 맞는 전략을 구사해야 한다.