kok202
도메인 주도 설계 (3/3) 심화

2022. 4. 4. 03:55[공부] 독서/DDD (도메인 주도 설계)

http://www.yes24.com/Product/Goods/5312881

 

도메인 주도 설계 - YES24

소프트웨어의 복잡성을 다스려라!소프트웨어의 복잡성은 도메인에서 기인하고, 그러한 복잡성을 어떻게 다루느냐가 프로젝트의 성패를 좌우한다. 도메인 주도 설계(Domain-Driven Design)는 복잡한

www.yes24.com

(😛) 은 사견입니다.

 

Deep model

  • 도메인 전문가의 주요 관심사와 가장 적절한 지식을 알기 쉽게 표현하는 모델.
  • 심층적 모델과 유연한 설계를 얻는 것이 목표다.
  • 낡은 야구 글러브와 비교하면 손가락을 구부리는 지점은 유연해지는 반면 움직임이 적은 부분은 딱딱해서 손을 보호 할 수 있다.

 

리팩토링

  • 리팩토링은 도메인에 대한 새로운 통찰력을 얻었을 때 수행하거나 코드를 사용해서 모델이 표현하고자 하는 바를 명확하게 드러내고자 할 때한다.
  • 리팩토링은 엔트로피와의 싸움이며 레거시 시스템이 퇴보하는 것을 막는 최전선에 놓여있는 것이다.

 

통합된 어휘

  • 특수한 개념보다는 직관적이 통합된 어휘를 사용한다.
  • (😛 경험적으로 특수 개념을 사용하면 추후에 개념을 확장할 때 문제가 되는 경우가 많았다. ex. "? 이게 여기서 처리되면 이상한데?" 처럼)

 

도약

  • 대부분의 도약은 여러 중요 개념이 모델 내에서 명확해지고 난 후에야 나타난다.

 

Specification

  • 프레임워크를 토대로 Repository 를 일반화하는 한 가지 특별한 접근법.
  • 특별한 목적을 위해 술어와 유사한 명시적인 Value object.
  • 어떤 객체가 특정 기준을 만족하는지 판단하는 술어이다.
  • Specification 의 세가지 용도는 1. 검증 2. 선택 3. 요청 구축 이다.
  • Specification 은 생성할 결과물에 대한 요구사항을 선언할 뿐 결과물의 생성 방법은 정의하지 않는다. (😛 필요조건과 그에 따른 처리를 분리할 수 있다.)
  • 규칙 자체를 외부로 유출하지 않고 도메인 객체 내부에 유지할 수 있다.
  • 단순한 Specification 의 조합으로 복잡한 명세를 만들어 낼 수 있고 덕분에 코드의 표현력이 올라갈 수 있다.

 

유연한 설계

  • 유연성을 이유로 오버 엔저니어링을 정당화해서는 안된다.
  • 너무 과도한 추상 계층이나 간접 계층이 존재하면 오히려 유연성을 방해한다.
  • 유연성을 제공하는 소프트웨어는 단순하다. (단순하다와 쉽다는 다르다.)
  • 유연한 설계는 설계의 의도를 전달한다.
  • 실행중인 코드의 영향을 쉽게 예측할수 있게 해주므로 설계 변경의 결과 역시 쉽게 예측할 수 있게된다.

 

유연한 설계를 위한 방법 1. 의도를 드러내는 인터페이스를 개발해야한다. 

  • 개발자가 컴포넌트를 사용하기 위해 구현 세부사항을 알아야하면 캡슐화의 가치가 사라진 것이다.
  • 원래의 개발자가 아닌 다른 개발자가 구현 내용을 토대로 객체나 연산의 목적을 추측해야 한다면, 새로운 개발자는 우연에 맡긴 채 연산이나 클래스의 목적을 짐작할 가능성도 생긴다. 이는 두 개발자가 서로 의도가 어긋난 상태로 일하게 되는 것이다.
  • 클래스와 연산의 이름을 지을 땐 수행 방법에 관해서는 언급하지 말고 결과와 목적만을 표현해야한다. (😛 If 가 메소드 이름에 들어가면 별로 좋지 않은 이유.)
  • 문제를 내라. 하지만 문제를 푸는 방법을 표현해서는 안된다.

 

유연한 설계를 위한 방법 2. 부수효과가 없는 함수를 만들어야한다.

  • 부수효과의 어휘 자체를 생각해보자. 대부분의 연산은 다른 연산을 호출하고 호출된 연산은 또 다른 연산을 호출한다. 연산에 대한 호출 계층이 깊어지고 무질서하게 중첩되면서 호출 결과를 온전히 예측하기 어려워진다. 이렇게 발생한 효과는 시스템에 대한 부수적인 효과에 해당한다. 
  • (😛 부수효과란 부작용이 아니다. 인터페이스만보고 파악했을 때 의도로 들어나지 않는 모든 효과를 말하는 것이다.)

 

명령과 질의 

  • 질의는 변수에 저장된 값에 접근하거나 정보를 얻는 연산을 의미한다.
  • 명령은 변수의 값을 변경하는 연산을 의미한다.
  • 대부분의 소프트웨어 시스템에서 명령을 사용하지 않기는 어렵다.
  • 명령과 질의를 엄격하게 분리된 연산으로 유지한다.
  • 명령은 도메인 데이터를 반환하지 않고 가능한 단순하게 유지한다.
  • 연산의 결과로 얻는 값은 새로운 VO 를 생성해서 반환한다.
  • VO 는 불변 객체이며 오직 VO를 생성할 때만 호출되는 초기화 연산을 제외한 모든 연산이 함수라는 장점이있다. 따라서 전체 책임을 VO로 옮기는 식으로 부수효과를 제거할 수 있다.
  • 가능한 프로그램의 로직을 관찰 가능한 부수효과 없이 결과를 반환하는 함수 안에 작성하라.

 

유연한 설계를 위한 방법 3. Assertion

  • 개발자가 각기 호출되는 명령과 결과를 이해해야한다면 캡슐화가 무용지물이 된다.
  • 상위 클래스를 상속하는 두 하위 클래스가 서로 다른 부수효과를 일으킬 수 있다면 추상화와 다형성도 무용지물이 된다.
  • 많은 객체 지향 언어에서 직접적으로 Assertion 을 지원하고 있지는 않지만, 자동화된 단위 테스트를 이용해서 이를 보완할 수 있다. (😛 개인적으로 이 부분이 TDD 의 주요 가치중 하나라고 생각.)

 

유연한 설계를 위한 방법 4. 개념적 윤곽

  • 모놀리식 구조에 묻혀있을 경우 각 요소의 기능이 중복된다. 반대로 클래스와 메소드를 너무 잘게 나누면 클라이언트 객체가 무의미하게 복잡해진다.
  • 변경을 분리하기 위한 패턴을 명확하게 표현하는 개념적 윤곽을 찾을 수 이어야한다.

 

유연한 설계를 위한 방법 5. 연산의 닫힘

  • 의존성은 늘 존재하겠지만 근본 개념을 구성하는 의존성 자체는 나쁜 것이 아니다.
  • 적절한 위치에 반환 타입과 인자타입이 동일한 연산을 정의하라.
  • 도메인 내에서 Entity 의 생명 주기는 매우 중요하므로 연산의 결과로 새로운 Entity 를 생성해서 반환할 수 는 없다.
  • 일반적으로 Entity 는 어떤 계산의 수행 결과를 표현하는 개념이 아니다.

 

유연한 설계를 위한 방법 6. Declarative design

  • 선언적 설계는 일반적으로 일종의 실행 가능한 명세다.
  • 프로그램 전체 혹은 프로그램의 일부를 작성하는 방식을 의미한다.
  • 특성을 매우 정확하게 기술함으로써 소프트웨어를 제어한다.

 

Strategy 패턴

  • 여러 종류의 프로세스중 하나를 선택해야할 경우 쓸 수있다.
  • 프로세스에서 변화하는 부분을 별도의 전략 객체로 분리해서 모델에 표현한다.
  • (😛 ex. 경로 찾기에 DFS, BFS, 다익스트라 등을 사용할 수 있다.)

 

Composite 패턴

  • 계층 구조를 재귀적으로 탐색하는 작업에 사용할 수 있다.
  • Composite 내부에 포함된 모든 구성요소를 포괄하는 추상 타입을 정의하고 이를 재귀적으로 표현한다.

 

Facade 패턴

  • 기저 시스템의 모델을 변경하지 않는다. 
  • 다른 시스템의 모델에 따라 엄격하게 작성된다.
  • 한쪽 모델에서 다른 모델로 번역하고 상호작용하기 어려운 하위 시스템의 인터페이스를 동시에 다루는 역할을 한다.

 

Adapter 패턴

  • 행위를 구현하는 측에서 이해한 것과 다른 프로토콜을 클라이언트에서 사용하게해주는 래퍼.

 

Context

  • 설계 원칙 가운데 분명히 드러나지는 않지만 실제로 가장 근본적인 원칙
  • 성공적인 모델은 규모와 상관없이 모순되거나 정의가 겹치지 않고 처음부터 끝까지 논리적인 일관성을 지녀야한다.

 

Context Map

  • 프로젝트의 컨텍스트 간의 관계를 전체적인 개관을 제공해주는 역할.
  • 프로젝트 관리와 소프트웨어 설계 영역사이에 걸쳐있는 개념이다.
  • 의사소통을 위해 컨텍스트간의 번역에 대한 윤곽을 명확하게 표현하고 컨텍스트 간에 공유 해야하는 정보를 강조함으로써 모델간의 경계 지점을 서술한다.
  • 항상 현재 상태 그대로 상황을 표현할 수 잇어야한다.
  • 유사성이 눈에 띈다면 패턴 이름을 사용하고 싶어질수도 있지만 이를 강요해선 안된다. Context map 은 발견한 관계만을 서술해야한다.
  • 실제로 변경이 완료되기 전까지 Map 을 수정해서는 안 된다는 사실을 명심한다.
  • (😛 팀 간에 합심해서 유지해야할 유일한 객체)
  • Context map 을 구성하는데 아래와 같은 전략이 존재한다. 번호가 클수록 타협을 본것이다.
  • Context map 에서 동종 컨텍스트를 모으는 수단으로 Shared kernel 을 활용할 수 있다. (😛 = 중복 제거와 시스템 통합)
  • Context map 에서 컨텍스트에 역할을 부여하는데 Customer / Supplier team 을 활용할 수 있다. (😛 = FE / BE team)
  • Context map 에서 다수의 클라이언트를 지원하는데 Open host service 를 활용할 수 있다.
  • Context map 에서 일방적으로 겹치는 방식에는 Conformist 를 활용할 수 있다.
  • Context map 에서 번역이나 일방적인 보호를 하는데  Anticorruption layer 를 활용할 수 있다. (😛 = Facade, Adapter)
  • Context map 에서 팀의 자유도를 보장하는데 Separate ways 를 활용할 수 있다.
  • Downstream 팀이 Upstream팀에 대한 고객 역할을 맡게한다. 요구사항에 대한 작업을 협상하고 예산을 책정하고 모든 일정과 약송을 이행할 수 있게 한다.
  • Upstream 팀은 테스트 스위트를 추가해서 CI 의 일부로 실행될 수 있게한다. 이를 통해 부수효과를 두려워하지 않고 자유로이 코드를 변경할 수 있다.
  • Anticorruption layer 는 Bounded context 를 연결하는 수단이며 이를 토대로 마찰을 줄일 수 있다.
  • Seperate ways 는 관계가 필수적인 것이 아니고 통합으로 이룰 수 있는 혜택이 적을 때 관계를 끊는 것이다. 

 

 

Bounded context

  • 모델의 적용 가능성의 범위를 정의한다.
  • 이 경계 내에서 모델을 엄격하게 일관된 상태로 유지하고 경계 바깥의 이슈 때문에 초점이 흐려지거나 혼란스러워 지는 것을 방지해야한다.
  • Bounded context 는 Module 이 아니다.
  • 협업하는 팀이 하나의 단일화된 시스템을 만들어 내는 것이 목표라할지라도 동일한 Bounded context 에서 일하고 있는게 아닐 수 있다. 따라서 두 팀이 같은 context 안에 있지 않다면 어느정도의 변화가 생기기 전까지 코드를 공유하려 해서는 안 된다.
    협업할 때 Bounded context 를 잘못 이해할 수 있는 케이스로는 크게 두가지가 있다. 1. 중복된 개념 2. 허위 동족 언어이다.
  • 중복된 개념은 실제로 같은 개념을 나타내는 두개의 모델이 존재하는 경우다. 이 같은 경우 정보가 변경되면 두 군데를 갱신해줘야한다.
  • 허위 동족 언어는 (😛 = 동상이몽) 같은 언어를 사용하는 두 사람이 같은 이야기를 하고 있다 생각하지만 실제로 그렇지 않은 경우를 말한다.
  • Bounded context 간에 코드 재사용은 위험하므로 피한다.

 

Continuous integration

  • 프로세스를 토대로 모델의 단일화를 유지할 수 있게한다.
  • CI 의 가장 근본적인 목적은 지속적으로 Ubiquitouse language 를 다듬는 것이다.
  • CI 는 크게 모델 개념의 통합과 구현 수준의 통합이라는 두 가지 레벨에서 작용한다.
  • CI 를 통해 모델의 균열을 빠르게 파악할 수 있어야한다.
  • 단편화가 발생했다는 사실을 빠르게 알려줄 수 있는 자동화된 테스트와 모든 코드나 산출물을 빈번하게 병합할 수 있는 프로세스를 수립해야한다.

 

Distillation

  • 혼란을 줄이고 적절히 주의를 집중시키는 방식
  • 핵심적인 부분에 주의를 집중한다.
  • 다른 구성요소는 보조적인 역할을 수행하도록 한다.
  • 혼합된 요소를 분리하고 본질을 좀 더 값지고 유용한 형태로 뽑아내는 과정.
  • 모델은 지식의 정수를 출한 것이다.

 

Distillation 기법

  1. Domain vision statement 는 최소한의 투자로 기본 개념과 가치를 전달한다. Core domain 을 짧게 기술하여 그것에 기대하는 가치를 가치 제안에 작성하여 부각하라.
  2. Highlight core 는 의사소통을 향상시키고 의사 결정을 내리는데 도움이 되는 동시에 설계를 거의 수정하지 않아도 된다. 모델의 주요 저장소 안에 있는 Core domain 의 구성요소에 대해 역할을 설명하려하지 말고 표시하라. 개발자가 힘들이지 않고도 Core 의 안과 밖을 알 수 있게 하라.
  3. Generic subdomain 은 리팩터링과 재패키지화를 거쳐 명확하게 구분하는 것이다. 프로젝트를 위한 것이 아닌 응집력 있는 하위 도메인을 식별하라. 하위 도메인에서 일반화 된 모델 요소를 추출해서 별도 Module 에 배치하라.
  4. Cohesive mechanism 은 용도가 다양하고 의미전달이 용이하며 유연한 설계를 통해 캡슐화 할 수 있도록 도와준다. 메카니즘을 캡슐화 하는 것이 객체 지향 설계의 표준 원칙이다. 의도를 드러내는 이름이 지정된 메서드에 복잡한 알고리즘을 숨기면 "무엇"과 "어떻게"를 분리할 수 있다.
  5. Segregated core 는 재패키지화하여 Core 를 코드상에서도 바로 볼 수 있게 한다.
  6. Abstract core 는 가장 근본적인 개념과 관계를 순수한 형태로 표현한 것이다. 추상화를 분리하면 더 작으면서도 응집력 있는 Core domain 을 추출할 수 있다. 동시에 Module 간의 결합도도 끊을 수 있다.

 

Generic subdomain VS Cohesive mechanism

Generic subdomain 과 Cohesive mechanism 은 둘 다 core domain 의 부담을 더는데 목적을 두고 있어서 헷갈릴 수 있다. 이를 비교하자면 Generic subdomain 은 중요도에 따라 덜 특화된 것으로 분리한 것일 뿐이지 어쨋거나 domain 이라는 점이며, Cohesive mechanism 은 도메인은 아니라는 것이다. 모델의 제안을 처리하는 것 뿐이다. Generic subdomain 을 추출하면 혼란스러움을 줄일 수 있고 Cohesive mechanism 은 복잡한 연산을 캡슐화하는데 기여한다.

(😛 이를 바탕으로 프로젝트를 리팩토링해도 괜찮을 듯)

 

Unification

모델의 가장 근본적인 요구사항은 모델은 내적으로 일관성을 유지하고 모델의 용어는 언제나 의미가 동일하며 모델엔느 어떠한 모순된 규칙도 없어야한다는 것이다. 이러한 내적 일관성을 Unification 이라고한다. 대규모 시스템의 도메인 모델을 완전하게 단일화하는 것은 타당하지 않거나 비용 대비 효과적이지 않을 것이다.

 

대규모 구조

  • 넓은 시각으로 시스템에 관해 토의하고 이해하게끔 돕는 언어를 의미한다.
  • 고수준 개념이나 규칙, 또는 둘 모두는 전체 시스템에 대한 설계 패턴을 확립한다.
  • Context map 과 달리 선택사항이다.
  • 비용 편익 측면을 고려하여 유리하다 판단되면 시스템에 적용한다.
  • 구조가 바뀔 때마다 전체 시스템은 새로운 질서를 따르도록 바뀌어야 한다.
  • 전체 시스템을 포괄하고 각 부분의 책임을 자세히 알지 못해도 전체적인 관점에서 해당 부분의 위치를 어느 정도 이해하는 데 도움을 주는 규칙 또는 규칙과 관계의 패턴을 고안하라.
  • 문제는 참고할 규칙이 존재하느냐가 아니라 그러한 규칙이 얼마나 엄격하고 어디에서 유래했느냐다. 설계를 관장하는 규칙이 정말로 상황에 맞아 떨어진다면 방해되지 않고 실질적으로 도움이 되는 방향으로 개발이 될 뿐 아니라 일관성도 제공한다.

 

대규모 구조를 구성하는 기법

  1. Model driven design 에서 구조를 발견 / 정제 하는데 Evolving order 를 사용할 수 있다.
  2. Model driven design 에서 사고를 이끌어내는 수단으로 System metaphor 를 사용할 수 있다. 시스템을 구체적으로 비유할 수 있는 은유가 있다면 은유를 대규모 구조로 채택하고 이를 중심으로 설계를 구성하라. 그리고 이를 Ubiquitous language 로 흡수하라.
  3. Model driven design 에서 계층화의 대상으로 Responsibility layer 를 사용할 수 있다. (계층화)
  4. Model driven design 에서 개별 기여도를 분리하는데 Pluggable component framework 를 사용할 수 있다.
  5. Model driven design 에서 인공적인 행위를 분리하는 대상으로 Knowledge level 를 사용할 수 있다. 이는 다른 객체 집단이 어떻게 행동해야 하는지를 기술하는 객체 그룹이다. 더 넓은 범위의 규칙으로 제약이 구성되기 전까지, 모델의 특정 부분을 사용자 손에 맡겨야할 때 생기는 문제를 해결한다. Relfection 과 다르게 도메인에 초점을 맞추는 방식이며 완전한 보편성을 달성하려고 애쓰지 않는다.