가능한 엔터티 대신 값 객체를 사용해 모델링하도록 노력해야 한다.

특성만 신경 쓴다면 이를 값 객체로 분리하라. 특성의 의미를 표현하고 기능도 부여하자. 불변성. 식별자는 필요 없고, 설계의 복잡성을 줄여준다.

값의 특징

개념을 값으로

  • 측정, 수량화, 설명
  • 불변성(no side-effect)
  • 개념적 전체
  • 변경 대신 대치
  • 등가성(value equality:동등성)

유비쿼터스 언어로써 표현하는 값이어야함

측정, 수량화, 설명

날짜와 시간, 나이, 통화, 주소, 도형, …

불변성

  • No Setter
    • 하지만 완벽하진 않다. 방어 복사가 필요할 수 있음 (final일지라도 불변은 깨질 수 있다.)

개념적 전체

  • 주소 = 우편번호 + 기본주소 + 상세주소
  • 돈 = 500(수량) + 달러(통화)

Bad Case

class ThingOfWorth {    // 가치 있는 것
    private String name;
    private BigDecimal amount;
    private String currency;
}

Good Case

class ThingOfWorth {    // 가치 있는 것
    private ThingName name;         // !!
    private MonetaryValue worth;    // 가치
}
@Value
class MonetraryValue {
    private BigDecimal amount;
    private Currency currency;
}
  • ThingName으로 값 객체로 만들어서 name의 기능을 중앙화 시킬 수 있다. (도메인 로직을 안으로 품을 수 있음)

대체성

값은 변경이 불가능하므로 값 참조를 다른 값으로 바꾸는 것을 말함

FullName name = new FullName("Myeongju", "Jung");
// ...
name = new FullName("Jiwon", "M", "Jung");

값 등가성

equals(), hasoCode()

엔터티로 설계된 개념이 식별자가 필요하지 않다면 값 객체로 모델링하자.

부작용이 없는 행동

만약 값객체에 행위가 없다면, 좋은 설계인지 의심하라

  • Getter : No side-effect
  • Setter : With side-effect
FullName name = new FullName("Myeongju", "Jung");
// ...
name = name.withMiddleInitial("M");
FullName withMiddleInitial(String middleName) {
    return new FullName(this.firstName, middleName, this.lastName);
}

값 객체의 매개변수로 값만 전달하자.

  • 만약 엔터티와 같은 가변성을 가지는 인자를 받는다면 부작용이 발생할 가능성이 생긴다.
  • 아니면 Getter만 제공하는 추상화된 인자를 받는 것도 좋은 방법이다. > Entity에 Getter만 있는 인터페이스를 Mixin

기본 언어 값 타입에는 도메인에 맞춘 부작용이 없는 함수를 할당할 수 없다

  • 도메인적인 표현이 기본 언어 함수에서 제공할 수 있을리가 없다.

미니멀리즘으로 통합하기

미니멀리즘으로 통합하기

  • ACL 내에서는 조회용으로써 일부 특성과 행위가 요구된다.
  • 즉 Entity 전체가 필요한(순응자나 공유모델) 것이 아니라, 해당 컨택스트 내에서 어울리는 타입으로써 값 객체가 좋은 선택

값으로 표현되는 표준 타입

표준 타입에는 Enum을 쓰는 것이 좋다

잘 이해가 안된다 ㅠㅠ

값 객체의 테스트

  • 클라이언트 관점에서 값 객체를 보는 것은 중요하다.
  • 불변성을 테스트 해야 한다.
  • 테스트를 통해서 도메인적 표현을 확인할 수 있어야 한다.

구현

  • 불변, 불변, 불변 (No Setter)
  • equals(), hashCode(), toString()
  • JPA를 위해서 빈 생성자는 필수 (PACKAGE 접근제어로 생성)
  • 보호절을 통한 불변식 강제

값 객체의 저장

데이터 모델 누수의 부정적 영향을 거부하라

도메인 모델을 위해서 데이터 모델을 설계해야한다.

ORM 구현

오래된 내용이라서 SKIP. 이젠 Spring Data Jpa를 사용하자.

마무리

  • 값 객체 특징, 사용, 구현

개발자는 도메인보다 데이터에 초점을 맞추려는 경향이 있다.

속성만 있고 행위는 없는 데이터홀더

엔터티를 사용하는 이유

Object + Identity = Entity + Life Cycle(Mutable:가변성)

비즈니스 관계자는 보통 근사한 데이터베이스 테이블 편집기를 개발하는 데만 너무 많은 노력을 들인다. 올바른 도구를 선택하지 않았다면 공들여 다룬 CRUD 기반 솔루션의 비용은 너무 커진다. CRUD가 타당한 선택일 때가 Django, RoR등과 같은 언어 및 프레임워크가 비로소 합리적일 수 있는 순간이다.

하지만 대부분의 엔터프라이즈 애플리케이션에서는 Entity가 좋은 선택이다.

고유 식별자

사용자가 식별자를 제공한다.

자연키

  • 변경될 가능성이 크고, 사용자를 신뢰하기 힘들다.

애플리케이션이 식별자를 생성한다.

UUID, Random, Timestamp NanoSeconds, …

읽을 수 있는 식별자

APM-P-08-14-2012-F36AB21C : 2012년 08월 14일에 생성된 애자일프로젝트관리(APM)의 Product(P)

@Entity
class Product {
    private ProductId productId;

    public Date creationDate() {
        return productId.creationDate();
    }
}
  • 개인적으로 위 패턴을 선호하지 않는다.
  • 대리키를 사용하고 표현이 필요한 코드는 따로 Unique 값으로 하는 것이 좋은 것 같다.

영속성 메커니즘이 식별자를 생성한다.

JPA @GenerationType

  • AUTO, IDENTITY, SEQUENCE, TABLE

또 하나의 바운디드 컨텍스트가 식별자를 할당한다.

SKIP : 비추천

식별자 생성의 시점이 문제가 될 때

식별자 생성 시점

  1. Repository저장 후 식별자가 생성
  2. Repository저장 전 식별자가 생성

저장 후 식별자 생성 시 부작용

  1. 도메인 이벤트를 발행하는 경우 식별자가 있어야지 정확히 발행 가능 : 문제 없음 - 아래 코드 참조
  2. Set에 추가할 시 다른 식별자와 같아질 수 있음 : 문제 없음 - 충돌가능성 없음
@Entity
class Product {
    @Id @GeneratedValue
    Long id;
    String name;

    Product(String name) {
        ...
        this.registerEvent(new ProductCreatedEvent(this))
    }
}
  • 위 코드 처럼 이벤트를 발행해도 구독 시점에는 id속성이 할당되어 있어서 문제없이 이벤트 처리됨

저자는 빠른 식별자 할당을 선호한다고 했지만, 현재 기술 상으로 지연 할당도 전혀 문제 없다.

대리 식별자

JPA @GenerationType

  • AUTO, IDENTITY, SEQUENCE, TABLE

== 영속성 메커니즘이 식별자

식별자 안정성

수정 불가(값 객체), 엔터티 수명주기 동안 안정적으로 유지

  • 식별자 Setter를 제거한다.
  • 식별자 Setter가 존재하면 유효성 체크를 강화한다. > 하지만 대리키를 사용해서 Setter가 존재하지 않는 것이 나은 것 같음

나는???

  • 왠만하면 단일 대리키를 사용한다. = 식별자 생성 지연
  • 식별자는 Getter만 사용한다.
  • 다른 접근이 요구되면 Unique 키로 보조한다.

엔터티 발견과 그들의 내부적인 특성

데이터 중심 모델 > Getter + Setter > 무기력한 도메인 모델

엔터티 발견

유비쿼터스 언어로 도메인 전문가와 대화하면서 나오는 개념들

하지만

  • 명사(이름) : 엔터티
  • 동사(행위) : 메서드

위처럼 단순하게 뽑아낸다고 생각하면 실수하는 것이다.

결국에는 도메인에 대한 토론을 통해 지식 탐구정제를 거쳐야지 깊은 도메인 모델을 얻을 수 있다.

엔터티와 속성을 알아내기

Example : User

  • 사용자는 테넌시와 관련이 있고 테넌시의 제어를 받는다.
  • 시스템의 사용자는 반드시 인증돼야 한다. : Need 검색
  • 사용자는 이름과 연락처를 비롯한 개인정보를 갖고 있다.
  • 사용자의 개인정보는 사용자나 관라자에 의해 변경될 수 있다. : 값 객체?
  • 사용자의 보안 인증(비밀번호)은 변경될 수 있다.

앞선 발견에 따른 Tenant와 User라는 두 개의 엔터티

그림 5.5 앞선 발견에 따른 TenantUser라는 두 개의 엔터티

추가 스팩

  • 테넌트는 초대를 통한 많은 사용자의 등록을 허용한다.
  • 테넌트는 활성화될 수 있고, 비활성화될 수도 있다.
  • 시스템의 사용자는 반드시 인증돼야 하지만, 테넌트가 활성화되니 경우에만 인증이 가능하다.

엔터티가 발견해서 이름 지은 후에는 이를 고유하게 식별하고 찾을 수 있도록 해주는 속성을 알아내자

그림 5.6 엔터티가 발견해서 이름 지은 후에는 이를 고유하게 식별하고 찾을 수 있도록 해주는 속성을 알아내자

확인 스팩

  • 테넌트 : 식별자와 엑세스 서비스와 그 밖에 다른 온라인 서비스의 명명된 조직적 구독자, 초대를 통해 사용자를 등록
  • 사용자 : 테넌티 내에 보안 주체. 고유 사용자명과 암호화된 비밀번호를 가진다.
    • SecurityPricipal : 인증 주체 값 객체 - 추후에 알아보자
  • 암호화 서비스 : 암호화, 복호화

필수 행동 파헤치기

Tenant의 스팩

  • 테넌트는 활성화되거나 비활성화될 수 있다.
@Entity
class Tenant {
    void activate() {
        // TODO 구현
    }
    void deactivate() {
        // TODO 구현
    }
}

테넌트에 필수적인 행동 추가

그림 5.7 테넌트에 필수적인 행동 추가

  • activate() : 테넌트 활성화
  • deactivate() : 테넌트 비활성화
  • isActive() : 사용자는 테넌트가 활성화된 경우에만 인증이 가능하다.
    • 인증을 위해 AuthenticationService가 필요
  • registerUser() : 테넌트는 초대를 통해서 사용자 등록을 허용한다.

User의 스팩

  • 사용자는 이름과 연락처를 비롯한 개인정보를 갖고 있다. : Person Entity 분리
  • 사용자의 개인정보는 사용자나 관리자에 의해 변경될 수 있다. : change?()
  • 사용자의 보안 인증(비밀번호)은 변경될 수 있다. : changePassword()

User 상세 모델링

그림 5.8 User 상세 모델링

역할과 책임

어떤 설계를 하더라도, 유비쿼터스 언어가 기술적 선호보다 항상 우위에 서도록 하자. DDD에서는 비즈니스 도메인 모델이 가장 중요하다.

생성

고정자(Invariant:불변식)을 지킨다. > 엔터티의 전체 수명주기에 걸쳐 트랜잭션적 일관성이 유지돼야 하는 상태

복잡한 엔터티 생성을 위해서 Factory를 사용한다.

유효성 검사

  • 특성 : 불변?
  • 속성 : 가변?

DBC(Design By Contract) : 계약의 의한 설계

  • Pre condition, Post condition, Invariant로 설명
  • 하지만 Java 등의 언어에서는 보호절로써 처리함 : 방어적 프로그램

유효성 체크의 책임 DB가 아니라 도메인이 져야한다.

  • 유효성 검사의 책임이 너무 커지면 도메인 모듈(패키지)내 Validator 클래스 분리한다.
  • with 명세 패턴, 전략 패턴

유효성 체크 책임 위치

  • 엔터티
  • 엔터티에서 분리된 Validator로 위임
  • 도메인 서비스

변화 추적

가장 실용적인 변경 추적 : 도메인 이벤트 + 이벤트 저장소 = 이벤트 소싱!

마무리

  • 식별자
  • 유비쿼터스 언어를 바탕으로 엔터티 모델링
  • 엔터티 구성, 유효성 검사, 변경 추적 등 구현

실제 요구(도메인)가 아키텍처 스타일과 패턴의 사용을 유도해야 한다.

  • 도메인이 기술 보다 먼저다.

성공한 CIO와의 인터뷰

  1. Server-Client Architecture
  2. 계층형 아키텍처
    • DIP, QP, PSA, DDD-Lite
  3. 헥사고날 아키텍처
    • To Mobile, Cloud
  4. CQRS
    • Materialized Views
  5. Event-Driven Architecture
    • Pipeline + Filter
  6. Saga 패턴
    • Long-lived transaction as a sequence of subtransactions.
    • In a distributed system
  7. 이벤트 소싱
    • 모든 변경을 추적 (ex: 보안 감사 등)

이러한 아키텍처 덕분에 결국 회사에 돈이 된다

계층

DDD Layred Architecture

그림 4.1 DDD가 적용된 전통적인 계층 아키텍처

[출처] https://ajlopez.wordpress.com/2008/09/12/layered-architecture-in-domain-driven-design/

  • 도메인 모델과 비즈니스 로직을 도메인 계층에 격리
  • 하위 계층에만 의존 - 아래에서 위쪽으로 직접 참조 불가 (하지만 Observer 사용 가능)
    • 느슨한 연결 : UI가 애플리케이션(바로 하위) 뿐만 아니라 도메인이나 인프라에도 의존 가능

UI 계층

  • View나 API 제공
  • 도메인 모델이 아닌 표현 모델(DTO)를 사용 추천

애플리케이션 계층

UI로 부터 매개변수를 받아 리파지토리를 사용해 애그리게잇을 획득하고, 커맨드를 위임

  • 보안과 트랜젝션 담당 (일반적으로 보안을 UI로 올리는 것 같음)
  • 도메인 로직 없으며 도메인 모델(애그리게잇, 도메인 서비스 등)에 위임
  • 도메인 모델에서 발행한 도메인 이벤트를 구독(subscribe)
@Transactionl
void commitBackLogItemToSpring(
        String aTanantId, String aBackLogItemId, String aSprintId) {
    // 재료(Aggregate) 준비
    BacklogItem backlogItem = 
        backlogItemRepository.backlogItemOfId(aTanantId, aBacklogItemId);
    Sprint sprint sprintRepository.sprintOfId(aSprintId);
    // 커맨드 위임
    backlogItem.commitTo(sprint);
}

DIP

  • 상위 수준의 모듈은 하위 수준 모듈에 의존해선 안된다. 둘 모두는 반드시 추상화에 의존해야 한다.
  • 추상화는 세부사항에 의존해선 안 된다. 세부사항은 추상화에 의존해야 한다.

하위 구현 컴포넌트가 상위 콤포넌트가 정의한 인터페이스에 의존해야한다.

어라 그러다 보니 계층이 없어진다. 여기에 대칭성을 더하면 어떻게 될까?

헥사고날 또는 포트와 어댑터

Hexagonal Architecture

헥사고날 아키텍처

[출처] http://alistair.cockburn.us/Hexagonal+architecture

Texi handling Hexagonal Architecture

Texi handling Hexagonal Architecture

[출처] https://github.com/seongminwoo/study/blob/master/7-part_series_about_microservices.md

헥사고날의 장점

  • 기능적 요구사항에 따라 애플리케이션 내부를 설계
    • UI, Infra는 그 다음이다.
    • 애플리케이션 내부가 캡슐화 된다.
  • Adapter를 통해 기술과 분리

서비스 지향(SOA)

중요한 것은 기술 보다 비즈니스(도메인) 가치가 우선해야 한다

REST: 표현 상태 전송

The glory of REST

[출처] https://martinfowler.com/articles/richardsonMaturityModel.html

RESTful HTTP 서버의 주요 특징

  1. Resource : URI
  2. Verbs : GET, POST, PUT, DELETE, …
  3. Hypermedia : 연결된 리소스를 제공. 상호작용, 클라이언트 무상태

RESTful HTTP 클라이언트의 주요 특징

Hypermedia를 바탕으로 상호 작용한다.

REST와 DDD

도메인 모델을 RESTful로 바로 노출하는 것은 좋지 않다.

  • 도메인 모델의 변경이 API와 연결되기 때문에 변경에 취약해지며, 클라이언트와 호환성이 깨진다.

해결책

  1. 도메인 모델 -> 표현 모델을 조립 (추천)
    • Assembler + DTO (PoEAA)
  2. 각 상황 별 공유 도메인 모델 사용
    • 공유커널

왜 REST인가

느슨함

CQRS

사용자가 필요로하는 데이터 뷰를 리파지토리로 쿼리하기란 어려울 수 있다.

CQRS = 객체 설계 원칙 + CQS

  1. Command : 객체의 상태를 수정, Void 형
  2. Query : 값을 반환, 수정 X

구현

  • 애그리게잇은 오직 커맨드 메소드만 가지고 있음.(커맨드 모델)
  • 쿼리를 위한 뷰 전용 모델(쿼리 모델)을 생성

CQRS의 영역 살펴보기

CQRS Architecture

출처 : https://docs.microsoft.com/ko-kr/azure/architecture/guide/architecture-styles/cqrs 참고 : https://docs.microsoft.com/ko-kr/azure/architecture/patterns/cqrs

클라이언트와 쿼리 처리기

쿼리 모델(읽기 모델)

  • 필요한 수 만큼 뷰를 지원하기
  • 실용적으로 하라
  • 데이터베이스 테이블 뷰가 오버헤드의 원인이 되지 않을까?

클라이언트가 커맨트 처리를 주도한다.

UI to Argument

커맨드 처리기

  • 카테고리 스타일 : 1 class n methods
  • 전용 스타일 : 1 class 1 method

커맨드 모델(쓰기 모델)은 행동을 수행한다.

public void commitTo(Sprint aSprint) {
    ...
    DomainEventPublisher
        .instance()
        .publish(new BacklogItemCommitted(
            this.tenant(),
            this.backlogItemId(),
            this.sprintId()
        ));
}
  • 어떤 경우든 쿼리 모델을 업데이트시키기 위해선 도메인 이벤트를 게시해야 한다.

이벤트 구독자가 쿼리 모델을 업데이트 한다.

  • 동기도 가능하고 비동기도 가능하다.

결국은 일관성이 유지되는 쿼리 모델 다루기

Eventually consistent

지연되는 뷰 데이터 동기화를 해결해야한다.

  • 낙관적 업데이트 기법!
  • 클라이언트에서 publish/subscribe 기법

결국은 동기화되는 지연시간이 문제

이벤트 주도 아키텍처(EDA)

이벤트의 생산, 감지, 소비와 이벤트에 따른 응답 등을 촉진하는 소프트웨어 아키텍처

From 도메인 이벤트

파이프와 필터

$ cat phone_number.txt | grep 303 | wc -1

필터를 처리하는 이벤트를 보냄으로써 파이프라인이 만들어진다.

그림 4.8 필터를 처리하는 이벤트를 보냄으로써 파이프라인이 만들어진다.

[출처] http://zhangyi.farbox.com/post/coding/understand-scala-stack

이는 가상의 예제이자 개념적 부분을 강조했을 뿐이다. 실제 엔터프라이즈에선 큰 문제를 좀 더 작은 단계로 나누기 위해 이 패턴을 사용하며, 좀 더 쉽게 분산 처리를 이해하고 관리하도록 해준다. 또한 여러 시스템이 오직 자신이 할 일(도메인)만을 걱정하게 되게 해주기도 한다.

장기 실행 프로세스

  1. 컴포지트
  2. 애그리게잇 집합
  3. 이벤트

역시나 중요한 것은 결과적 일관성(Eventual Consistency)

Saga Pattern

Events/Choreography

Saga Sequence

Rollback

Saga Rollback

이벤트 소싱

거의 완벽한 수준의 변경 추적

이벤트 소싱

[출처] https://docs.microsoft.com/ko-kr/azure/architecture/patterns/event-sourcing

Replay : 이벤트소싱에서 이벤트를 되감아서 특정 버전 상태로 되돌리는 것

  • 하지만 최신 버전 Replay는 병목의 원인
  • 이를 해결하기 위해서 최신 버전의 상태를 Snapshot으로 지정해서 최적화

데이터 패브릭과 그리드 기반 분산 컴퓨팅

분산 캐시

ex) hazelcast

데이터 복제

  • 캐시 master/slave
  • 복제를 통한 장애 극복
    • 이벤트 유실 제거 (이벤트 publish/subscribe도 가능)

이벤트 주도 패브릭과 도메인 이벤트

For Domain Event

지속적 쿼리

For CQRS

분산 처리

For Saga 또는 배치 병렬 처리

마무리

  • Layerd Architecture > DIP > Hexagonal Architecture
  • SOA, REST, 분산 컴퓨팅
  • CQRS, Event Sourcing, pipe/filter, Saga

바운디드 컨텍스트 간 관계를 그리는 것으로써 해결책 공간에 초점을 맞추가 있다.

컨텍스트 맵이 필수적인 이유

추상적 도메인의 컨텍스트 맵.

그림 3.1 추상적 도메인의 컨텍스트 맵

[출처] https://www.safaribooksonline.com/library/view/implementing-domain-driven-design/9780133039900/ch03lev1sec1.html

  • U = 업스트림
  • D = 다운스트림

컨텍스트 맵은 상호 교류하는 시스템의 목록을 제공하고, 팀 내 의사소통의 촉매 역할을 한다.

프로젝트와 조직 관계

통합 패턴들

  • 파트너십(Partnership) : 두 컨텐스트가 한 트랜젝션으로 묶임 - 2 phase commit?
  • 공유 커널(Shared kernel) : 상호 의존하는 공유 모델을 관리 - 안티 패턴이라고 봄
  • 고객-공급자(Customer-Supplier Development) : 업스트림(서버:공급자), 다운스트림(클라이언트:고객)로 단방향 의존 표현
  • 순응주의자(Conformist) : 업스트림(서버) is King
  • 부패 방지 계층(Anticorruption Layer) : 변환을 통해서 다운스트림 컨텍스트 내 순수함을 지킴 (Adapter + Translator)
  • 오픈 호스트 서비스(Open Host Service) : REST/API, RPC, Socket
  • 발행된 언어(Published Language) : Json, XML, Byte
  • 분리된 방법(Seprate Ways) : 의존 없음
  • 큰 진흙공(Big ball of mud) : 똥덩어리

세 가지 컨텍스트를 매핑하기

before

Big ball of mud

  • 문제점 공간
  • 분리된 핵심을 이용

after

Separated

  • 해결책 공간

Context Map Example

  • 대부분 순응주의자를 많이 사용하는 것 같다.
  • 상호 의존이 걸리는 partnership도 많은 것 같다.
  • 개인적으로는 고객-공급자가 좋다.
  • 일반적으로 핵심 도메인은 다운스트림이다.
  • 업스트림에서 다운스트림으로 모델 복제는 Evil이다.

[출처] https://www.codeproject.com/Articles/1158628/Domain-Driven-Design-What-You-Need-to-Know-About-

  • 통합 전에 ACL를 통해서 변활할 시 최소한의 속성만 있으면 된다.

식별자와 액세스 컨텍스트의 통합

식별자 접근 부패방지 Sequence Diagram

  • 중요한 것은 애자일 프로젝트 컨택스트에 식별자 도메인이 침투하지 못하게 하는 것

협업 컨텍스트와 통합

결과적 일관성이 중요하다. - 이벤트 기반 아키텍쳐

그렇다면 상태관리를 해야한다.

enum DiscussionAvailability {
    ADD_ON_NOT_ENABLED, NOT_REQUESTED, REQUESTED, READY;
}

@Value
class Discussion {
    DiscussionAvailiability availability;
    DiscussionDescriptor descriptor;
    ...
}

class Product extends Entity {
    private Discussion discussion;
    ...
}
  • Product에서 Discussion을 사용할 시 READY인 경우에만 가능

프로젝트 관리 컨텍스트와 협업 컨텍스트 통합

  • 협업.Calendar -> 프로젝트관리.Scheduling
  • 협업.Forum -> 프로젝트관리.Discussion

마무리

  • OHS, PL, ACL

서브도메인과 바운디드 컨텍스트로 전략적으로 설계해보자.

큰그림

서브도메인과 바운디드 컨텍스트의 활용

서브도메인과 바운디드 컨텍스트를 포함한 도메인

온라인 쇼핑몰 도메인

  • 전자상거래 시스템 : 상품 카탈로그, 주문, 송장, 배송 서브도메인
  • 재고관리 시스템 : 재고관리
  • 외부 예측 시스템 : ?

제품 카탈로그, 주문, 송장, 배송 모델 등을 하나로 엮어서 복잡도가 높아지며 부정적인 결과를 초래

소프트웨어의 관심사를 도메인을 바탕으로 분명하게 분리시켜야 한다.

상호 의존성을 갖기 때문에, 서브도메인으로 나누지 않는다면 변화가 계속됨에 따라 훨씬 큰 부담을 지게 된다.

일반적으로 하나의 바운디드 컨텍스트에 하나의 서브도메인이 있는 것이 좋다. : 재고관리는 좋아 보인다.

  • 물론 DSL이 포함되어야 한다.
  • 같은 언어일지라도 개념은 전혀 다를 수 있다.

핵심 도메인에 집중하기

서브도메인과 바운디드 컨텍스트를 포함하는 추상적인 비즈니스 도메인

  • 핵심도메인 : 주문
  • 지원 서브도메인 : 배송, 상품, 송장
  • 범용 서브도메인 : 회원(인증과 권한)

결국 도메인 전문가와 소통으로 정제하는 것이 중요하다.

왜 전략적 설계가 엄청나게 필수적인가

협업 개념이 사용자 및 권한과 강한 결합을 가지는 것이 옳은가?

  • 협업 개념에서는 회원은 그저 작성자일 뿐이다.

팀이 전략적 설계의 기초를 이해하지 못했고, 이는 공동 모델에서 개념들이 잘못 찢어지도록 했다. 점선 안의 문제가 되는 요소들이다.

그림 2.3 팀이 전략적 설계의 기초를 이해하지 못했고, 이는 공동 모델에서 개념들이 잘못 찢어지도록 했다. 점선 안의 문제가 되는 요소들이다.

협업 도구는 사용자의 역할에 집중해야지, 사용자가 누구며 수행권한이 부여된 행동이 무엇인지에 관심이 있어선 안 된다. 하지만 위에서는 뒤섞여 버렸다.

  • 포럼은 단지 토론을 게시하고 싶은 작성자만 있으면 된다.
  • 사용자와 권한은 협업 컨텍스트에서 독립돼야 한다.

Big ball of mud : 거대한 진흙공 - 빈약한 도메인으로 가득찬 모노리식 애플리케이션

이런 사용자와 권한 서브도메엔을 범용 서브도메인이라고 한다.

이윽고 팀이 비협업 개념의 또 다른 집합을 모델링하는 상황이 오면, 핵심 도메인은 더욱 불확실해진다. 풍부한 협업의 유비쿼터스 언어를 제대로 소스 코드에 반영하지 못한 채, 이를 은영준에 내포하고 있을 뿐인 모델을 만들고 말 수도 있다. 팀은 비즈니스 도메인과 그에 따른 서브데메인은 물론이고, 반드시 그들이 개발하고 있는 바운디드 컨텍스트도 제대로 이해해야한다. 이를 통해 전략적 설계를 가로 막는 비열한 적인 거대한 진흙공의 더러운 물을 막을 수 있다.

현실의 도메인과 서브도메인

문제점 공간과 해결책 공간

도메인은 문제점 공간(problem space)과 해결책 공간(solution space)을 모두 갖고 있다.

  • 문제점 공간 : 새로운 핵심 도메인을 만들기 위한 전체 도메인의 일부 > 핵심 도메인과 서브 도메인의 조합
  • 해결책 공간 : 해결책을 소프트웨어로 구현 > 바운디드 컨텍스트

서브 도메인을 1:1로 바운디드 컨텍스트로 묶는 것은 좋은 목표이다 하지만 항상 그렇지는 않다.

구매나 재고관리와 관련된 핵심 도메인과 다른 서브도메인.

그림 2.4 구매나 재고관리와 관련된 핵심 도메인과 다른 서브도메인. 이 관점은 특정 문제점 공간 분석을 위해 선택된 서브도메인으로 제한되며 전체 도메인에 적용할 수는 없다.

  • 최적 매입 컨텍스트 = 핵심 도메인
  • 구매 컨텍스트 + ERP 구매 모듈 = 구매 지원 서브 도메인
  • 재고 관리 컨텍스트 + ERP 재고 모듈 = 재고 관리 지원 서브 도메인
  • 매핑 컨텍스트 : 범용 서브 도메인

최적 매입 컨텍스트를 개발하는 회사 입장에서의 이런 핵심 포인트를 살펴봤음을 기억하자. 지리적 매핑 서비스는 문제점 공간에서 재고관리 서브도메인의 일부로 간주하지만, 해결책 공간에서 재고관리 컨텍스트가 아니다. 매핑 서비스가 해결책 공간에선 간단한 컴포넌트 기반 API로 제공됐다 하더라도, 이는 다른 바운디드 컨텍스트다. 재고 관리와 매핑의 유비쿼터스 언어는 상호 배타적이며, 이는 두 요소가 서로 다른 바운디드 컨텍스트라는 의미다. 재고 관리 컨텍스트가 외부 매핑 컨텍스트의 어떤 부분을 사용하는 상황에서, 데이터는 적어도 최소한의 변환을 거쳐야만 적절히 사용될 수 있다.

한편 구독자를 위해 매핑 서비스를 개발하고 제공하는 외부 비즈니스 조직의 관점에서 보면 매핑은 핵심 도메인이다.

전략적 이니셔티브 : KPI를 달성하기 위한 핵심적인 활동이나 계획

바운디드 컨텍스트 이해하기

바운디드 컨텍스트는 그 안에 도메인 모델이 존재하는 명시적 경계

  • 바운디드 컨텍스는 명시적이고 언어적이다

컨텍스트가 왕이다. in DDD

모델의 중앙화, 범용화는 Evil 이다. 컨텍스트 별로 모델은 다른 의미를 가진다.

두 바인디드 컨텍스트 속 어카운트 객체는 의미가 서로 완전 다르지만, 각 바운디드 컨텍스트 안에서 고려해야만 그 사실을 알 수 있다.

그림 2.5 두 바인디드 컨텍스트 속 어카운트 객체는 의미가 서로 완전 다르지만, 각 바운디드 컨텍스트 안에서 고려해야만 그 사실을 알 수 있다.

컨텍스트 어카운트 의미
은행 계좌 적금 계좌
문학 이야기 A Personal Account of Mt. Everest Disaster

모든 것을 빠짐없이 포괄하는 모델을 생성하려 시도하는 함정에 빠지며, 어디서든 통용되는 유일한 의미를 가진 이름의 개념에 대해 전체 조직이 모두 동의하는 결과를 목표로 삼는다. 이런 모델링 접근법에는 구멍이 있다.

  1. 모든 이해관계자로부터 모든 개념이 하나의 순수하고 구분된 글로벌한 의미를 갖는 것에 대해 동의를 얻기란 거의 불가능하다.
  2. 모든 사람을 함께 모으는 것은 절대 불가능하다. : 이건 좀 아닌 것 같음.

최상의 선택을 위해선 언제나 차이점은 존재한다는 사실을 직시하고 ,바운디드 컨텍스트를 통해 차이점이 명확하며 잘 이해하고 있는 도메인 모델을 각각 기술해야 한다.

1 바운디드 컨텍스트 != 1 프로젝트 아티팩트(산출물)

  • 주문은 카탈로그 컨텍스트와 주문 컨텍스트에서 다른 모델로 표현된다.
  • 고객은 등록, 계정, 배송 컨텍스트 별로 다른 모델로 표현된다.

모델 그 이상을 위해

아래 항목들도 바운디드 컨텍스트 경계 안에 있다.

  • Application Service : 보안 트랜젝션 관리 : 퍼사드
  • UI
  • Api Client : API 통합

안티패턴

  • Smart UI

바운디드 컨텍스트의 크기

= 유비쿼터스 언어를 표현하기 위해 필요한 크기

딱 내가 원했던 만큼의 음표들이 있었습니다. 더도 덜도 아닌

  • 모짜르트

잘못된 크기의 바운디드 컨텍스트를 만들게 되는 이유?

  1. 아키텍처적 영향을 기준으로 삼는 경우(플랫폼, 프레임워크, 컴포넌트, 인프라 등)
  2. 개발자 리소스(또는 팀)를 작업을 분배하기 위해

기술적 컴포넌트로 정렬하기

com.mycompany.optimalpuchasing : bounded context

  • com.mycompany.optimalpuchasing.prsentation : ui
  • com.mycompany.optimalpuchasing.application : application
  • com.mycompany.optimalpuchasing.domain.model : domain
  • com.mycompany.optimalpuchasing.infrastructure : infra

샘플 컨텍스트

완전히 서브도메인과 정렬된 바운디드 컨텍스트 샘플의 평가 관점 그림 2.7 완전히 서브도메인과 정렬된 바운디드 컨텍스트 샘플의 평가 관점

출처 : https://www.safaribooksonline.com/library/view/implementing-domain-driven-design/9780133039900/ch02lev1sec5.html

협업 컨텍스트

협업 컨텍스트

before

public class Forum extends Entity {
    public Discussion startDiscussion(...) {
        // repository 의존
        User user = userRepository.userFor(this.tenantId(), aUsername);
        // 보안 로직
        if (!user.hasPermissionTo(Permission.Foru.StartDiscussion))
        //...
        // 열차 충돌
        String authorName = user.person().name().asFormattedName();
        //...
        return newDiscussion;
    }
}
  • 협업 보다는 보안을 염두함 in 협업 컨텍스트
  • 전술적 패턴(DDD-Lite)으로는 해결 불가능. 전략적 설계 컨텍스트 맵!!

열차사고

결론적 해결책을 통해 추가적인 전략적 설계를 사용해, 재사용 가능한 모델을 별도의 바운디드 컨텍스트로 분리하고 적절히 통합할 수 있게 됐다

after

public class ForumService {
    @Transactional
    public Discussion startDiscussion(...) {
        Author author = this.colloboratorSrvice.authorFrom(tenant, anAuthorId);
        Discussion new Discussion = forum.startDiscussionFor(...);
        //...
        return newDiscussion;
    }
}
public class Forum extends Entity {
    public Discussion startDiscussionFor(...) {
        //...
    }
}
  • UserPermission의 의존을 제거하고 모델을 엄격히 협업에만 집중하게 함 Author

식별자와 엑세스 컨텍스트

사일로 효과 : 연통 배관 : 상호 협력을 하지 않고 중복이 생기고 각각 따로 놀게됨

식별자 컨텍스트

  • 위에서 지적된 것 처럼 식별자 컨텍스트를 분리하고 범용 지원 도메인으로써 활용

애자일 프로젝트 관리 컨텍스트

애자일 프로젝트 관리 컨텍스트

  • DDD 전략적 설계를 바탕으로 애자일 프로젝트 컨텍스트를 분리

컨텍스트는 각 팀에게 아주 구체적인 의미를 부여한다.

마무리

  • 도메인, 서브도메인, 바운디드 컨텍스트
  • 문제점 공간, 해결책 공간
  • 모델을 구분(User vs Author)
  • 바운디드 컨텍스트의 크기
  • 사스오베이션 팀의 바운디드 컨텍스트 정제