[책] DDD start!
입사 초기 회사에서 사용 중인 DDD 에 대한 기초 필독 도서가 있어서 읽으면서 내용을 정리했었다.
아직도 완벽하게 이해하진 못했지만 다른 도서들을 더 읽고 추후 정리해볼 계획이다.
- DDD에 대한 지식이 0인 상태에서 읽었던 책으로 아래는 각 챕터를 읽으며 정리해놓은 내용이다.
- 무지성 상태에서 쓴 내용이라 이해보단 완독에 초점을 두어 나중에 다시 읽어봐야 한다..
Chapter1. 도메인 모델 시작
• 도메인
도메인은 여러 하위 도메인으로 구성된다.
하위 도메인끼리 엮여서 완전한 기능을 제공한다.
만들고자 하는 소프트웨어에 따라 하위 도메인 구성이 달라진다.
도메인에 따라 용어의 의미가 달라지므로, 각 하위 도메인마다 별도로 모델을 만들어야 한다.
• 도메인 모델
특정 도메인을 개념적으로 표현한 것, 도메인 자체를 이해하기 위한 개념적인 모델
• 도메인 모델 패턴
도메인 계층은 도메인의 핵심 규칙을 구현한다.
도메인 규칙을 객체 지향 기법으로 구현 -> 도메인 모델 패턴
- 개념 모델 : 순수하게 문제를 분석한 결과물(실제 코드를 작성할 때 개념 모델을 있는 그대로 사용하기 어려움) -> 구현 가능한 형태의 모델로 전환하는 과정이 필요
초기에 완벽한 모델을 만드는 것은 불가능하므로 전반적인 개요를 알 수 있는 수준으로 개념 모델을 작성하는 것이 효율적이다.
•도메인 모델 도출
구현을 시작하려면 도메인에 대한 초기 모델이 필요하다.
도메인 모델링의 기본 작업 : 모델을 구성하는 핵심 구성요소, 규칙, 기능 파악
•엔티티와 밸류
-엔티티
엔티티마다 고유한 식별자(불변)를 갖는 것이 가장 큰 특징이다.
ex) 주문도메인 - 주문번호 (서로 다름) / 엔티티 - Ordrer, 식별자 - orderNum
두 엔티티 객체의 식별자가 같으면 두 엔티티는 같다고 판단할 수 있다.
-엔티티의 식별자 생성
DB의 자동 증가 칼럼은 DB 테이블에 데이터를 삽입해야 비로소 값을 알 수 있기 때문에 테이블에 데이터를 추가하기 전에는 식별자를 알 수 없다. ( = 엔티티 객체를 생성할 때 매개변수로 식별자를 전달할 수 없다.)
- Repository : 도메인 객체를 데이터베이스에 저장할 때 사용하는 구성요소
-밸류 타입
개념적으로 완전한 하나를 표현할 때 사용한다.
의미를 명확하게 표현하기 위해 사용하는 경우도 있다. (코드 가독성 향상)
밸류 타입을 위한 기능을 추가할 수 있다.
밸류 타입을 불변으로 구현하면 보다 안전한 코드를 작성할 수 있다. (set 메서드가 없어서 데이터 변경 불가능)
두 밸류 객체가 같은지 비교할 때는 모든 속성이 같은지 비교해야 한다.
-엔티티 식별자와 밸류 타입
엔티티의 식별자는 도메인에서 특별한 의미를 지니는 경우가 많기 때문에 식별자를 위한 밸류 타입을 사용해서 의미가 잘 드러나도록 할 수 있다.
•도메인 모델에 set 메서드 넣지 않기
도메인 모델에 get/set 메서드를 무조건 추가하는 것은 좋지 않은 버릇이다.
set 메서드는 도메인의 핵심 개념이나 의도를 코드에서 사라지게 한다.
set 메서드는 도메인 객체를 생성할 때 완전한 상태가 아닐 수도 있다.
(도메인 객체가 불완전한 상태로 사용되는 것을 막으려면 생성자를 통해 필요한 데이터를 모두 받아야 한다.)
불변 밸류 타입을 사용하면 밸류 타입에는 set 메서드를 구현하지 않는다.
•도메인 용어
도메인에서 사용하는 용어를 코드에 반영해서 코드의 가독성과 이해도를 높여야 한다.
Chapter2. 아키텍처 개요
•네 개의 영역
표현, 응용, 도메인, 인프라스트럭처
-표현(UI) 영역 : 사용자의 요청을 받아 응용 영역에 전달하고 응용 영역의 처리 결과를 다시 사용자에게 보여주는 역할
ex) 스프링 MVC의 controller 역할
-응용 영역 : 시스템이 사용자에게 제공해야 할 기능을 구현
기능을 구현하기 위해 도메인 영역의 도메인 모델을 사용한다.
응용 서비스는 로직을 직접 수행하기보다는 도메인 모델에 로직 수행을 위임한다.
ex) 스프링 MVC의 service 역할
-도메인 영역 : 도메인 모델을 구현
실질적으로 로직을 수행하는 영역이다.
-인프라스트럭처 영역 : 구현 기술에 대한 것을 다룬다.
(RDBMS 연동, REST API 호출..)
•계층 구조 아키텍처
표현 -> 응용 -> 도메인 -> 인프라스트럭처
상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않는다.
상위 계층(응용영역)이 하위계층(인프라스트럭처)에 의존하면 '테스트 어려움'과 '기능 확장의 어려움'의 두 가지 문제가 발생한다.
•DIP (Dependency Inversion Principle, 의존 역전 원칙)
고수준 모듈 : 의미 있는 단일 기능을 제공하는 모듈
고수준 모듈의 기능을 구현하려면 여러 하위 기능이 필요하다.
저수준 모듈 : 하위 기능을 실제로 구현한 것
고수준 모듈이 저수준 모듈을 사용하면 '테스트 어려움'과 '기능 확장의 어려움' 문제가 발생 !
DIP는 저수준 모듈이 고수준 모듈에 의존하도록 바꾼다. -> 추상화한 인터페이스를 이용
ex) 스프링 프레임워크의 DI
추상화 인터페이스를 이용하면 실제 구현 없이 테스트 대용 객체를 이용해 테스트를 진행할 수 있다.
DIP를 적용하면 응용 영역과 도메인 영역에 영향을 최소화하면서 구현체를 변경하거나 추가할 수 있다.
•DIP 주의사항
DIP를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출한다.
(단순히 인터페이스와 구현 클래스를 분리하는 것이 아님)
하위 기능(인프라)을 추상화한 인터페이스는 고수준 모듈(도메인)에 위치한다.
•도메인 영역의 주요 구성요소
엔티티 : 고유 식별자를 갖는 객체, 도메인의 고유한 개념을 표현
ex) 주문(Order), 회원(Member), 상품(Product) ..
밸류 : 고유 식별자를 갖지 않는 객체, 개념적으로 하나인 도메인 객체의 속성을 표현
ex) 주소(Address), 금액(Money) ..
애그리거트(Aggregate) : 관련된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것
리포지터리(repository) : 도메인 모델의 영속성을 처리
도메인 서비스 : 특정 엔티티에 속하지 않은 도메인 로직을 제공
- 엔티티와 밸류
도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 제공한다.
도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경되는 것을 막는다.
도메인 모델의 엔티티는 두 개 이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현할 수 있다.
RDBMS는 밸류 타입을 제대로 표현하기 힘들다.
밸류는 불변이기 때문에 엔티티의 밸류 타입 데이터를 변경할 때 객체 자체를 완전히 교체한다. - 애그리거트
상위수준에서 전체 모델의 관계와 개별 모델을 이해하는 데 도움을 주도록 관련 객체를 하나로 묶은 군집이다.
관련된 객체를 애그리거트로 묶으면 복잡한 도메인 모델을 관리하는 데 도움이 된다.
애그리거트는 군집에 속한 객체들을 관리하는 루트 엔티티를 갖는다.
-리포지터리
리포지터리는 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다.
(RDBMS, NoSQL, 로컬 파일 등 물리적인 저장소에 객체 보관)
리포지터리의 사용 주체가 응용 서비스이기 때문에 리포지터리는 응용 서비스가 필요로 하는 메서드를 제공한다.
Chapter3. 애그리거트
•애그리거트
복잡한 도메인을 이해하고 관리하귀 쉬운 단위로 만들고 상위 수준에서 모델을 확인할 수 있게 관련된 객체를 하나의 군으로 묶어준다.
애그리거트는 일관성을 관리하는 기준이 된다.
애그리거트는 복잡한 도메인을 단순한 구조로 만들어준다.
*애그리거트는 관련된 모델을 하나로 모은 것이기 때문에 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프사이클을 갖는다.
애그리거트는 독립된 객체 군이며. 한 애그리거트에 속한 객체는 다른 애그리거트에 속하지 않고, 다른 애그리거트를 관리하지 않는다.
'A가 B를 갖는다'로 해석할 수 있는 요구사항이 있다고 하더라도 이것이 반드시 A와 B가 한 애그리거트에 속한다는 것을 의미하는 것은 아니다.
ex) 상품과 리뷰는 서로 다른 애그리거트에 속한다.
•애그리거트 루트
애그리거트 루트 엔티티는 애그리거트에 속한 모든 객체가 일관된 상태를 유지하기 위해 애그리거트 전체를 관리하는 주체이다.
애그리거트의 일관성을 유지하기 위해 애그리거트가 제공해야 할 도메인 기능을 구현한다.
도메인 규칙과 일관성을 위해 단순히 필드를 변경하는 set 메서드를 public 범위로 만들지 않고, 밸류 타입은 불변으로 구현해야 한다.
•트랜잭션 범위
트랜잭션 범위는 작을수록 좋다.
한 트랜잭션에서는 한 개의 애그리거트만 수정해야 한다. ( = 애그리거트에서 다른 애그리거트를 변경하지 않는다.)
기술제약이 있을 경우 한 트랜잭션에서 두 개 이상의 애그리거트를 수정하는 대신 도메인 이벤트와 비동기를 사용하는 방식을 사용한다. 단, 기술적으로 이벤트 방식을 도입할 수 없는 경우 한 트랜잭션에서 다수의 애그리거트를 수정해서 일관성을 처리해야 한다.
•리포지터리와 애그리거트
객체의 영속성을 처리하는 리포지터리는 애그리거트 단위로 존재한다.
리포지터리는 적어도 애그리거트 저장(save), 애그리거트를 구하는(findyById) 메서드를 제공해야 한다.
애그리거트는 개념적으로 하나이므로 리포지터리는 애그리거트 전체를 저장소에 영속화해야 한다.
애그리거트를 구하는(findyById) 리포지터리 메서드는 완전한 애그리거트를 제공해야 한다.
(완전하지 않으면 NullPointException이 발생할 수 있음)
•ID를 이용한 애그리거트 참조
애그리거트도 다른 애그리거트를 참조할 수 있다.
(= 애그리거트 관리 주체가 애그리거트 루트이므로 애그리거트 루트를 참조)
애그리거트 간의 참조는 필드를 통해 쉽게 구현이 가능하다.
(개발자에게 구현의 편리함을 제공)
단, 필드를 이용한 애그리거트 참조는 '편한 탐색 오용'(가장 큰 문제), '성능에 대한 고민', '확장 어려움'에 대한 문제를 야기할 수 있다. -> 다른 애그리거트 상태가 쉽게 변경될 수 있다. (애그리거트 간의 의존 결합도가 높아져 결과적으로 애그리거트의 변경을 어렵게 만든다.)
이 문제를 완화할 때 사용하는 것 : ID를 이용해서 다른 애그리거트를 참조하는 것
ID 참조를 사용하면 모든 객체가 참조로 연결되지 않고 한 애그리거트에 속한 객체들만 참조로 연결된다. ( = 구현 복잡도와 모델의 복잡도를 낮추고, 도메인 응집도를 높여준다. 외부 애그리거트를 직접 참조하지 않기 때문에 한 애그리거트에서 다른 애그리거트를 수정하는 문제를 원천적으로 방지할 수 있다. 애그리거트별로 구현 기술을 다르게 사용할 수 있다.)
ID를 이용한 애그리거트 참조 시 지연 로딩으로 인한 N+1 조회 문제(더 많은 쿼리를 실행해서 전체 조회 속도가 느려지는 현상)가 생길 수 있는데, 조회 전용 쿼리를 사용해 막을 수 있다.
•애그리거트 간 집합 연관
M:N 연관은 개념적으로 양쪽 애그리거트에 컬렉션으로 연관을 만든다.
RDBMS를 이용해서 M:N 연관을 구현하려면 조인 테이블을 사용한다.
•애그리거트를 팩토리로 사용하기
애그리거트가 갖고 있는 데이터(Store)를 이용해서 다른 애그리거트를 생성(Product)해야 할 때, 애그리거트에 팩토리 메서드를 구현하면 된다.
(Store에 Product를 생성하는 팩토리 메서드를 추가하면 Product를 생성할 때 필요한 데이터의 일부를 직접 제공하면서 동시에 중요한 도메인 로직을 함께 구현할 수 있다.)
Chapter4. 리포지터리와 모델구현(JPA 중심)
•JPA를 이용한 리포지터리 구현
애그리거트를 어떤 장소에 저장하느냐에 따라 리포지터리를 구현하는 방법이 다르다.
-모듈 위치
애그리거트, 리포지터리 인터페이스 : 도메인 영역
리포지터리를 구현한 클래스 : 인프라스트럭처 영역
(인터페이스를 이용해 인프라스트럭처에 대한 의존성을 낮춤)
-리포지터리 기본 기능 구현
- 아이디로 애그리거트 조회하기(findById)
- 애그리거트 저장하기(save)
인터페이스는 애그리거트 루트를 기준으로 작성한다.
•매핑 구현
엔티티를 객체가 제공할 기능 중심으로 구현하도록 유도하려면 JPA 매핑 처리를 프로퍼티 방식이 아닌 필드 방식으로 선택해서 불필요한 get/set 메서드를 구현하지 말아야 한다.
밸류 타입으로 식별자를 구현할 때, 식별자에 기능을 추가할 수 있는 장점이 있다.
•별도 테이블에 저장하는 밸류 매핑
애그리거트에 속한 객체가 밸류인지 엔티티인지 구분하는 방법은 고유 식별자를 갖는지 여부를 확인하는 것이다.
(단, 별도 테이블로 저장되고 테이블에 PK가 있다고 해서 테이블과 매핑되는 애그리거트 구성요소가 고유 식별자를 갖는 것은 아니다.)
•애그릭거트 로딩 전략*
애그리거트 루트를 로딩하면 루트에 속한 모든 객체가 완전한 상태여야 한다. ( = 조회 뿐만 아니라 저장, 삭제할 때도 하나로 처리해야 함)
조회 시점에서 애그리거트를 완전한 상태가 되도록 하려면 애그리거트 루트에서 연관 매핑의 조회 방식을 즉시 로딩(eager)으로 설정하면 된다.
(잘못 사용할 경우, 과다 쿼리 실행으로 조회 성능이 나빠지는 문제가 발생한다.)
애그리거트가 완전해야 하는 이유
- 상태를 변경하는 기능을 실행할 때 애그리거트 상태가 완전해야 한다. -> 지연 로딩을 이용해 해결 가능
- 표현 영역에서 애그리거트의 상태 정보를 보여줄 때 필요하다. -> 별도의 조회 전용 기능을 구현하는 방식을 이용하면 유리하다.
=> 애그리거트에 맞게 즉시 로딩과 지연 로딩을 선택해서 사용해야 한다.
Chapter5. 리포지터리의 조회 기능(JPA 중심)
•검색을 위한 스펙
검색 조건의 조합이 다양할 경우 스펙을 이용한다.
스펙(Specification)은 애그리거트가 특정 조건을 충족하는지 여부를 검사한다.
리포지터리는 스펙을 전달받아 애그리거트를 걸러내는 용도로 사용한다.
스펙 장점 : 조합해서 새로운 스펙이나 더 복잡한 스펙을 만들 수 있다.
•조회 전용 기능 구현
여러 애그리거트를 조합해서 한 화면에 보여주는 데이터 제공, 각종 통계 데이터 제공 할 때 사용한다.
-동적 인스턴스 생성 : 객체 기준으로 쿼리를 작성, 지연/즉시 로딩에 대한 고민 없이 원하는 형태로 데이터 조회 가능
-하이버네이트 @Subselect : 쿼리 결과를 @Entity로 매핑할 수 있는 기능
Chapter6. 응용 서비스와 표현 영역
•표현 영역과 응용 영역
응용 영역과 표현 영역은 사용자와 도메인을 연결해 주는 매개체 역할을 한다.
표현 영역 : 사용자의 요청을 해석
응용 영역의 서비스 : 실제 사용자가 원하는 기능을 제공하는 주체
응용 서비스는 표현 영역에 의존하지 않는다.
•응용 서비스의 역할
도메인 객체를 사용해서 사용자(클라이언트)가 요청한 기능을 실행한다.
응용 서비스는 도메인의 상태 변경을 트랜잭션으로 처리해야 한다.
(트랜잭션 범위에서 응용 서비스를 실행해야 한다.)
도메인 로직을 도메인 영역과 응용 서비스에 분산해서 구현하면 코드 품질에 문제가 발생하므로, 응용 서비스에 도메인 로직을 넣지 말아야 한다.
(코드 응집성이 떨어짐, 코드 중복 발생)
•응용 서비스의 구현
구현 방법
- 한 응용 서비스 클래스에 특정 도메인의 모든 기능 구현하기
장점 : 코드가 한 클래스에 위치하므로 동일 로직에 대한 코드 중복을 제거할 수 있다.
단점 : 한 서비스 클래스의 크기(코드 줄 수)가 커질 수 있다. (관련 없는 코드가 뒤섞여 코드 이해가 어려워짐) - 구분되는 기능별로 응용 서비스 클래스를 따로 구현하기
장점 : 코드 품질을 일정 수준으로 유지할 수 있다. 다른 기능을 구현한 코드에 영향을 받지 않는다. 코드 중복을 방지할 수 있다.
단점 : 클래스의 개수가 많아진다.
응용 서비스의 인터페이스가 필요한 상황 : 구현 클래스가 여러 개인 경우
하지만 일반적으로 구현 클래스가 여러 개인 경우가 거의 없으며, 소스 파일만 많아지고 전체 구조만 복잡해지는 문제가 발생한다.
응용 서비스에서 애그리거트 자체를 리턴하면 실행 로직을 응용 서비스와 표현 영역에 분산시켜 코드의 응집도를 낮추는 원인이 된다. -> 응용 서비스는 표현 영역에서 필요한 데이터만 리턴하는 것이 기능 실행 로직의 응집도를 높이는 확실한 방법이다.
응용 서비스가 표현 영역에 의존하지 않기 위해 파라미터 타입을 결정할 때, 표현 영역과 관련된 타입을 사용하지 말아야 한다.
(응용 서비스가 표현 영역의 역할까지 대신해서 표현 영역의 응집도가 깨지게 된다.)
응용 서비스의 역할 중 하나는 도메인 영역에서 발생시킨 이벤트(도메인에서 발생한 상태 변경)를 처리하는 것이다.
ex) 암호 변경됨, 주문 취소함
이벤트를 사용하면 코드가 복잡해지는 대신 도메인 간의 의존성이나 외부 시스템에 대한 의존을 낮춰주는 장점이 있다.
(시스템을 확장해주는 역할도 함)
•표현 영역
- 사용자가 시스템을 사용할 수 있도록 알맞은 흐름을 제공(또는 제어)한다.
- 사용자의 요청을 받은 응용 서비스에 기능 실행을 요청한다.
- 사용자의 연결 상태인 세션을 관리한다.
•값 검증
값 검증은 표현 영역과 응용 서비스 두 곳에서 모두 수행할 수 있다.
원칙적으로는 응용 서비스에서 처리해야 하지만 사용자에게 좋지 않은 경험을 제공하기 때문에 표현 영역에서도 값을 검사한다.
•권한 검사
권한 검사를 할 수 있는 곳 : 표현 영역, 응용 서비스, 도메인
•조회 전용 기능과 응용 서비스
조회를 위한 응용 서비스가 단지 조회 전용 기능을 실행하는 코드밖에 없다면 응용 서비스를 생략한다.
Chapter7. 도메인 서비스
•여러 애그리거트가 필요한 기능
여러 애그리거트가 필요할 경우 도메인 서비스를 별도로 구현한다.
ex) 할인 금액 규칙 계산
•도메인 서비스
한 애그리거트에 넣기 애매한 도메인 개념을 구현하려면 도메인 서비스를 이용해서 도메인 개념을 명시적으로 드러내면 된다.
도메인 서비스는 도메인 로직을 다룬다.
도메인 서비스는 상태 없이 로직만 구현한다.
*애그리거트 객체에 도메인 서비스를 전달하는 것은 응용 서비스 책임이다.
*도메인 서비스는 도메인 영역에 위치한다.
도메인 서비스의 구현이 특정 기술에 종속되면 인터페이스(도메인 영역)와 구현 클래스(인프라스트럭처 영역)로 분리한다.
Chapter8. 애그리거트 트랜잭션 관리
•애그리거트와 트랜잭션
동시에 애그리거트를 수정할 때 발생하는 데이터 충돌 문제를 해소한다.
애그리거트에 대한 트랜잭션 처리 방식 : 선점(Pessimistic) 잠금, 비선점(Optimistic) 잠금 (= 비관적 잠금, 낙관적 잠금)
•선점 잠금(Pessimistic Lock)
먼저 애그리거트를 구한 스레드가 애그리거트 사용이 끝날 때까지 다른 스레드가 해당 애그리거트를 수정하는 것을 막는 방식이다.
(다른 스레드는 잠금된 동안 블로킹)
보통 DBMS가 제공하는 행 단위 잠금을 사용해서 구현한다.
(특정 레코드에 한 사용자만 접근할 수 있는 잠금 장치)
선점 잠금 기능을 사용할 때는 잠금 순서에 따른 교착 상태(deadlock)가 발생하지 않도록 주의해야 한다.
-> 잠금을 구할 때 최대 대기 시간을 지정해서 해결한다. (DBMS 마다 최대 대기 시간 처리 방식이 다르다.)
•비선점 잠금(Optimistic Lock)
서로 다른 애그리거트에서 변경이 일어나 선점 잠금으로 트랜잭션 처리를 할 수 없을 때 사용한다.
잠금을 해서 동시에 접근하는 것을 막는 대신 변경한 데이터를 실제 DBMS에 반영하는 시점에 변경 가능 여부를 확인하는 방식이다.
(애그리거트 버전이 동일한 경우에만 수정 기능을 수행하도록 한다.)
비선점 잠금 방식을 여러 트랜잭션으로 확장하려면 애그리거트 정보를 뷰로 보여줄 때 버전 정보도 함께 사용자 화면에 전달해야 한다.
•오프라인 선점 잠금
단일 트랜잭션에서 동시 변경을 막는 선점 잠금 방식과 달리 오프라인 선점 잠금은 여러 트랜잭션에 걸쳐 동시 변경을 막는다.
단, 오프라인 선점 방식은 잠금의 유효 시간을 갖고, 유효 시간이 지나면 자동으로 잠금을 해제해서 다른 사용자가 다시 사용할 수 있도록 해야 한다.
(유효 시간은 일정 주기로 유효 시간을 증가시키는 방식이 필요하다.)
Chapter9. 도메인 모델과 BOUNDED CONTEXT
•도메인 모델과 경계
하위 도메인마다 사용하는 용어와 의미가 다르기 때문에 한 개의 모델로 모든 하위 도메인을 표현할 수 없다.
( = 하위 도메인마다 모델을 만들어야 한다.)
모델은 특정한 컨텍스트(문맥)하에서 완전한 의미를 갖기 때문에, 구분되는 경계를 갖는 컨텍스트를 DDD에서는 BOUNDED CONTEXT 라고 부른다.
•BOUNDED CONTEXT
BOUNDED CONTEXT는 모델의 경계를 결정하며 한 개의 BOUNDED CONTEXT는 논리적으로 한 개의 모델을 갖는다. (단, 이는 이상적인 내용일 뿐 실제는 조직 구조에 따라 BOUNDED CONTEXT가 결정된다.)
여러 하위 도메인을 하나의 BOUNDED CONTEXT에서 개발할 때는 하위 도메인의 모델이 뒤섞이지 않도록 주의해야 한다.
BOUNDED CONTEXT는 용어를 기준으로 구분한다.
물리적인 BOUNDED CONTEXT가 한 개이더라도 내부적으로 패키지를 활용해서 논리적으로 BOUNDED CONTEXT를 만든다.
•BOUNDED CONTEXT의 구현
BOUNDED CONTEXT는 도메인 모델뿐만 아니라 도메인 기능을 사용자에게 제공하는 데 필요한 표현 영역, 응용 서비스. 인프라 영역, DB 등을 모두 포함한다.
각 BOUNDED CONTEXT는 도메인에 알맞은 아키텍처를 사용한다.
Chapter10. 이벤트
•시스템 간 강결합(high coupling)의 문제*
서로 다른 BOUNDED CONTEXT 간의 강결합은 비동기 이벤트를 사용해서 없앨 수 있다.
•이벤트 용도
- 트리거 : 도메인 상태가 바뀔 때 다른 후처리를 해야할 경우 후처리를 실행하기 위한 트리거로 사용한다.
ex) 주문 - 주문 취소 이벤트(트리거) -> 환불 처리 - 서로 다른 시스템 간의 데이터 동기화
ex) 배송지 변경 이벤트 -> 외부 배송 서비스, 배송지 정보 동기화
장점 : 서로 다른 도메인 로직이 섞이는 것을 방지할 수 있다.
이벤트 핸들러를 추가해 도메인 로직에 영향 없이 기능을 확장할 수 있다.
멱등성 : 연산을 여러 번 적용해도 결과가 달라지지 않는 성질
이벤트 핸들러가 멱등성을 가지면 시스템 장애로 인해 같은 이벤트가 중복해서 발생해도 결과적으로 동일한 상태가 되기 때문에, 이벤트 중복 발생이나 중복 처리에 대한 부담을 줄일 수 있다.
Chapter11. CQRS
•CQRS
CQRS는 상태를 변경하는 명령(Command)을 위한 모델과 상태를 제공하는 조회(Query)를 위한 모델을 분리하는 패턴이다.
CQRS는 복잡한 도메인에 적합하다.
장점 : 명령 모델을 구현할 떄 도메인 자체에 집중할 수 있다, 조회 성능을 향상시키는 데 유리하다.
단점 : 더 많은 구현 기술이 필요하고, 구현해야 할 코드가 더 많다
-> 도메인이 복잡하고 트래픽이 높은 서비스에 적합하다.