Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- Linux
- r
- Heap
- ajax
- reactive
- Elk
- 네트워크
- network
- libuv
- VCS
- Lombok
- github
- socket
- spring
- html
- AWS
- Static
- reactor
- javascript
- 데이터통신
- mybatis
- effective
- git
- HTTP
- nodejs
- cache
- NoSQL
- Java
- redis
- mongodb
Archives
- Today
- Total
빨간색코딩
DDD Quickly (도메인 주도 설계, 전술적-전략적 설계, Spring @Service의 유래, @Configurable를 통한 도메인 모델에 bean 주입) 본문
디자인패턴
DDD Quickly (도메인 주도 설계, 전술적-전략적 설계, Spring @Service의 유래, @Configurable를 통한 도메인 모델에 bean 주입)
빨간색소년 2022. 1. 17. 05:35Domain Driven Design Quickly (도메인 주도 설계란 무엇인가? 쉽고 간략하게 이해하는 DDD, 플로이드 마리네스쿠 지음, 최수경 옮김)을 읽고 이해한 내용을 정리해보았다.
Evans Eric 의 Domain-Driven Design: Tackling Complexity in the Heart of Software 는 2003년 8월 22일 초판인데, 이걸 기반으로 2011년에 쓴 책이라고 한다.
책에 있는 내용을 기반으로 썼지만, 좀 더 쉬운 이해를 위하여 예제를 덧붙이거나, 주관적 해석, 책에 없는 내용(반 버논의 주석, Spring 프레임워크에서 DDD 이야기 등)도 약간 적어보았다.
1장. 도메인 주도 설계란 무엇인가?
- 소프트웨어의 복잡성을 낮추는 설계 기법 중 하나이다.
- 소프트웨어가 복잡한 주요한 이유는 도메인 자체가 복잡하기 때문이다.
- 소프트웨어를 올바르게 표현하고 관리하는 방법을 아는게 중요한데, 이걸 위해서 기술, 엔지니어링에만 집중하는 것은 한계가 있다.
- ex) 회계 프로그램이 복잡한 것은 코드나 기술이 문제가 아니라, 회계 도메인 자체가 어렵고 난해하기 때문이다.
- 좋은 소프트웨어를 만들기 위해서는, 그 소프트웨어가 무엇에 관련된 것인지를 알아야한다.
- 소프트웨어는 도메인을 모델링해야 한다.
- 추상화 : 도메인을 표현한 모델
- 모델 : 대상 도메인에 대한 내부적 표현으로 설계와 개발 내내 사용
- 도메인 전문가, 설계자, 개발자 간 의사소통의 중심
- 소프트웨어 설계 : 청사진
- 코드 설계 : 세부 작업들
- 폭포수 접근법의 단점
- 일방적 전달로 피드백 없음
- 프로젝트 초기에 많은 요구사항들을 확정
- 분석 마비 : 너무 규모가 크다보니 설계 결정을 쉽게 못 내리고 두려워함
- 도메인 주도 설계의 원칙
- 도메인 지식 쌓기
2장. 유비쿼터스 언어
- 유비쿼터스(Qbiquitous) 뜻 : 언제 어디서나 동시에 존재하는
- 개발자는 클래스, 메소드, 알고리즘, 패턴 등으로 머리가 가득차 있고, 늘 현실 세계를 프로그램으로 매핑하려는 경향을 지닌다.
- 소통이 가장 중요하다. 도메인전문가와 소프트웨어전문가 중 한쪽이 이해를 잘못했다면 성공할 수 없다.
- 공통 언어의 필요성 = 모델 기반 언어
- 클래스, 메소드, 모듈의 이름도 늘 쓰는 단어의 의미에 서로 합의된 것을 사용하라
- 데이터중심적 사고, DB엔지니어들이 비즈니스 용어를 사용하지 않고 알아보기 힘든 약자 등을 사용하는 것을 지양
- 지금이야 업계에서 이런 네이밍이 많이 사라졌지만, DDD가 주장될 쯤은 2000년대 초였다.
- cf) https://jang8584.tistory.com/35
- ex) 사용자이름 USER_NM, 중분류코드명 J_CODE_IDX
- UML은 핵심 개념들을 클래스로 표현하고 클래스간 관계로 표현하는데 훌륭하다.
- 그러나, 규모가 커지면 사실상 읽을 수 없다.
- 또한, 행동이나 제약사항 표현이 어렵다.
- 따라서, 작은 범위로 쪼개서 작게 다이어그램을 작성하고 주석을 달아라
3장. 모델 주도 설계 (전술적 설계, Tactical Design)
- 비즈니스 도메인 전문가와 소프트웨어 분석가가 모델을 도출하더라도, 소프트웨어 개발자의 구현 레벨에서 간극이 생길 수 있다.
- 모델을 코드로 어떻게 변환할 것인가?
- 1안) 도메인 분석과 코드 구현을 별개로 분리함. 개발자는 분석모델을 참고만 할 뿐이어서 코딩이 시작되면 분석모델은 폐기됨
- 영속성, 성능 등에 있어서 분석 모델이 항상 최적최선은 아니기 때문이다.
- 분석된 모델을 개발자들에게 문서로 넘겨줄 때, 분석가들의 지식이 온전히 전달되지 않는다. 개발자들은 어림짐작을 하게 된다.
- 2안) 분석가와 개발자가 함께 참여하여 소프트웨어로 표현될 수 있는 모델을 설계해야 한다.
- 개발을 고려하지 않는 분석은 구현에서 발생할 수 있는 문제점을 놓치고 모델은 비현실적인 게 된다.
- 구현과 모델을 밀접하게 연관시키려면 객체지향 언어처럼 모델링 패러다임을 지원하는 걸 사용하는게 좋다.
- 1안) 도메인 분석과 코드 구현을 별개로 분리함. 개발자는 분석모델을 참고만 할 뿐이어서 코딩이 시작되면 분석모델은 폐기됨
- 2안을 전술적 설계라고 할 수 있다.
- 5장의 전략적 설계와 다르게, 분할된 컨텍스트(특정 모델) 내부를 모델링한다고 보면 될 듯
- 반 버논(Vaughn Vernon)은 전술적 설계 중 일부만 차용한 것을 DDD-Lite 라고 정의한다.
- 설계 블록
3-1. 계층형 아키텍처 (Layered Architecture)
- 어플리케이션은 도메인, 비즈니스 외에도 DB, 네트워크, 파일 등 인프라스트럭처 코드들이 상당하다.
- 도메인 코드가 다른 레이어와 섞여있으면, 이해하기가 어려워지고 외부변경에 사이드이펙트가 있을 수 있다.
- 아래와 같이 레이어를 분할해야한다.
- 프리젠테이션 : UI영역, view
- 어플리케이션 : 어플리케이션 활동을 조율하며, 각 영역을 호출하고 작업의 결과를 보관한다.
- 서비스로 요청하여 인프라스트럭처를 통해 도메인 모델을 찾고, 상태를 변경하고 다시 인프라스트럭처를 호출하여 영속화
- 2003년에 나온 개념이라 현재 씬에서 딱 맵핑되는 건 없는 것 같다. 이미 DDD 철학이 다 녹아들어있기때문에.. MVC로 치면 컨트롤러 느낌? 마이크로서비스에서 로케이터 느낌?
- 도메인 : 비즈니스 객체를 다룬다. 서비스와 모델로 구성되어 있는 듯
- 인프라스트럭처 : 영속성 구현, 레이어 간 통신 제공
3-2. 엔티티 (Entity)
- 엔티티는 연속성과 식별성을 갖는 객체이다. 소프트웨어가 여러 상태를 거치는 동안에도 유일하다.
- 인스턴스와 엔티티의 차이
- 객체가 메모리에 보관되고 주소를 할당받는 인스턴스는 참조될 동안만 존재하며, 같은 값을 가질 수 있기 때문에 식별성이 있진 않다.
- 엔티티를 구현한다는 것은 식별자를 만들어내는 것이라고 볼 수 있다.
- 도메인 객체 ⊃ 엔티티
3-3. 값 객체 (Value Object)
- 모든 객체를 엔티티로 만들 필요는 없다. 굳이 식별할 필요가 없는 것도 있으므로..
- 하나의 객체가 도메인의 어떤 측면을 표현하는데 사용되지만 식별자가 없다면 값 객체
- 쉽게 생성되고 쉽게 폐기되며 영속화도 신경안써도 된다. 설계를 단순하게 만들고, VO의 단순함을 유지해라.
- 불변으로 사용해야 한다. 상태를 변경해야한다면 새로운 VO를 만들어서 사용해라.
- VO 역시 도메인 로직을 갖는다. 예를들면 변환, 포매팅, 검증 등이 있겠다.
3-4. 서비스
- 도메인의 행위 가운데 어떤 행동은 어느 객체에도 속하기 어려운 것이 있을 수 있다.
- 예를들어, 송금 행위에서 송금인 객체에서 이걸 메소드화할 것인가? 수취인 객체에서 이걸 메소드화할 것인가? 둘다 아니다.
- 이 부분은 Object Oriented Design 와의 큰 차이점이라고 볼 수도 있겠다. 행동을 객체에서 의도적으로 분리시키기 때문이다.
- 이러한 행위들을 서비스 객체로 정의한다.
- 내부적인 상태를 갖지 않으면서 도메인의 기능을 제공한다.
- 서비스의 특징
- 엔티티, VO에 속할 수 없는 도메인의 개념
- 도메인의 다른 객체를 참조
- stateless
- 서비스가 어느 레이어에 속해야하는지는 상황과 개념에 따라 다르다.
- 이 부분에서 문득, 자주 다루는 Spring 프레임워크의 @Service 과는 무슨 상관이 있을까? 궁금했는데..
- DDD 에서 따온 개념이라 한다.. 소름..
3-5. 모듈
- 모델이 거대해지면서 전체를 파악하기 어려워짐
- 모듈화는 관련된 개념과 작업을 조직화하여 모델을 모듈로 쪼개는 것이다.
- 모듈을 쪼갤 때, 높은 응집도(High Cohesion)와 낮은 결합도(Loose Coupling)를 추구해야 한다는 원칙을 지키기 위해 노력해야한다.
- 통신(communicational) 응집도 : 비슷한 데이터들을 그룹핑하여 모듈화
- 기능(functional) 응집도
- 모듈은 다른 모듈에서 쉽게 접근 할 수 있는 인터페이스를 가져야한다.
- 구체적인 클래스를 참조하지 않아도 되므로 결합도 감소
- 모듈은 관점에 따라 MSA의 마이크로서비스 단위가 될 수도 있을 듯?
- cf) 반 버논은 분할된 컨텍스트 내 여러 마이크로서비스가 들어갈 수도 있다고 한다.
3-6. 집합 (Aggregate)
- 도메인 객체의 소유권과 경계를 정의하는 패턴
- 모델링에서의 과제는 어떻게 해야 단순하고 이해하기 쉬운 모델을 만들 수 있는가?
- 보통 두 객체간 참조 관계를 제거하며 단순화함
- 1대N 관계는 N이 1의 집합으로 들어감으로써 단순화
- N대M 관계는 아래와 같이 단순화하라
- 모델의 핵심이 아닌 관계는 제거
- 관계에 참여하는 객체의 수를 제약사항으로 걸러내어 감소
- 양방향인데 단방향으로 대체
- ex) 자동차에 엔진이 있고, 엔진에는 호환되는 자동차 목록이 있음. 그러나 전자만 고려하여 단방향으로 바꿔버릴 수 있음
- 복잡한 참조관계를 유지하며 데이터 무결성을 지키기 위해, DBMS의 트랜잭션을 사용한다.
- 모델이 잘못 설계되었을 수록 DB에 기대야하며, lock 등으로 인하여 높은 비용이 소모된다.
- 따라서, 모델 설계수준에서 직접 해결할 수 있다면 가장 좋다.
- 집합은 하나의 root(=엔티티)를 갖는다. root는 집합된 다른 객체들을 참조하고 있고 관계를 맺고 있다.
- 집합을 사용하면 데이터 무결성을 보장하고 불변식을 강제할 수 있다.
- 하위의 집합된 객체들은 root만이 참조하고 변경할 수 있기때문이다. 외부에서는 root만 public하므로..
- 외부에서 하위 객체를 꼭 참조해야한다면 데이터 무결성을 위하여 복사하여 VO으로 전달해야한다.
- 이미 VO가 불변이라면 굳이 복사는 안해도 될듯..
- 영속성 레벨에서는 집합된 데이터를 어떻게 저장해야 좋을까?
- NoSQL 같은 경우, 통으로 저장할 수도 있겠다.
- RDB라면 table 다 쪼개서 저장하고 join 하는 형태로?
- 하위 집합의 객체들이 DB에 별도 table 에 저장되어있다면, 참조가 필요해졌을 때, select 로 얻어오면 될 듯?
- 이런 거에서 출발해서 JPA의 LazyLoading 개념이 나온건가..?
- cf) 반 버논은 하나의 트랜잭션으로 join 하여 가져오지말고, id를 통해 필요할 때 여러번 조회하여 참조하라고 한다.
3-7. 팩토리 (Factory)
- 거대한 엔티티를 생성자를 통해 생성하기에는 너무 크고 복잡하다. 이러한 복잡한 객체 생성의 절차를 캡슐화한 것이 팩토리다.
- 실제 도메인을 예로 들어보면, 자동차는 아래 공정을 거친다.
- 프레스(new FramePress(철판, ...), new BodyPanel(...)) -> 차체(new 차량구조(각종 판넬, 용접기)) -> 도장 -> 의장(조립) -> 검사 -> 완성
- 여러 모듈들이 먼저 완성되고 이것들을 조합한 것이다. (new Car(제조공정의 결과물들))
- 이렇게 복잡한 것을 별도로 분리하여 캡슐화한다는 것이다.
- 실제 도메인을 예로 들어보면, 자동차는 아래 공정을 거친다.
- 팩토리 구현 방식
- 팩토리 메소드 패턴
- 조건에 따라 객체 생성을 팩토리 클래스에 위임
- 하나의 종류당 1개의 팩토리이며 단일메소드를 가짐
- 추상 팩토리 패턴
- 조건에 따라 객체를 생성하는 팩토리 생성을 팩토리 클래스에서 한다.
- 생성된 팩토리 객체는 연관있는 객체들을 일관되게 생성할 수 있다.
- 팩토리 메소드 패턴
- root를 생성하려면 집합된 객체들도 생성하고 관계를 잡아줘야한다. 이런 로직은 어떤 객체에도 포함하는게 어색하기 때문에 factory 객체가 맡는게 적절하다.
- 마치 컴퓨터라는 도메인 객체가 CPU, RAM, 디스크 등 여러 하위 객체들로 구성된 것처럼..
- 팩토리가 굳이 필요하지 않고, 생성자로 충분한 경우
- 생성 작업이 복잡하지 않음
- 객체 생성이 다른 객체의 생성과 연관되어 있지 않음
- 전략 패턴을 선택할 때
- cf) 전략 패턴 : 객체가 할 수 있는 행위들을 각각의 전략으로 만들고, 행위의 수정이 필요하다면 전략을 단순히 교체만하면 되도록 함
- 인자에 따라 생성되는 클래스가 달라지지 않고, 자기 자신 일때
- 팩토리는 순수한 도메인 레이어다.
3-8. 리파지토리 (Repository)
- 클라이언트가 DB에서 필요한 정보(객체)를 조회하고 바로 사용한다면, 도메인 모델 설계에 위반한다.
- sql 결과로 필요이상으로 많은 데이터가 노출된다.
- 도메인 개념보다 인프라스트럭처를 더 다루게 된다.
- 모델은 단순히 데이터운반 역할만 하게 될 것이다.
- 인프라스트럭처 변경에도 취약해진다.
- 하위 집합의 어떤 객체가 필요하더라도, 도메인 모델 설계에 맞게 root(=엔티티, 도메인객체)를 조회하고, root부터 참조하라
- 객체의 참조를 얻는 로직을 캡슐화하기 위하여 리파지토리를 사용하라
- 리파지토리로만 참조할 수 있다면, 도메인 모델은 본분에 충실할 수 있다.
- 도메인 모델이 객체의 저장이나 참조와 연관을 없애고 인프라스트럭처에 접근할 필요가 없어지는 효과
- 예를 들면, root(=엔티티)를 리파지토리가 갖고있으면서, 어떤 메소드를 호출하면 하위 집합의 객체들을 반환해주는 형태
- 리파지토리는 객체 조회 시, cache hit 하기도 하지만, cache miss 라면 영속성 스토리지에서 읽어오는 경우가 더 많다.
- 리파지토리는 팩토리와 다르게 인프라스트럭처 레이어와도 연결이 포함될 수 있다.
- 리파지토리는 CRUD를 수행할 수 있다. 여기서 Create는 아무것도 없는 상태에서 객체를 생성하는 것이 아니라, 팩토리에서 넘겨받은 객체를 추가하는 것이다.
- Spring 프레임워크의 @Repository 도 역시 DDD 에서 따온 개념이라 한다.
- cf) Spring 2.0 은 06년 10월에 출시
4장. 깊은 통찰을 향한 리팩터링
- 도메인 모델링의 리팩토링
- 초창기에 비즈니스 명세를 읽으며 명사는 클래스로 동사는 메소드로 변환하는 것부터 시작하지만 초기에는 깊이가 얕고 편협하다.
- 코드적, 패턴을 기반으로 하는 기술적인 동기를 갖는 리팩토링과는 다르다.
- 리팩토링은 어플리케이션의 기능에 변화를 주지 않고 코드를 더 좋게 만들기 위해 재설계하는 절차
- 도메인에 대한 통찰, 모델이나 코드에 드러나는 표현을 가다듬기 위한 것도 리팩토링이다.
- 정교한 모델, 좋은 모델은 도메인 전문가와 개발자들이 밀접하게 엮여서 반복적으로 리팩토링을 해야 만들어진다.
- 도메인 전문가와 이야기할 때, 모델에 표현되지 않은 암시적 개념이 설계에서 핵심적 역할을 한다면, 이것을 모델에 추가함으로써 명시적으로 만들어야한다.
- 암시적 개념, 누락된 개념을 발견하는 방법
- 언어를 주의 깊게 듣는다.
- 누락이 되었다면, 어떤 객체는 그것을 대체하기 위해 매우 비대해졌을 가능성이 높다.
- 도메인 전문가의 요구사항이 다른 요구사항과 상충되어 보이더라도, 조화시키려고 노력해야한다. 그 과정에서 중요한 개념이 도출될 수도 있다.
- 해당 도메인의 문헌을 활용하라
- 명시적인 것을 만들어낼 때, 유용한 것은 아래 3가지다.
- 제약 조건(constraint)
- 제약사항을 별도 메소드로 분리하여 명확하게 하라
- 읽기 쉬워지고, 제약 조건이 추가되어도 넣기 쉬워진다.
- 처리(process)
- 대개 절차적 코드로 표현된다. 처리를 표현하는 권장방법은 Service 를 이용하는 것이다.
- 명세(specification)
- 객체가 특정 기준(비즈니스 규칙)을 만족하는지 여부
- 개별 비즈니스 규칙들은 자체적인 객체로 분리되어 캡슐화되어야 한다. 이것들을 조합해서 하나의 규칙을 표현한다.
-
Customer customer = customerRepository.findCustomer(id); Specification customerEligibleForRefund = new Specification(new CustomerPaidHisDebtsInThePast(), new CustomerHasNoOutstandingBalances()); if (customerEligibleForRefund.isSatisfiedBy(customer)) { ... }
- 코드만 읽어도 명세파악이 가능해야 한다.
- 제약 조건(constraint)
5장. 모델 무결성 보존 (전략적 설계, Strategic Design)
- 기업 규모의 대형 프로젝트에서 통일된 하나의 큰 모델을 유지하는 것은 쉽지않다.
- 큰 모델을 각 팀에서 특정 부분(모듈 단위) 할당받아 작업하는 형태로 병렬 개발하는데,
- 각 팀은 필요한 만큼만 모델에 대해 이해를 하고 있기 때문에, 진행이 될수록 통합되지 않고, 불일치가 많은 쪽으로 변질되기 쉽다.
- 차라리 의도적으로 처음부터 여러 개의 모델로 분할하라
- 분할된 모델의 순수성, 일관성, 통일성을 지키기 위해서 노력해야한다.
- 사이드이펙트가 사라진다.
- 이것을 전략적 설계라고 부를 수 있다.
5-1. 분할된 컨텍스트 (Bounded Context)
- 모델에 적용할 수 있는 컨텍스트를 정의하라
- 모델의 컨텍스트란 모델 안에서 사용된 용어들이 특정한 의미를 갖고 사용되는 집합이다.
- 대규모 모델을 하나의 작은 것으로 분할하는 명확한 기준은 없다.
- 하나의 팀에 할당하기 적합해야 한다. (여러 팀이 하나의 분할된 컨텍스트를 맡는건 안된다)
- 팀, 조직 관점의 용어로 경계를 명시적으로 설정하라
- 분할된 컨텍스트 = 문제 영역
- 분할된 컨텍스트는 모듈이 아니다. 모듈을 포함하는 개념이다. 분할된 컨텍스트는 논리적으로 1개의 모델을 갖는다.
- 예를들어, 전자상거래 서비스가 있다면 domain은 전자상거래이다.
- 여기서 회원, 결제, 주문, 리포팅은 subdomain 이다.
- 결제라는 Bounded Context에서는 회원의 신용카드정보 등이 보일 것이다.
- 주문이라는 Bounded Context에서는 회원의 주소 등이 보일 것이다.
- 즉, 모델이 Bounded Context에 따라서 취급되거나 중요해지는 요소들이 달라진다.
- subdomain 과 Bounded Context 가 꼭 1대1은 아닐 수 있다.
- 2개의 subdomain 이 1개의 Bounded Context 를 가질 수도 있다.
- 1개의 subdomain 이 2개의 Bounded COntext 를 가질 수도 있다.
- 분할된 컨텍스트는 유비쿼터스 언어의 일부인 고유한 이름이 있어야한다. 팀 간 의사소통이 원활해진다.
5-2. 지속적인 통합
- 모델의 각 구성 요소들이 어떤 역할을 수행하는지 확실히 이해하기 위해 팀 내 의사소통을 해야한다.
- 그렇지 않으면, 순수성을 해친 코드, 단일 모델로 통합되지 않는 코드를 작성할 수 있다.
- 새로운 구성 요소가 기존 모델에 조화롭게 추가되고, 올바르게 구현되도록 보장할 수 있는 프로세스가 필요하다.
- 코드는 일찍 통합될수록 좋다.
- 소규모 팀에서는 일일 단위로 통합하라
- 통합된 코드는 테스트할 수 있도록 자동으로 빌드되어야 한다.
5-3. 컨텍스트 맵 (Context Map)
- 컨텍스트 맵은 서로 다른 분할된 컨텍스트들과 그들의 관계에 대해 개요를 표현한 문서
- 모든 사람은 각 컨텍스트의 범위와 코드의 맵핑 상태를 알고 있어야 한다.
- 컨텍스트 맵핑 기술/패턴을 통해 분할된 컨텍스트간 역할, 관계를 설명할 수 있다.
- 컨텍스트 간 상호작용의 수준이 높음 : 공유커널 패턴, 고객-공급자 패턴
- 컨텍스트 간 독립성이 높음 : 분할방식 패턴
- 내외부 상호작용을 다룬 패턴 : 오픈호스트서비스 패턴, 변질방지 패턴
5-3-1. 공유-커널 (Shared Kernel)
- 각 분할된 컨텍스트가 하나의 모델을 공유해야한다면, 명시적으로 공유된 부분으로 지정할 수 있다.
- 단순 모델 공유가 아니라, 코드 및 DB도 포함될 수 있다.
- 중복 개발을 줄여준다.
- 공유된 부분은 상대 팀과 의사소통을 하면서 변경해야한다.
5-3-2. 고객-공급자 (Customer-Supplier)
- A와 B가 있을 때, B가 A에게 전적으로 의존을 한다면, A는 공급자이고, B는 고객이다.
- 고객-공급자 관계는 공유커널을 사용할 수 없을 때이다.
- 공유된 하나를 갖는게 맞지 않는다.
- 공통 코드를 기술적으로 가질 수 없는 구조다.
- 고객-공급자 관계는 양쪽 팀이 관계 유지에 관심이 있을 때, 성공할 수 있다.
- 고객은 공급자에게 요구사항을 표현하고, 공급자는 계획을 제공하며 의사소통하라.
- 예를들어, 전자상거래 서비스는 리포팅 서비스가 하나도 안중요하지만, 리포팅 서비스는 전자상거래의 모든 데이터를 사용한다.
- 컨텍스트 상호 간 인터페이스가 정교하게 정의되어야하고, 테스트케이스로 안전하게 검증되어야 한다.
- 이 경우가 가장 많다.
5-3-3. 순응 (Conformity)
- 고객-공급자 관계에서 두 팀이 관리체계(서로 다른회사거나)가 없거나, 공급자가 매우 바쁘다면, 관계를 유지할 수 없게 된다.
- 고객이 공급자의 모델을 사용해야 한다면 순응해야한다.
- 순응하지 않아도 된다면, 고객 팀은 아래의 여지가 있다.
- 공급자 모델을 아예 고려하지않고 분할방식 패턴으로 별도의 모델을 만들어라
- 공급자 모델이 사용하기 불편하다면, 변질방지레이어를 사용해라
- 공유 커널과 차이점은 고객이라서 공유된 부분에 변경을 가할 수 없다는 것이다.
- cf) 주로 API를 일방적으로 사용하는 경우
5-3-4. 변질 방지 레이어 (Anticorruption)
- DDD로 만들어진 어플리케이션은 레거시 어플리케이션이나 분리된 어플리케이션과 상호작용을 할 일이 있을 수 있다.
- 도메인 모델과 레거시 모델 간 일정 수준으로 통합이 필요하다
- 네트워크나 DB를 통해 외부 데이터를 가져올 수 있으나, 원시 데이터이다. 여기에는 모델에 대한 정보가 없고 시맨틱이 숨어있다.
- 변질 방지 레이어는 외부 시스템을 추상화하고 외부에는 외부언어로, 내부에는 클라이언트언어로, 양방향 번역자의 역할을 한다.
- 변질 방지 레이어의 변환을 통해 컨텍스트 내 순수함을 지킬 수 있다.
- 외부 인터페이스 변경에도 기존 도메인 모델의 영향도를 낮출 수 있다.
- 실제 구현은 Facade 패턴을 활용한다.
- Facade : 서브시스템에 있는 인터페이스들을 통합하여 사용하기 쉽고 고수준의 인터페이스를 제공
- Adapter : 클라이언트가 이해할 수 있는 것으로 변환. 두 시스템간 번역
- Translator : 객체와 데이터를 번역
5-3-5. 분할 방식 (Separate Way)
- 한 묶음의 요구사항에서 모델링과 설계의 관점에서 공통(=의존된)된 부분이 없다면 어플리케이션을 쪼개서 개발할 수 있다.
- 독립적으로 개발된 모델은 다중에 다시 합치기가 매우 어려우므로, 통합이 나중에라도 필요하지 않을지 면밀히 검토해야한다.
5-3-6. 오픈 호스트 서비스 (Open Host Service)
- 어떤 어플리케이션에서 여러 서브시스템을 통합하려 할 때, 번역(내부와 외부의 완충지대)을 위한 레이어를 만든다.
- 만약, N개의 서브시스템을 통해 데이터를 얻어온다면, N개마다 번역레이어가 필요할 것이고 유사한 코드가 반복될 것이다.
- 오픈 호스트 서비스는 외부 서브시스템을 서비스의 제공자로 바라보는 관점으로, 외부 서브시스템을 서비스로 감싼다.
- 자신의 시스템에 접근할 수 있는 프로토콜(ex. http, rpc, socket)을 서비스의 집합으로 정의하고 호환프로토콜을 공개하라.
- 고객-공급자 패턴에서는 고객이 요청을 해야하지만, 오픈호스트서비스 패턴은 요청이 예상될만한 것들을 미리 만들어 제공하는 느낌
- 어떤 예외적인 서브시스템에는 일회용 번역기를 사용하라
5-4. 증류 (Distillation)
- 증류 뜻 : 혼합체를 구성하고 있는 물질을 분리해 내는 절차
- 대규모 도메인 모델은 리팩터링을 많이 거쳐도 여전히 거대하다. 따라서, 이것은 증류가 필요하다.
- 핵심 도메인을 정의해야 한다. 일반적인 것으로부터 핵심 개념을 추출해야 한다.
- 증류로 생기는 부산물은 서브도메인이다.
- 핵심(core) 도메인과 서브(generic) 도메인을 분리해내는데 많은 노력을 쏟아라.
- 핵심 도메인을 기준으로 인력을 채용하고 최고의 개발자들을 핵심 도메인 구현에 할당하라.
- 개발자들은 인프라스트럭처, 최신기술에 매력을 느끼지만, 이것보다 도메인이 더욱 중요하다.
- 개발자 입장 ex) 비행도메인을 잘 아는게 개발자에게 뭐가 중요하겠는가? 프로젝트 끝나면 아무 의미가 없다.
- 도메인 비즈니스는 도메인의 심장이고 핵심 설계나 구현의 실수는 프로젝트의 실패로 귀결된다.
- 아무리 멋진 기술적 부가기능(인프라스트럭처, 기술스택 등)은 핵심 비즈니스가 제 역할을 하지못한다면 모두 무용지물이다.
- 서브도메인, 일반 모델이 분리되었다면 핵심 도메인보다 우선순위를 낮게 개발하라.
- 선택과 집중
- 서브도메인 작업에 핵심 개발자를 투입X. 도메인 관련 지식을 얻을 수 없기 때문이다.
- 서브도메인(ex. 검색)을 구현하는데 아래 방법들을 검토해봐라
- 상용 솔루션(오픈소스) : 이미 완성된 것을 사용해라. 기능부족이나 버그가 있다면 기다려야할 수도 있다.
- 외주 : 설계와 구현을 다른 회사에 맡기고 핵심도메인에 집중하라. 통합이 불편하고 서브도메인과 소통하기 위해 인터페이스를 정의해야한다.
- 기존 모델
- 사내(In-House) 개발 : 높은 수준의 통합을 이룰 수 있으나, 비용이 많이 든다.
6장. 에릭 에반스 인터뷰) 오늘날 DDD는 중요하다.
- cf) InfoQ에서 이 책을 낸 것은 2011년이다. 아래는 에릭 에반스가 답하는 내용의 요약이다.
- 도메인에 집중해야하는 것은 시대와 상관없이 가장 중요하다.
- DDD를 적용하기 쉬운 개발플랫폼, 애자일 프로세스 환경으로 성숙되가고 있다.
- 새로운 기술이나 프로세스는 도메인에 좀 더 집중하기 좋아졌느냐로 평가해야한다.
- 90년대말~2000년초 J2EE, EJB 시절 도메인 모델링이 거의 불가능했던 암흑기였으나,
- 2000년대 후반부터 등장하며 새로운 트렌드를 가져온 Spring 프레임워크, Hibernate, Ajax 를 극찬..
- 도메인 모델링의 조언
- 팀 전체가 함께하는 거대한 작업이다.
- 단순한 상태를 유지하고 모델러도 코드를 작성해야한다.
- 구체적인 시나리오에 초점을 맞춰라. 추상적 생각은 실제 사례와 연결되어야만 한다.
- 컨텍스트 맵을 그리고 어느 부분엔 DDD를 적용하고, 경계 바깥에 있는 것들은 신경쓰지말아라
Spring 프레임워크에서는 얼마나 도메인 중심적일까?
- Spring 에서 어떤 상황에서는 도메인 모델에 넣고 싶어도, bean 이 아니기 때문에 넣을 수 없는 문제가 있다.
- 예를들어, phase 별로 imageDomain 이 다르다고 해보자.
- User 도메인모델에서 프로필이미지 url 을 얻으려면 아래와 같은 식일 것이다.
-
@Component class ApplicationEnvironment { @Value("${imageDomain}") private String imageDomain; } class User { public getProfileImageUrl() { // imageDomain 을 생성자나 setter 로 외부에서 주입받음. 아니면 해당 메소드의 인자로 받거나 return imageDomain + this.profileImagePath; } }
- 도메인 모델에서 imageDomain 을 외부에서 주입받을 수 있으나, 불편하기 때문에 구조적으로 도메인 중심적 사고를 못하게 방해한다.
- static + lazy holder 등으로 우아하진 않으나, 우회하는 방안이 있긴하다.
- Spring 2.0 부터는 이것을 AspectJ 의 weaving 을 이용하여 주입해준다..
- 사실 Spring 에서 이게 안되는줄알고, 한계에 대한 이야기로 마무리하려 했는데 release note 에 있길래 놀랐다..
- https://docs.spring.io/spring-framework/docs/2.0.x/reference/new-in-2.html
- Section 6.8.1, "Using AspectJ to dependency inject domain objects with Spring"
- 도메인 모델에 bean 을 주입하려면 아래와 같이 해야한다.
- https://docs.spring.io/spring-framework/docs/5.3.15/reference/html/core.html#aop-atconfigurable
-
@Configurable class User { @Autowired private ApplicationEnvironment environment; public getProfileImageUrl() { return environment.getImageDomain() + this.profileImagePath; } } // 아래와 같은 설정이 필요하다 @EnableAspectJAutoProxy // = <aop:aspectj-autoproxy /> @EnableSpringConfigured // = <context:spring-configured /> @EnableLoadTimeWeaving // <context:load-time-weaver />
- debug 해보면, 잘 주입된 것을 확인할 수 있다.
- 대부분의 한글문서에서는 위의 weaving을 하려면 -javaagent 를 jvm 실행인자로 꼭 넘겨야한다고 이야기하는데, spring-aspects.jar 가 있다면 ClassLoader 가 알아서 해주는 듯하다. (위 테스트에서도 설정하지 않음)
- aop.xml 도 언급이 많은데, spring-aspects.jar 에 기본으로 포함되어있다. 별도 설정이 필요할때만 검토해보면 될 것 같다.
- 다만, 위와 같은 경우를 빼고 CUD같은 것도 도메인 모델에서 다뤄야할까? 에 대해선 회의적이다.
- User 도메인 객체가 스스로 인프라스트럭처 레이어를 통해 영속화시킨다. 는 말은 되지만 객체간 종속성이나 트랜잭션 처리 등이 복잡해질 것 같다. 또한, Update를 해야하므로 불변으로 쓸 수도 없어진다.
- 위와 관련된 질문도 참고해볼만 하다. https://stackoverflow.com/questions/20185808/java-where-should-i-put-my-domain-object-logic/20189373
- 이외에도, 이 책에서 다루진 않지만 도메인 이벤트에 대해서도 스프링은 지원한다. 전반적으로, Spring 진영이 DDD에게 많은 영향을 받았고, feature들을 지원하기 위해 노력해온 듯하다.
'디자인패턴' 카테고리의 다른 글
Reactor Pattern 과 I/O Multiplexing (반응자 패턴, 입출력 다중화, select, epoll, 혼동 포인트, ProjectReactor) (0) | 2021.01.17 |
---|---|
Observer Pattern (관찰자, 옵저버, JDK의 Observable, 발행구독과 차이점) (0) | 2020.12.30 |
Template Method Pattern (템플릿 메소드 패턴, hook 메소드, 예제) (0) | 2018.02.26 |
Builder Pattern (빌더 패턴) (0) | 2018.02.25 |
Comments