kok202
도메인 주도 설계 (2/3) 기본

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

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

 

도메인 주도 설계 - YES24

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

www.yes24.com

(😛) 은 사견입니다.

 

모델

  • 모델은 지식의 정수만 뽑아낸 것이다.
  • 모델은 프로젝트에 참여한 사람들의 머릿속에 축적된 개념을 모아 놓은 것이다.

 

모델이 사용자에게 중요한 이유

IE 사용자는 즐겨찾기를 세션간에 지속되는 웹사이트 목록이라고 생각한다. 그러나 구현 레벨에서는 즐겨찾기를 URL 이 저장된 파일로 간주하고 해당 파일의 이름을 즐겨찾기 목록에 넣는다. 즐겨찾기 이름에 특수문자를 넣는다 해보자. 그러면 "파일 이름은 특수 문자를 포함할 수 없습니다." 라고 오류 메시지가 나올 것이다. 사용자는 즐겨찾기를 추가하는데 무슨 파일 이름을 말하는 거지? 라고 생각할 수 있다. UI 에 도메인 모델이 아닌 다른 모델의 환영을 만들려는 시도는 완벽하지 않을 경우 혼란만 초래할 수 있다.

 

실천적 모델러

  • 모델을 설계하는 사람이라해서 구현이나 개발을 신경쓰지 않아도 된다는 의미는 아니다.
  • 모델이 동작하게 만드는 법을 모른다면, 모델이 구현을 뒷받침하고 핵심 도메인 지식을 추상화 한다는 Model driven design 의 기본적인 가치는 사라지고 실용적이지 못한 모델이 될 것이다. 모델러를 포함한 모델에 기여하는 모든 기술자는 코드를 접하는데 어느정도 시간을 투자해야 한다.
  • 모델링과 프로그래밍을 뚜렷하게 구분하는 것은 별의미가 없지만, 규모가 큰 프로젝트에서는 설계와 모델링을 조율하고 어려운 의사결정을 내리는 것을 돕는 기술적 측면의 리더가 필요하다.

 

도메인

  • 사용자가 프로그램을 사용하는 대상 영역이 소프트웨어의 도메인이다.
  • 어떤 도메인이냐에 따라 다양한 범주의 개념이 존재할 수 있다.
  • 소프트웨어 본질은 해당 소프트웨어의 사용자를 위해 도메인에 관련된 문제를 해결하는 능력에 있다.
  • 효과적인 모델링을 위해선 모델을 기반으로 하는 언어를 정제해야하며, 풍부한 지식이 담긴 모델을 개발해야한다. 객체는 행위를 지니고 규칙을 이행한다.

 

구현

  • 업무 전문가가 개발자의 도움 없이도 코드를 읽고 규칙을 검증할 수 있는 형태로 코드가 작성되어야한다.
  • 코드와 요구사항을 연관 지을 수 있어야한다.
  • 설계 혹은 설계의 주된 부분이 도메인 모델과 대응하지 않는다면 그 모델은 그다지 가치가 없으며 소프트웨어의 정확도도 의심스럽다.
  • 모델이 구현에 대해 비현실적으로 보인다면 새로운 모델을 찾아내야한다.
  • 프로그래머에게 객체는 실제로 메모리에 존재하고 다른 객체와 연관관계를 맺으며 여러 클래스로 조직되고 메시지 전달로 이용 가능한 행위를 제공하는 것을 의미한다.
  • 객체 설계에서의 진정한 도약은 코드가 모델의 개념을 표현할 때 나온다.
  • 개발자는 설계를 어렵계 만들고 모호함과 불일치를 찾아내는 데 촉각을 곤두세워야 한다.
  • 종종 프로그램을 개발하다보면 부가적인 업무 로직은 UI 위젯과 데이터 베이스 스크립트에 들어간다. 단기적으로 이렇게 하는 것이 뭔가를 동작하게 하기 위해 가장 쉬운 방법이기 때문이다. 하지만 이는 Bad case 이다. 도메인과 관련된 코드가 상당한 양의 도메인과 관련 없는 다른 코드를 통해 확산될 경우 도메인에 관련된 코드를 확인하고 추론하기 굉장히 어려워진다.

 

지식

  • 추상화를 해야 지식을 축적할 수 있다.
  • 지식은 단순한 명사 찾기가 아니며 도메인에 관련된 업무 활동과 규칙도 중요하다.

 

공통된 언어 (Ubiquitous language)

  • 개발자와 도메인 전문가 사이에 합의된 언어를 공통 언어(Ubiquitous language)라고 한다.
  • 개발자와 도메인 전문가 사이에 언어적 분열이 일어나서는 안된다.
  • 수준 높은 도메인 전문가도 모델을 이해하지 못한다면 모델이 잘못된 것이다.
  • 다이어그램과 문서에서 동일한 언어를 사용하여 설명할 수 있어야한다.
  • 하나의 모델이 구현 설계, 의사소통의 기초가 되어야한다.
  • 설명을 위한 모델이 꼭 객체 모델일 필요는 없다. 오히려 그렇지 않는게 좋다.
  • 설명을 위한 모델과 설계를 주도하는 모델은 차이가 있다.

 

문서와 다이어그램

  • 모델은 다이어그램이 아니다.
  • UML 은 모델의 가장 중요한 두가치 측면, 개념의 의미와 모델내 객체의 행위를 전달할 수 없다.
  • 잘 작성된 코드는 UML 만큼 표현력이 있다.
  • 문서는 코드가 이미 잘하고 있는 것을 하려해서는 안된다. 코드는 이미 세부사항을 충족한다. 코드는 프로그램의 행위를 정확하게 규정한 명세이다.
  • 문서는 유효한 상태를 유지하고 최신 내용을 담고 있어야한다.
  • 하나의 모델이 구현 설계, 의사소통의 기초가 되어야한다.
  • 설명을 위한 모델이 꼭 객체 모델일 필요는 없다. 오히려 그렇지 않는게 좋다.
  • 설명을 위한 모델과 설계를 주도하는 모델은 차이가 있다.
  • 다이어그램이 도메인의 핵심 개념을 밝히는데 도움이 된다면 클래스 다이어그램일 필요는 없다.

 

Layered architecture

  1. 사용자 인터페이스(Controller or FE): 사용자에게 정보를 보여주고 사용자의 명령을 해석하는 일을 책임진다.
  2. 응용 계층(Service): 소프트웨어가 수행할 작업을 정의하고 표현력있는 도메인 객체가 문제를 해결하게 하는 레이어, 다른 시스템의 응용 계층과 상호작용하는 데 필요한 것들. 이 계층은 최대한 얇게 유지되어야한다. 업무 규칙이나 지식이 포함되지 않아야한다. 오직 작업을 조정하고 아래에 위치한 계층에 도메인 객체의 협력자에게 작업을 위임한다.
  3. 도메인 계층(모델): 업무 개념과 업무 상황에 관한 정보, 업무 규칙을 표현하는 일을 책임진다. 상태 저장과 관련된 기술적인 세부사항은 인프라스트럭쳐에 위임한다. 이 계층이 소프트웨어의 핵심이다.
  4. 인프라스트럭쳐 계층(JPA): 일반화된 기술적 기능을 제공하는 레이어.
  • 계층화의 가치는 각 계층에서 컴퓨터 프로그램의 특정 측면만을 전문적으로 다룬다는 것에 있다.
  • 이러한 전문화를 토대로 각 측면에서는 더욱 응집력 있는 설계가 가능해진다. 이로써 설계를 훨씬 더 쉽게 이해할 수 있다.
  • 하위 수준의 객체가 상위 수준의 객체와 소통해야할 경우. Callback 이나 Observer 패턴처럼 계층 간에 관계를 맺어주는 아키텍처 패턴을 활용할 수 있다.
  • 보통 인프라스트럭처 계층에서는 도메인 계층에서 어떤활동이 일어나게 하지 않는다. 인프라스트럭처 계층은 도메인 계층의 아래에 있으므로 해당 인프라 스트럭처 계층이 보조하는 도메인의 구체적인 지식을 가져서는 안된다. 
    (😛 업무를 하면서 자꾸 DB Entity 에 도메인 로직을 넣고 싶었었는데, 현업에서 사용하는 레이어 구조가 일반적이지 않아서 자꾸 그랬던 듯)
  • (😛 사용자쪽에 가까울 수록 downstream(FE), 사용자쪽에게 멀수록 upstream(BE) 이라고 이해하면된다.)
  • 컴포넌트가 바로 아래 있는 계층만 접근할 수 있는게 아니라 하위 모든 계층에 접근을 허용하는 방식을 Relaxed layered system 이라고 부른다.

 

Smart UI

  • (😛 Smart controller 라고 이해해도 될 듯)
  • 안티 패턴
  • 도메인 주도 설계 접근법과는 양립 할 수 없는 패턴
  • 이 책의 진리는 도메인과 UI를 분리해야한다는 것이다.

 

Transaction script

  • Smart UI 와 layered architecture 에 있는 패턴
  • 애플리케이션으로 부터 UI를 분리하지만 객체에 모델을 제공하지 않는다.
  • 아키텍처에 응집력있는 도메인 설계가 시스템의 다른 부분과 느슨하게 결합될 수 있게 도메인 관련 코드를 격리하면 도메인 주도 설계로 발전할 수 있다.
  • (😛 현재 우리 프로젝트가 위치한 단계인 듯, TDD 를 하면서 점점 DDD 로 발전해나가고 있다.)

 

연관관계

  • 현실세계는 양방향 참조로 나타난다. 하지만 이러한 일반적인 형태의 인관관계는 구현과 유지보수를 복잡하게 하며 해당 관계의 특성에 관해서는 거의 전해주는 바가 없다.
  • 가능한 한 관계를 제약하는 것이 중요하다.
  • 양방향 연관관계는 두객체가 모두 있어야만 이해할 수 있다.
  • 애플리케이션 요구사항에 두 방향 모두 탐색해야 한다는 요건이 없을 경우 탐색 방향을 추가하면 상호 의존성이 줄고 설계가 단순해진다.

 

Entity

  • Entity 의 근본적인 개념은 객체의 생명주기 내내 이어지는 추상적인 연속성이다.
  • 추상적인 연속성은 여러 형태를 거쳐 전달된다.
  • 어떤 객체를 일차적으로 해당 객체의 식별성으로 정의할 경우 그 객체를 Entity 라고 한다.
  • 모델 Entity 는 자바의 Entity bean 과는 다르다. 자바의 Entity bean 은 Entity 구현을 위한 프레임워크를 목표로 하였으나 그렇게 되지 못했다.
  • 객체의 일치 여부를 식별자가 아닌 속성으로 판단하는 요구사항이 생기는 것을 조심하라.
  • 식별 수단은 외부에서 가져오거나 시스템에서 자체적으로 만들어내는 임의적인 값일 수도 있으나 모델에서 식별성을 구분하는 방법과 일치해야한다.
  • Entity 의 가장 기본적인 책임은 객체의 행위가 명확하고 예측 가능해질 수 있게 연속성을 확립하는 것이다.

 

Value object

  • Value object 는 불변적으로 다뤄라.
  • Value object 에는 아무런 식별성도 부여하지 말고 Entity 를 유지하는 데 필요한 설계상의 복잡성을 피하라.
  • 불변성은 공유와 객체 참조 전달을 안전하게 만들어 구현을 상당히 단순하게 만들어준다.
  • 동일한 데이터를 여러개 사본에 저장하는 기법을 역정규화 라고 한다. 중복을 허용하는 대신 속도를 올려준다.

 

Service

  • Entity 나 Value object 에 서 찾지 못하는 중요한 도메인 연산이 있다.
  • 도메인들 중 일부는 본질적으로 사물이 아닌 활동이나 행동인데, 객체 지향 패러다임에 맞춰 연산도 객체에 잘 어울리게끔 노력해야한다.
  • 오늘날 흔히 하는 실수는 행위를 적절한 객체로 다듬는 것을 너무나도 쉽게 포기해서 점점 절차적 프로그래밍에 빠지는 것이다.
  • 종종 서비스는 특정 연산을 수행하는 것 이상의 의미가 없는 모델 객체로 가장해서 나타나기도한다. 이러한 현상의 초기 증상은 이름끝에 Manager 같은 것이 붙는 것이다.
  • 서비스라는 이름은 다른 객체와의 관계를 강조한다.
  • 서비스는 주로 활동으로 이름을 지어야한다.
  • 그리고 연산의 명칭은 Ubiquitous language 에서 유래되거나 도입되어야한다.
  • 서비스는 여러 서비스가 있을 수 있다. (도메인 Service, 응용 Service, 인프라스트럭처 Service)

 

Module

  • Module 간에는 결합도가 낮아야하고 응집도가 높아야한다.
  • Module 을 쪼개는 것은 코드가 아닌 개념이다.
  • Module 을 선택할 때 초기에 한 불가피한 실수로 결합도가 높아지면 리팩터링을 수행하기 어려워진다.
  • Module은 단순하게 유지하는 편이 좋다. 기술 환경에 필수적이거나 실질적으로 개발에 도움이 되는 최소한의 분할 규칙만 선택한다.

 

객체

  • 객체의 가장 기본적인 개념중 하나는 데이터와 해당 데이터를 이용하여 연산하는 수행 로직을 캡슐화하는 것이다.
  • 다수의 팀에서 객체 패러다임을 선택하는 이유 기술적인 이유 때문도, 객체 때문도 아니다. 객체 모델링이 복잡함과 단순함의 절묘한 조화를 이룬다는 것이다.
  • 객체를 다루는 방법들
    • Aggregate: 복잡한 객체
    • Factory: 복잡한 객체 생성을 추상화
    • Repository: 객체들을 영속화

 

Aggregate

  • 데이터 변경의 단위로 다루는 연관 객체의 묶음
  • Aggregate에는 루트와 경계가 있다.
  • 루트는 단 하나만 존재한다.
  • 루트는 Aggregate 에 포함된 특정 Entity를 가리킨다.
  • 루트 Entity 는 전역 식별성을 지닌다.
  • 루트 이외의 Entity 는 지역 식별성을 지니며 지역 식별성은 Aggregate 내에서만 구분되면 된다.
  • Aggregate 의 경계 밖에 위치한 객체는 루트 Entity의 컨텍스트 말고는 Aggregate 내부를 볼 수 없다.
  • Aggregate 의 내부 구성요소에대한 참조는 단일 연산에서만 사용할 목적에 한해 외부로 전달되어야한다.
  • 루트를 경유하지 않고 내부 변경을 할 수 없도록 하라.
  • 하나의 Aggregate 에 적용된 불변식은 트랜잭션이 완료될 때 이행된다.
  • DB에 질의 할 경우 Aggregate 의 루트만 직접적으로 획득할 수 있다.

 

Factory

  • 다른 클래스의 생성자 내에서 생성자를 호출하지 않도록 하라. 생성자는 극도로 단순해야한다.
  • Aggregate 처럼 복잡한 조립과정을 거쳐 만들어지는 것을 생성하려면 Factory 가 필요하다.



Repository

  • 실질적으로 직접 접근해야하는 Aggregate 의 루트에 대해서만 Repository 를 제공하고 모든 객체 저장과 접근은 Repository 에 위임해서 모델에 집중할 수 있도록하라.
  • 타입을 추상화하는데 사용하라. 각 클래스마다 하나의 Repository 가 필요한 것은 아니다. 타입은 인터페이스가 될 수도 있다.
  • 클라이언트와 분리를 하라.
  • 트랜잭션 제어는 클라이언트에 둬라. Repository 에서는 아무것도 커밋하지 않을 것이다.
  • Factory 가 새로운 객체를 만들어 낸다면 Repository 는 기존 객체를 찾아낸다.
  • Factory 에서 영속화에 대한 책임을 빼내는 것은 명확한 분리에도 도움이 된다.
  • 사람들은 종종 Upsert 기능을 요구하곤하는데, Facotry 와 Repository 를 결합하게 만드는 경우이기 때문에 이 기능 사용은 가급적 자제해야한다. 이 기능은 기껏해야 조금 더 편리할 뿐이다. (😛 그럼에도 개인적으론 실용주의적 관점에서 필요하다고 생각한다.)
  • 모델과 데이터베이스를 의식적으로 분리해야 깔끔한 데이터베이스 스키마가 만들어질 수 있다.

 

기타

  • 소프트웨어 설계는 복잡성과의 끊임없는 전투다.
  • 우리는 특별하게 다뤄야할 부분과 그렇지 않은 부분을 구분해야한다.
  • Entity 와 Value object가 관례적인 객체 모델의 주된 요소이지만 실용주의적 관점에서 Service 를 사용할 수 있다.
  • Model 내의 관계망을 제한하는 것이 뼈저리게 중요하다.
  • Model driven design 을 굳이 포기할 필요가 없으며 그것을 유지하기위해 노력하는 것은 충분한 가치가 있다. 
  • Value 를 전역적으로 검색하는 것은 종종 무의미하다.
  • Value 의 속성을 이용해 Value를 찾는 것이 해당 속성을 지닌 새로운 인스턴스를 생성하는 것과 마찬가지이기 때문이다.
  • 순환 참조는 논리적으로 여러 도메인에 존재하며 간혹 설계에도 필요하지만 순환 참조를 유지하는 데는 신중을 기해야 한다.
  • Enterprise segment 는 업무 분야라는 클래스로 업무를 분할하는 방법을 정의한 차원의 집합이다.