반응형
만들면서 배우는 클린 아키텍처의 8장을 읽고 내용을 작성했다. 아직 정리하면서 이해까지는 하지 못했는데, 나중에 프로젝트에 여러 매핑 전략을 사용하면 체득이 될 것 같다.
매핑 전략
- 서로 다른 계층(웹, 애플리케이션, 도메인, 영속성 등)간 데이터가 어떻게 변환되고 전달될지를 정의
- 각 계층이 자신의 관심사에만 집중할 수 있게 하고, 한 계층의 변경이 다른 계층에 미치는 영향을 최소화 해야한다.
- 적절한 매핑 전략을 선택함으로써 단일 책임 원칙을 지키고, 코드의 유지보수성과 확장성을 높일 수 있다.
매핑하지 않기 전략
- 웹 계층의 웹 컨트롤러가 SendMoneyUseCase 인터페이스를 호출해서 유스케이스를 실행
- 이 인터페이스는 Account 객체를 인자로 가진다
- 웹 계층, 애플리케이션 계층, 영속성 계층 모두 동일한 모델을 사용
- 결과는?
- 웹 계층과 영속성 계층은 모델에 특별한 요구사항이 있을 수 있다
- 웹 계층에서 REST로 모델을 노출시켰다면 모델을 JSON으로 직렬화하기 위한 애노테이션을 특정 필드에 추가해야 할 수도 있다
- 영속성 계층도 마찬가지: ORM 프레임워크를 사용하면 데이터베이스 매핑을 위한 애노테이션이 필요할 것이다
- 도메인과 애플리케이션 계층은 웹이나 영속성과 관련된 특수한 요구사항에 관심이 없음에도 불구하고 Account 도메인 모델 클래스는 이런 모든 요구사항을 다뤄야 한다
- Account 클래스는 웹, 애플리케이션, 영속성 계층과 관련된 이유로 인해 변경돼야 하기 때문에 단일 책임 원칙을 위배한다
- 웹 계층과 영속성 계층은 모델에 특별한 요구사항이 있을 수 있다
- 매핑하지 않는 전략을 절대 쓰면 안 되는 걸까? 그렇지는 않다
- 모든 계층이 정확히 같은 구조의, 정확히 같은 정보를 필요로 한다면 적합하다
- 하지만 애플리케이션 계층이나 도메인 계층에서 웹과 영속성 문제를 다루게 되면 곧바로 다른 전략을 취해야 한다
- 어떤 매핑 전략을 선택했더라도 나중에 언제든 바꿀 수 있다
- 저자의 경험에 따르면 많은 유스 케이스들이 간단한 CRUD 유스케이스로 시작했다가 시간이 지남에 따라 더 값비싼 매핑 전략이 필요한 경우가 많다
양방향 매핑 전략
- 각 계층이 전용 모델을 가진 매핑 전략
- 각 어댑터가 전용 모델을 가지고 있어서 해당 모델을 도메인 모델로, 도메인 모델을 해당 모델로 매핑할 책임을 가진다
- 각 계층이 전용 모델을 가지고 있는 덕분에 각 계층이 전용 모델을 변경하더라도 다른 계층에는 영향이 없다 (내용을 변경하지 않는 한)
- 웹 모델은 데이터를 최적으로 표현하는 구조를 가질 수 있고
- 도메인 모델은 유스케이스를 제일 잘 구현할 수 있는 구조를 가질 수 있다
- 영속성 모델은 데이터베이스에 객체를 저장하기 위해 ORM에서 필요로 하는 구조를 가질 수 있다
- 단일 책임 원칙을 만족
- 매핑하지 않기 전략 다음으로 간단한 전략
- 매핑 책임이 명확: 바깥 계층은 안쪽 계층의 모델로 매핑하고 다시 반대 방향으로 매핑
- 안쪽 계층은 자기의 모델만 알면 되고 매핑에 연연하지 않을 수 있다
- 하지만 단점
- 너무 많은 보일러플레이트 코드가 생긴다
- 매핑 프레임워크를 사용하더라도 두 모델 간 매핑을 구현하기 꽤 번거롭다: 제네릭 코드나 리플렉션을 사용할 경우 매핑 로직을 디버깅하기도 힘들다
- 도메인 모델이 계층 경계를 넘어서 통신하는 데 사용되고 있다
- 인커밍/아웃고잉 포트에서 도메인 객체를 입력 파라미터와 반환값으로 사용한다
- 너무 많은 보일러플레이트 코드가 생긴다
완전 매핑 전략
- 각 연산이 전용 모델을 필요로 한다
- 웹 어댑터와 애플리케이션 계층 각각이 자신의 전용 모델을 각 연산을 실행하는 데 필요한 모델로 매핑한다
- 웹 계층은 입력을 애플리케이션 계층의 커맨드 객체로 매핑할 책임을 가진다
- 각 유스케이스는 전용 필드와 유효성 검증 로직을 가진 전용 커맨드를 가진다
- 그러고 나서 애플리케이션 계층은 커맨드 객체를 유스케이스에 따라 도메인 모델을 변경하기 위해 필요한 무엇인가로 매핑할 책임을 가진다
- 한 계층을 다른 여러 개의 커맨드로 매핑하는 데는 하나의 웹 모델과 도메인 모델 간의 매핑보다 더 많은 코드가 필요하다
- 하지만 이렇게 하면 여러 유스케이스의 요구사항을 함께 다뤄야 하는 매핑에 비해 구현하고 유지보수하기 훨씬 쉽다
- 이 매핑 전략을 전역 패턴으로 추천하지는 않는다
- 이 전략은 인커밍 어댑터와 애플리케이션 계층 사이에서 상태 변경 유스케이스의 경계를 명확히 할 때 가장 빛을 발한다
- 애플리케이션 계층과 영속성 계층 사이에서는 매핑 오버헤드 때문에 사용하지 않는 것이 좋다
단방향 매핑 전략
- 동일한 상태 인터페이스를 구현하는 도메인 모델과 어댑터 모델을 이용하면 각 계층은 다른 계층으로부터 온 객체를 단방향으로 매핑하기만 하면 된다
- 즉 모든 계층의 모델들이 같은 인터페이스를 구현한다
- 이 인터페이스는 관련 있는 속성에 대한 getter 메서드를 제공해서 도메인 모델의 상태를 캡슐화
- 도메인 모델 자체는 풍부한 행동을 구현할 수 있고, 애플리케이션 계층 내의 서비스에서 이러한 행동에 접근할 수 있다
- 도메인 객체를 바깥으로 전달하고 싶으면 매핑 없이 가능
- 인커밍/아웃고잉 포트가 기대하는 대로 상태 인터페이스를 구현하고 있다
- 바깥 계층에서는 상태 인터페이스를 이용할지 전용 모델로 매핑해야 할지 결정할 수 있다
- 행동을 변경하는 것이 상태 인터페이스에 의해 노출돼 있지 않기 때문에 실수로 도메인 객체의 상태를 변경하는 일은 발생하지 않는다
- 바깥 계층에서 애플리케이션 계층으로 전달하는 객체들도 이 상태 인터페이스를 구현하고 있다
- 애플리케이션 계층에서는 이 객체를 실제 도메인 모델로 매핑해 도메인 모델의 행동에 접근 가능
- 이 매핑은 factory라는 DDD 개념과 잘 어울린다
- 팩토리는 특정한 상태로부터 도메인 객체를 재구성할 책임을 가지고 있다
- 이 전략에서 매핑의 책임은 명확
- 한 계층이 다른 계층으로부터 객체를 받으면 해당 계층에서 이용할 수 있도록 다른 무언가로 매핑하는 것
- 각 계층은 한 방향으로만 매핑한다 -> 따라서 단방향 매핑 전략
- 매핑이 계층을 넘나들며 퍼져있어서 다른 전략에 비해 개념적으로 어렵다
- 계층 간의 모델이 비슷할 때 효과적
- 읽기 전용 연산의 경우 상태 인터페이스가 필요한 모든 정보를 제공하기 때문에 웹 계층에서 전용 모델로 매핑할 필요가 전혀 없다
언제 어떤 매핑 전략을 사용할 것인가?
- 답은 그때그때 다르다
- 각 매핑 전략이 장단점을 가지고 있다
- 같은 코드에 여러 패턴을 섞으면 어수선하게 느껴지긴 하지만 그런 이유로 최선의 패턴을 선택하지 않는 건 별로다
- 언제 어떤 전략을 사용할지 결정하려면 팀 내에서 합의할 수 있는 가이드라인을 정해둬야 한다
- 이 가이드라인은 어떤 상황에서 어떤 매핑 전략을 가장 먼저 택해야 하는가에 답할 수 있어야 한다
가이드라인
- 변경 유스케이스를 작업하고 있다면:
- 웹 계층과 애플리케이션 계층 사이에서는 유스케이스 간의 결합을 제거하기 위해 완전 매핑 전략을 첫 번째 선택지로 해야 한다
- 유스케이스별 유효성 검증 규칙이 명확해진다
- 특정 유스케이스에서 필요하지 않은 필드를 다루지 않아도 된다
- 애플리케이션 계층과 영속성 계층 사이에서는 매핑 오버헤드를 줄이고 빠르게 코드를 짜기 위해서 매핑하지 않기 전략을 첫 번째 선택지로 둔다
- 애플리케이션 계층에서 영속성 문제를 다뤄야 하게 되면 양방향 매핑 전략으로 바꿔서 영속성 문제를 영속성 계층에 가둘 수 있게 한다
- 웹 계층과 애플리케이션 계층 사이에서는 유스케이스 간의 결합을 제거하기 위해 완전 매핑 전략을 첫 번째 선택지로 해야 한다
- 쿼리 작업을 한다면:
- 매핑 오버헤드를 줄이고 빠르게 코드를 짜기 위해 매핑하지 않기 전략이 웹 계층과 애플리케이션 계층 사이, 애플리케이션 계층과 영속성 계층 사이에서 첫 번째 선택지가 되어야 한다
- 애플리케이션 계층에서 영속성 문제나 웹 문제를 다뤄야 하게 되면 웹 계층과 애플리케이션 계층, 애플리케이션 계층과 영속성 계층 사이에서 각각 양방향 매핑 전략으로 바꿔야 한다
반응형