빨간색코딩

DDD Quickly (도메인 주도 설계, 전술적-전략적 설계, Spring @Service의 유래, @Configurable를 통한 도메인 모델에 bean 주입) 본문

디자인패턴

DDD Quickly (도메인 주도 설계, 전술적-전략적 설계, Spring @Service의 유래, @Configurable를 통한 도메인 모델에 bean 주입)

빨간색소년 2022. 1. 17. 05:35

Domain 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년대 초였다.
  • UML은 핵심 개념들을 클래스로 표현하고 클래스간 관계로 표현하는데 훌륭하다.
    • 그러나, 규모가 커지면 사실상 읽을 수 없다.
    • 또한, 행동이나 제약사항 표현이 어렵다.
    • 따라서, 작은 범위로 쪼개서 작게 다이어그램을 작성하고 주석을 달아라

3장. 모델 주도 설계 (전술적 설계, Tactical Design)

  • 비즈니스 도메인 전문가와 소프트웨어 분석가가 모델을 도출하더라도, 소프트웨어 개발자의 구현 레벨에서 간극이 생길 수 있다.
  • 모델을 코드로 어떻게 변환할 것인가?
    • 1안) 도메인 분석과 코드 구현을 별개로 분리함. 개발자는 분석모델을 참고만 할 뿐이어서 코딩이 시작되면 분석모델은 폐기됨
      • 영속성, 성능 등에 있어서 분석 모델이 항상 최적최선은 아니기 때문이다.
      • 분석된 모델을 개발자들에게 문서로 넘겨줄 때, 분석가들의 지식이 온전히 전달되지 않는다. 개발자들은 어림짐작을 하게 된다.
    • 2안) 분석가와 개발자가 함께 참여하여 소프트웨어로 표현될 수 있는 모델을 설계해야 한다.
      • 개발을 고려하지 않는 분석은 구현에서 발생할 수 있는 문제점을 놓치고 모델은 비현실적인 게 된다.
      • 구현과 모델을 밀접하게 연관시키려면 객체지향 언어처럼 모델링 패러다임을 지원하는 걸 사용하는게 좋다.
  • 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 와의 큰 차이점이라고 볼 수도 있겠다. 행동을 객체에서 의도적으로 분리시키기 때문이다.
  • 이러한 행위들을 서비스 객체로 정의한다.
  • 내부적인 상태를 갖지 않으면서 도메인의 기능을 제공한다.
  • 서비스의 특징
    1. 엔티티, VO에 속할 수 없는 도메인의 개념
    2. 도메인의 다른 객체를 참조
    3. stateless
  • 서비스가 어느 레이어에 속해야하는지는 상황과 개념에 따라 다르다.
  • 이 부분에서 문득, 자주 다루는 Spring 프레임워크의 @Service 과는 무슨 상관이 있을까? 궁금했는데..
    • DDD 에서 따온 개념이라 한다.. 소름..

3-5. 모듈

  • 모델이 거대해지면서 전체를 파악하기 어려워짐
  • 모듈화는 관련된 개념과 작업을 조직화하여 모델을 모듈로 쪼개는 것이다.
  • 모듈을 쪼갤 때, 높은 응집도(High Cohesion)와 낮은 결합도(Loose Coupling)를 추구해야 한다는 원칙을 지키기 위해 노력해야한다.
    • 통신(communicational) 응집도 : 비슷한 데이터들을 그룹핑하여 모듈화
    • 기능(functional) 응집도
  • 모듈은 다른 모듈에서 쉽게 접근 할 수 있는 인터페이스를 가져야한다.
    • 구체적인 클래스를 참조하지 않아도 되므로 결합도 감소
  • 모듈은 관점에 따라 MSA의 마이크로서비스 단위가 될 수도 있을 듯?
    • cf) 반 버논은 분할된 컨텍스트 내 여러 마이크로서비스가 들어갈 수도 있다고 한다.

3-6. 집합 (Aggregate)

  • 도메인 객체의 소유권과 경계를 정의하는 패턴
  • 모델링에서의 과제는 어떻게 해야 단순하고 이해하기 쉬운 모델을 만들 수 있는가?
    • 보통 두 객체간 참조 관계를 제거하며 단순화함
    • 1대N 관계는 N이 1의 집합으로 들어감으로써 단순화
    • N대M 관계는 아래와 같이 단순화하라
      1. 모델의 핵심이 아닌 관계는 제거
      2. 관계에 참여하는 객체의 수를 제약사항으로 걸러내어 감소
      3. 양방향인데 단방향으로 대체
        • 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, 디스크 등 여러 하위 객체들로 구성된 것처럼..
  • 팩토리가 굳이 필요하지 않고, 생성자로 충분한 경우
    1. 생성 작업이 복잡하지 않음
    2. 객체 생성이 다른 객체의 생성과 연관되어 있지 않음
    3. 전략 패턴을 선택할 때
      • cf) 전략 패턴 : 객체가 할 수 있는 행위들을 각각의 전략으로 만들고, 행위의 수정이 필요하다면 전략을 단순히 교체만하면 되도록 함
    4. 인자에 따라 생성되는 클래스가 달라지지 않고, 자기 자신 일때
  • 팩토리는 순수한 도메인 레이어다.

3-8. 리파지토리 (Repository)

  • 클라이언트가 DB에서 필요한 정보(객체)를 조회하고 바로 사용한다면, 도메인 모델 설계에 위반한다.
    • sql 결과로 필요이상으로 많은 데이터가 노출된다.
    • 도메인 개념보다 인프라스트럭처를 더 다루게 된다.
    • 모델은 단순히 데이터운반 역할만 하게 될 것이다.
    • 인프라스트럭처 변경에도 취약해진다.
  • 하위 집합의 어떤 객체가 필요하더라도, 도메인 모델 설계에 맞게 root(=엔티티, 도메인객체)를 조회하고, root부터 참조하라
  • 객체의 참조를 얻는 로직을 캡슐화하기 위하여 리파지토리를 사용하라
    • 리파지토리로만 참조할 수 있다면, 도메인 모델은 본분에 충실할 수 있다.
    • 도메인 모델이 객체의 저장이나 참조와 연관을 없애고 인프라스트럭처에 접근할 필요가 없어지는 효과
      • 예를 들면, root(=엔티티)를 리파지토리가 갖고있으면서, 어떤 메소드를 호출하면 하위 집합의 객체들을 반환해주는 형태
    • 리파지토리는 객체 조회 시, cache hit 하기도 하지만, cache miss 라면 영속성 스토리지에서 읽어오는 경우가 더 많다.
  • 리파지토리는 팩토리와 다르게 인프라스트럭처 레이어와도 연결이 포함될 수 있다.
  • 리파지토리는 CRUD를 수행할 수 있다. 여기서 Create는 아무것도 없는 상태에서 객체를 생성하는 것이 아니라, 팩토리에서 넘겨받은 객체를 추가하는 것이다.
  • Spring 프레임워크의 @Repository 도 역시 DDD 에서 따온 개념이라 한다.
    • cf) Spring 2.0 은 06년 10월에 출시

4장. 깊은 통찰을 향한 리팩터링

  • 도메인 모델링의 리팩토링
    • 초창기에 비즈니스 명세를 읽으며 명사는 클래스로 동사는 메소드로 변환하는 것부터 시작하지만 초기에는 깊이가 얕고 편협하다.
    • 코드적, 패턴을 기반으로 하는 기술적인 동기를 갖는 리팩토링과는 다르다.
      • 리팩토링은 어플리케이션의 기능에 변화를 주지 않고 코드를 더 좋게 만들기 위해 재설계하는 절차
    • 도메인에 대한 통찰, 모델이나 코드에 드러나는 표현을 가다듬기 위한 것도 리팩토링이다.
  • 정교한 모델, 좋은 모델은 도메인 전문가와 개발자들이 밀접하게 엮여서 반복적으로 리팩토링을 해야 만들어진다.
  • 도메인 전문가와 이야기할 때, 모델에 표현되지 않은 암시적 개념이 설계에서 핵심적 역할을 한다면, 이것을 모델에 추가함으로써 명시적으로 만들어야한다.
  • 암시적 개념, 누락된 개념을 발견하는 방법
    1. 언어를 주의 깊게 듣는다.
    2. 누락이 되었다면, 어떤 객체는 그것을 대체하기 위해 매우 비대해졌을 가능성이 높다.
    3. 도메인 전문가의 요구사항이 다른 요구사항과 상충되어 보이더라도, 조화시키려고 노력해야한다. 그 과정에서 중요한 개념이 도출될 수도 있다.
    4. 해당 도메인의 문헌을 활용하라
  • 명시적인 것을 만들어낼 때, 유용한 것은 아래 3가지다.
    • 제약 조건(constraint)
      • 제약사항을 별도 메소드로 분리하여 명확하게 하라
      • 읽기 쉬워지고, 제약 조건이 추가되어도 넣기 쉬워진다.
    • 처리(process)
      • 대개 절차적 코드로 표현된다. 처리를 표현하는 권장방법은 Service 를 이용하는 것이다.
    • 명세(specification)
      • 객체가 특정 기준(비즈니스 규칙)을 만족하는지 여부
      • 개별 비즈니스 규칙들은 자체적인 객체로 분리되어 캡슐화되어야 한다. 이것들을 조합해서 하나의 규칙을 표현한다.
      • Customer customer = customerRepository.findCustomer(id);
        Specification customerEligibleForRefund = new Specification(new CustomerPaidHisDebtsInThePast(), new CustomerHasNoOutstandingBalances());
        if (customerEligibleForRefund.isSatisfiedBy(customer)) { ... }
      • 코드만 읽어도 명세파악이 가능해야 한다.

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. 검색)을 구현하는데 아래 방법들을 검토해봐라
    1. 상용 솔루션(오픈소스) : 이미 완성된 것을 사용해라. 기능부족이나 버그가 있다면 기다려야할 수도 있다.
    2. 외주 : 설계와 구현을 다른 회사에 맡기고 핵심도메인에 집중하라. 통합이 불편하고 서브도메인과 소통하기 위해 인터페이스를 정의해야한다.
    3. 기존 모델
    4. 사내(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 을 이용하여 주입해준다..
  • 도메인 모델에 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같은 것도 도메인 모델에서 다뤄야할까? 에 대해선 회의적이다.
  • 이외에도, 이 책에서 다루진 않지만 도메인 이벤트에 대해서도 스프링은 지원한다. 전반적으로, Spring 진영이 DDD에게 많은 영향을 받았고, feature들을 지원하기 위해 노력해온 듯하다.
Comments