각 계층의 모델을 매핑하는 방법
논쟁
- 매핑에 찬성
- 양 계층에서 같은 모델을 사용하게 되면 두 계층이 강하게 결합된다.
- 매핑에 반대
- 보일러플레이트 코드: 상용구 코드 또는 단순히 상용구는 거의 또는 전혀 변형 없이 여러 위치에서 반복되는 코드 섹션
- 많은 유스케이스가 CRUD만 수행하고, 같은 모델을 계층에 걸쳐서 사용한다.
결론
- 각 유스케이스마다 적절한 전략을 택할 수 있어야 한다.
- 어떤 매핑 전략을 선택했더라도 나중에 언제든 바꿀 수 있다.
- 여러 가지 매핑 전략을 섞어쓸 수 있고, 섞어 써야만 한다.
8-1. ‘매핑하지 않기(No Mapping)’ 전략
웹, 애플리케이션, 도메인, 영속성 계층이 모두 같은 모델 사용
Account
도메인 모델
- 웹 계층: 필드에 모델 ↔ JSON 직렬화, 역직렬화하기 위한 애너테이션 필요 ex>
@NotNull
, ..
- 영속성 계층: 데이터베이스 매핑을 위한 애너테이션 ex>
@Entity
, @Id
, @Column
, …
- 애플리케이션, 도메인 계층: 위 요구사항들이 필요없음에도 가지고 있어야 한다.
단점
- SRP 위반: 여러 계층에 의해 변경될 가능성 있음
- 특정 계층에서만 필요한 필드 추가할 경우 파편화된 모델이 됨
결론
모든 계층이 정확히 같은 구조, 같은 정보를 필요로 한다면 완벽한 선택지이다.
하지만 웹, 영속성 문제를 조금이라도 다루게 된다면 다른 전략을 취해야 한다.
8-2. ‘양방향(Two-Way)’ 매핑 전략
각 계층이 전용 모델을 가진 매핑 전략
계층별 책임
- 웹 계층: 웹 모델 ↔ 도메인 모델 매핑
- 영속성 계층: 영속성 모델 ↔ 도메인 모델 매핑
계층별 모델
- 웹 모델: 데이터를 최적으로 표현할 수 있는 구조 + JSON annotation
- 도메인 모델: 유스케이스를 제일 잘 구현할 수 있는 구조
- 웹, 영속성 관심사로 오염되지않은 깨끗한 도메인 모델. 도메인 로직에 집중할 수 있다.
- 영속성 모델: 데이터베이스에 객체를 저장하기 위해 ORM에서 필요로 하는 구조 + ORM annotation
장점
- 각 계층 전용 모델이 변경되더라도 다른 계층에 영향을 주지 않는다. ← SRP
- 간단한 전략. 매핑 책임이 명확하다.
- 바깥쪽 계층의 어댑터가 안쪽 계층의 모델로 매핑, 역방향 매핑
단점
- 너무 많은 보일러플레이트 코드
- 매핑 프레임워크가 제네릭 코드와 리플렉션 뒤로 내부 동작 방식을 숨길 경우 디버깅이 매우 어렵다.
- 도메인 모델이 계층 경계를 넘어 통신하는데 사용된다.
- = 인커밍, 아웃고잉 포트의 파라미터와 리턴값으로 사용된다.
- 도메인 모델의 필요가 아닌 바깥쪽 계층의 요구에 의해서 변경될 수도 있다.
결론
해당 전략도 어떤 엔지니어링 상황에서도 완벽하게 잘 들어맞는 해결책은 아니다.
8-3. ‘완전(Full)’ 매핑 전략
각 연산마다 별도의 입출력 모델
- 계층 경계를 넘어 통신할 때 도메인 모델 대신
SendMoneyCommand
(인커밍 포트의 파라미터) 같은 각 작업에 특화된 모델을 사용한다.
계층별 책임
- 웹 계층: 입력 → 애플리케이션 계층의 커맨드(요청) 객체로 매핑
- 각 유스케이스는 전용 필드와 유효성 검증 로직을 가진 전용 커맨드를 가진다.
- 애플리케이션 계층: 커맨드 객체 → 도메인 모델
장단점
- 웹 모델 : 도메인 모델 = 1 : 1 일 경우 보다 여러 개의 커맨드로 매핑할 때 더 많은 코드가 필요
- 여러 유스케이스의 요구사항을 함께 다룰 때 구현, 유지보수가 쉽다.
결론
전역 패턴으로 추천하지는 않는다.
인커밍 어댑터와 애플리케이션 계층 사이에서 상태 변경 유스케이스의 경계를 명확하게 할 때 좋다.
애플리케이션 계층과 영속성 계층 사이에서는 매핑 오버헤드 때문에 사용하지 않는 게 좋다.
8-4. ‘단방향 매핑(One-Way)’ 전략
모든 계층들이 동일한 ‘상태’ 인터페이스를 구현
이때 다른 계층에서 온 객체를 단방향으로 매핑하기만 하면 된다.
상태 인터페이스
도메인 모델의 상태를 캡슐화
관련 있는 특성(attribute)에 대한 getter 제공
장점
- 도메인 모델 자체는 풍부한 행동을 구현할 수 있고, 서비스에서 여기에 접근할 수 있다.
- 인커밍/아웃고잉 포트는 상태 인터페이스를 가지고 있기 때문에 계층의 전용 모델(구현체)을 매핑 없이 다른 계층으로 전달할 수 있다.
- 받은 계층에서 상태 인터페이스를 이용할지, 전용 모델로 매핑할지 결정
- DDD의 factory: 어떤 특정한 상태로부터 도메인 객체를 재구성할 책임
- 매핑 책임이 명확하다.
- 한 계층이 다른 계층으로부터 객체를 받으면 해당 계층에서 이용할 수 있도록 전용 모델로 매핑 = 한 방향으로 매핑
단점
- 매핑이 여러 계층에 퍼져 있기 때문에 다른 전략에 비해 어렵다.
결론
계층 간의 모델이 비슷할 때 가장 효과적이다.
8-5. 언제 어떤 매핑 전략을 사용할 것인가?
그때그때 다르다
- 한 전략을 전체 코드에 적용하려고해선 안 된다.
- 빠르게 코드를 짤 수 있는 간단한 전략으로 시작해서 계층 간 결합을 떼어내는 데 도움이 되는 복잡한 전략으로 갈아타는 방법
- 팀 내 가이드라인 정하기: 어떤 상황에서 어떤 매핑 전략을 왜 가장 먼저 택해야 하는가?
전략 선택 예시
- 변경 유스케이스
- 웹~애플리케이션 계층
- 유스케이스간 결합 제거하기 위해 ‘완전 매핑’ 전략 선택
- 유스케이스별 유효성 검증규칙이 명확해진다.
- 특정 유스케이스에 필요하지 않은 필드를 다루지 않아도 된다.
- 애플리케이션~영속성 계층
- 매핑 오버헤드를 줄이고 빠른 코딩을 위해 ‘매핑하지 않기’ 전략 선택
- 애플리케이션 계층에서 영속성 문제를 다루게 된다면 ‘양방향’ 매핑’ 전략으로 영속성 문제를 영속성 계층에 가둔다.
- 쿼리 유스케이스
- 모든 계층간 매핑에서 ‘매핑하지 않기’ 전략을 첫번째로 선택
- 애플리케이션 계층에서 웹/영속성 문제를 다루어야 한다면 각 계층 사이에서 ‘양방향’ 매핑 전략으로 변경
8-6. 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까?
인커밍/아웃고잉 포트
- 서로 다른 계층이 어떻게 통신해야 하는지 정의
- 계층 사이에 매핑을 수행할지 여부, 어떤 매핑 전략을 선택할지
- 전용 모델을 사용하느냐, 통일된 모델을 넘겨주느냐
유스케이스별 특화된 좁은 포트를 사용하면 서로 영향을 끼치지 않으면서 유스케이스마다 다른 최선의 매핑 전략을 사용할 수 있다.
더 어렵고 많은 커뮤니케이션을 필요로 하지만 가이드라인이 있으면 더 유지보수하기 쉬운 코드가 될 것이다.
질문 & 논의할 점
-
7-3 완전 매핑 전략 p103 / 웹 계층과 애플리케이션 계층 사이 상태 변경 유스케이스의 경계를 명확하게 할 때 가장 빛을 발한다. 애플리케이션 계층과 영속성 계층 사이에서는 매핑 오버헤드 때문에 사용하지 않는 것이 좋다.
- 왜 앱-영속성 계층 사이만 매핑 오버헤드이지?
- 웹 계층이 애플리케이션 계층 인커밍 포트의 메서드를 호출하는 입장이다. 따라서 이 메서드를 호출하면서 파라미터로 넘겨줄 커맨드 객체를 웹 계층에서 매핑할 책임이 있다. 반면 아웃고잉 포트에서 별도의 전용 객체를 사용한다면, 이 아웃고잉 포트를 호출하는 서비스에서 도메인 모델을 이 전용 객체로 매핑해야할 책임이 있다. 따라서 애플리케이션 코어에서 매핑해야하는 이 상황을 매핑 오버헤드라고 해석했다.
- 각 유스케이스는 필요한 필드들과 검증이 다르기 때문에 연산별 전용 모델이 따로 필요하다.(웹 ↔ 애플리케이션) 하지만 한 도메인 내에서 연산들은 같은 영속성 모델 엔티티를 사용한다.(애플리케이션 ↔ 영속성) 이때 아웃고잉 포트에서 각자 다른 전용 모델을 사용한다면 한 엔티티로 어차피 변환해야하기 때문에 매핑 오버헤드이다.
-
7-4 단방향 매핑 전략 p104 마지막 문단 / 읽기 전용 연산은 인터페이스가 필요한 모든 정보를 제공하기 때문에 웹 계층에서 전용 모델로 매핑할 필요가 전혀 없다.
→ 이럴 때에는 매핑하지 않기 전략을 사용하라는 뜻?
-
7-5 매핑 전략 선택 p106 첫 문단 / 변경 유스케이스, 애플리케이션과 영속성 계층 사이에서 매핑하지 않기 전략을 첫 번째 선택지로 둔다. 하지만 애플리케이션 계층에서 영속성 문제를 다뤄야 되면 ‘양방향’ 매핑 전략으로 바꾼다.
- 그러면
@Entity
, @Id
등 애노테이션을 가진 하나의 모델을 애플리케이션 계층, 아웃고잉 포트, 아웃고잉 어댑터에서 사용하라는 건데 이 자체가 애플리케이션 계층에서 영속성 문제를 가지게 되는것 아닌가