[책 내용 정리] 도메인 주도 설계

June·2021년 11월 1일
0

책 요약 및 정리

목록 보기
5/6
post-thumbnail

1부. 동작하는 도메인 모델 만들기

사용자가 프로그램을 사용하는 대상이 바로 해당 소프트웨어의 도메인이다.

1장. 지식 탐구

모든 구성원이 함께 모델을 면밀히 만들어 나가면 팀 구성원 간의 상호작용은 그 양상을 달리한다. 팀 구성원은 더욱 유능한 지식 탐구자로 거듭난다. 분석가와 프로그래머 모두가 모델을 만들어 나가므로 모델은 명료하게 조직화되고 추상화된다.

2장. 의사소통과 언어 사용

모델 기반 의사소통은 토압 모델링 언어(Uuified Modeling Language, UML)상의 다이어그램으로 한정돼서는 안되나. 모델을 가장 효과적으로 사용하려면 모든 의사소통 수단에 스며들 필요가 있다.

Ubiquitous Language

모델을 언어의 근간으로 사용하라. 팀 내 모든 의사 소통과 코드에서 해당 언어를 끊임없이 적용하는데 전념하라. 다이어그램과 문서에서, 그리고 특히 말할 때 동일한 언어를 사용하라.

UQIAUITOUS LANGUAGE의 변화가 곧 모델의 변화라는 것을 인식하라.

3장. 모델과 구현의 연계

설계 혹은 설계의 주된 부분이 도메인 모델과 대응하지 않는다면 그 모델은 그다지 가치가 없으며 소프트웨어의 정확함도 의심스러워진다. 동시에 모델과 설계 기능 사이의 복잡한 대응은 이해하기 힘들고, 실제로 설계가 변경되면 유지보수가 불가능해진다. 분석과 설계가 치명적으로 동떨어지고, 그에 따라 각자의 활동에서 얻은 통찰력이 서로에게 전해지지 않는다.

코드를 작성하는 사람이 모델에 책임을 느끼지 못하거나 애플리케이션을 대상으로 모델이 동작하게 만드는 법을 모른다면 그 모델은 소프트웨어와 무관해진다. 코드의 변경이 곧 모델의 변경이라는 점을 개발자가 인지하지 못하면 리팩터링은 모델을 강화하기보다는 약화시킬 것이다.

2부

모델 주도 설계의 기본 요소

4. 도메인의 격리

도메인에 관련된 코드가 상당한 양의 도메인과 관련이 없는 다른 코드를 통해 널리 확산될 경우 도메인에 관련된 코드를 확인하고 추론하기가 굉장히 힘들어진다.

사용자 인터페이스(또는 표현 계층): 사용자게 정보를 보여주고 사용자의 명령을 해석하는 일을 책임진다. 간혹 사람이 아닌 다른 컴퓨터 시스템이 외부 행위자가 되기도 한다.

응용 계층: 소프트웨어가 수행할 작업을 정의하고 표현력 있는 도메인 객체가 문제를 해결하게 한다. 이 계층에서 책임지는 작업은 업무상 중요하거나 다른 시스템의 응용 계층과 상호작용하는데 필요한 것들이다.
이 계층은 얇게 유지된다. 여기에는 업무 규칙이나 지식이 포함되지 않으며, 오직 작업을 조정하고 아래에 위치한 계층에 포함된 도메인 객체의 협력자에게 작업을 위임한다. 응용 계층에서는 업무 상황을 반영하는 상태가 없지만 사용자나 프로그램의 작업에 대한 진행사항을 반영하는 상태를 가질 수는 있다.

도메인 계층(또는 모델 계층): 업무 개념과 업무 상황에 관한 정보, 업무 규칙을 표현하는 일을 책임진다. 이 계층에서는 업무 상황을 반영하는 상태를 제어하고 사용하며, 그와 같은 상태 저장과 관련된 기술적인 세부사항은 인프라스트럭처에 위임한다. 이 계층은 업무용 소프트웨어의 핵심이다.

인프라스트럭처 계층: 상위 계층을 지원하는 일반화된 기술적 기능을 제공한다. 이러한 기능에는 애플리케이션에 대한 메시지 전송, 도메인 영속화, UI에 위젯을 그리는 것 등이 있다. 또한 인프러스트럭처 계층은 아키텍처 프레임워크를 통해 네 가지 계층에 대한 상호작용 패턴을 지원할 수도 있다.

주의해야 할 것은 응용 계층이 아닌 도메인 계층에서 주요 업무 규칙을 책임지고 있다는 것이다.

5. 소프트웨어에서 표현되는 모델

Entity

어떤 객체를 일차적으로 해당 객체의 식별성으로 정의할 경우 그 객체를 ENTITY라 한다. ENTITY에는 모델링과 설계상 특수한 고려사항이 포함돼 있다. 엔티티는 자신의 생명주기 동안 형태와 내용이 급격하게 바뀔 수도 있지만 연속성은 유지해야 한다. 또한 사실상 ENTITY를 추적하려면 ENTITY에 식별성이 정의돼 있어야 한다. 엔티티의 클래스 정의와 책임, 속성, 연관관계는 엔티티에 포함된 특정 속성보다는 엔티티의 정체성에 초점을 맞춰야 한다. 엔티티가 그렇게까지 급격하게 변형되지 않거나 생명주기가 복잡하지 않더라도 의미에 따라 엔티티를 분류한다면 모델이 더욱 투명해지고 구현은 견고해질 것이다.

Value Object

개념적 식별성을 갖지 않으면서 도메인의 서술적 측면을 나타내는 객체를 VALUE OBJECT라고 한다.

모델에 포함된 어떤 요소의 속성에만 관심이 있다면 그것을 값 객체로 분류하라. 값 객체에서 해당 값 객체가전하는 속성의 의미를 표현하게 하고 관련 기능을 부여하라. 또한 값 객체는 불변성으로 다뤄라. 값 객체에는 아무런 식별성도 부여하지 말고 엔티티를 유지하는데 필요한설계상의 복잡성을 피하라.

서비스

도메인의 개념 가운데 객체로는 모델에 어울리지 않는 것이 있다. 필요한 도메인 기능을 엔티티나 value에서 억지로 맡게 하면 모델에 기반을 둔 객체의 정의가 왜곡되거나, 또는 무의미하고 인위적으로 만들어진 객체가 추가될 것이다.

도메인의 중대한 프로세스나 변화 과정이 ENTITY나 VALUE OBJECT의 고유한 책임이 아니라면 연산을 SERVICE로 선언되는 독립 인터페이스를 모델에 추가하라. 모델의 언어라는 측면에서 인터페이스를 정의하고 연산의 이름을 UBIQUITOUS LANGUAGE의 일부가 되게끔 구성하라. SERVICE는 상태를 갖지 않게 만들어라.

모듈

모듈간에는 결합도가 낮아야 하고 모듈의 내부는 응집도가 높아야 한다.

도메인 주도 설계의 다른 모든 것들과 마찬가지로 모듈도 하나의 의사소통 메커니즘이다. 이는 분할되는 객체의 의미에 따라 모듈을 선택해야 한다.

시스템의 내력을 말해주는 모듈을 골라 일련의 응집력 있는 개념들을 해당 모듈에 담아라. 이렇게 하면 종종 모듈 간의 결합도가 낮아지기도 하는데, 그렇게 되지 않는다면 모델을 변경해서 얽혀 있는 개념을 풀어낼 방법을 찾아보거나, 아니면 의미 있는 방식으로 모델의 각 요소를 맺어줄 모듈의 기준이 될 법한 것 중 미쳐 못보고 지나친 개념을 찾아보라.

6. 도메인 객체의 생명주기

AGGREGATE

AGGREGATE는 우리가 데이터 변경의 단위로 다루는 연관 객체의 묶음을 말한다.

각 AGGREGATE에는 루트와 경계가 있다. 경계는 AGGREGATE에 무엇이 포함되고 포함되지 않는지를 정의한다. 루트는 단 하나만 존재하며, AGGREGATE에 포함된 특정 ENTITY를 가리킨다. 경계 안의 객체는 서로 참조할 수 있지만, 경계 바깥의 객체는 해당 AGGREGATE 구성요소 가운데 루트만 참조할 수 있다. 루트 이외의 ENTITY는 지역 식별성을 지니며, 지역 식별성은 AGGREGATE 내에서만 구분되면 된다. 이는 해당 AGGREGATE의 경계 밖에 위치한 객체는 루트 ENTITY의 컨텍스트 말고는 AGGREGATE의 내부를 볼 수 없기 때문이다.

  • 루트 ENTITY는 전역 식별성을 지니며 궁극적으로 불변식을 검사할 책임이 있다.

  • 각 루트 ENTITY는 전역 식별성을 지닌다. 경계 안의 ENTITY는 지역 식별성을 지니며, 이러한 지역 식별성은 해당 AGGREGATE 안에서만 유일하다.

  • AGGREGATE의 경계 밖에서는 루트 엔티티를 제외한 애그리거트 내부의 구성요소를 참조할 수 없다. 루트 엔티티가 내부 엔티티에 대한 참조를 다른 객체에 전달해줄 수는 있지만 그러한 객체는 전달받은 참조를 일시적으로만 사용할 수 있고, 참조를 계쏙 보유하고 있을 수 없다.

  • 데이터베이스 질의를 하면 애그리거트의 루트만 직접적으로 획득할 수 있다. 다른 객체는 모두 애그리거트를 탐색해서 발견해야 한다.

  • 애그리거트 안의 객체는 다른 애그리거트의 루트만 참조할 수 있다.

  • 삭제 연산은 애그리거트 경게 안의 모든 요소를 한번에 제거해야 한다.

  • 애그리거트 경계안의 어떤 객체를 변경하더라도 애그리거트의 불변식은 모두 지켜져야 한다.

엔티티와 값 객체를 애그리거트로 모으고 각각에 대해 경계를 정의하라. 한 엔티티를 골라 애그리거트의 루트로 만들고 애그리거트 경계 내부의 객체에 대해서는 루트를 거쳐 접근할 수 있게 하라. 애그리거트 밖의 객체는 루트만 참조할 수 있게 하라. 내부 구성요소에 대한 일시적인 참조는 단일 연산에서만 사용할 목적에 한해 외부로 전달될 수 있다. 루트를 경유하지 않고는 애그리거트의 내부를 변경할 수 없다. 이런 식으로 애그리거트의 각 요소를 배치하면 애그리거트 안의 객체와 전체로서의 애그리거트의 상태를 변경할 때 모든 불변식을 효과적으로 이행할 수 있다.

FACTORY (팩터리)

어떤 객체를 생성하는 것이 그 자체로도 주요한 연산이 될 수 있지만 복잡한 조립 연산은 생성된 객체의 책임으로 어울리지 않는다. 이런 책임을 클라이언트에 두면 이해하기 힘든 볼품없는 설계가 만들어질 수 있다. 클라이언트에서 직접 필요로 하는 객체를 생성하면 클라이어늩 설계가 지저분해지고 조립되는 객체나 애그리거트의 캡슐화를 위반하며, 클라이언트와 생성된 객체의 구현이 지나치게 결합된다.

자신의 책임이 다른 객체를 생성하는 것인 프로그램 요소를 FACTORY라 한다.

복잡한 객체와 애그리거트의 인스턴스를 생성하는 책임을 별도의 객체로 옮겨라. 이 객체 자체는 도메인 모델에서 아무런 책임도 맡지 않을 수도 있지만 여전히 도메인 설계의 일부를 구성한다. 모든 복잡한 객체 조립 과정을 캡슐화하는 동시에 클라이언트가 인스턴스화되는 객체의 구상 클래스를 참조할 필요가 없는 인터페이스를 제공하라. 전체 애그리거트를 하나의 단위로 생성해서 그것의 불변식이 이행되게 하라.

REPOSITORY (리파지터리)

애그리거트 내부에 존재하는 모든 객체는 루트에서부터 탐색을 토대로 접근하는 것 말고는 접근이 금지돼 있다는 점이다.

영속 객체는 해당 객체의 속성에 근거하여 검색하는 식으로 전역적으로 접근할 수 있어야 한다. 그러한 접근 방식이 필요한 곳은 탐색으로 도달하기에는 편리하지 않은 래그리거트의 루트다. 일반적으로 루트는 엔티티이며, 간혹 복잡한 내부 구조를 지닌 값 객체이거나 열거형 값이기도 하다. 다른 객체에서도 접근할 수 있게 한다면 중요한 구분법이 혼동될 것이다. 마음대로 데이터베이스에 질의를 수행하면 실제로 도메인 객체와 애그리거트의 캡슐화를 어길 수도 있다. 기술적 인프라스트럭처와 데이터베이스 접근 메커니즘을 드러내면 클라이언트가 복잡해져서 MODEL-DRIVEN DESIGN이 불분명해질 것이다.

REPOSITORY 패턴은 그와 같은 해법을 캡슐화해서 우리를 다시 모델에 집중하게 하는 단순한 개념적 틀에 해당한다.

REPOSITORY는 특정 타입의 모든 객체를 하나의 개념적 집합으로 나타낸다. 더욱 정교한 질의 기능이 있다는 점을 제외하면 리파지터리는 컬렉션처럼 동작한다. 리파지터리에는 적절한 타입의 객체가 추가되고 제거되며, 이러한 리파지터리 이면에 존재하는 장치가 그러한 객체를 데이터베이스에 삽입하거나 삭제된다. 이 같은 정의에는 생명주기의 초기 단계에서 마지막 단계에 이르기까지 애그리거트의 루트에 대한 접근을 제공하는 각종 응집력있는 책임이 포함된다.

리파지토리 이점

  • 리파지토리는 영속화된 객체를 획득하고 해당 객체의 생명주기를 관리하기 위한 단순한 모델을 클라이언트에게 제시한다.

  • 리파지토리는 영속화 기술과 다수의 데이터베이스 전략, 또는 심지어 다수의 데이터 소스로부터 애플리케이션과 도메인 설계를 분리해준다.

  • 리파지토리는 객체 접근에 대한 관계 설정을 전해준다.

  • 리파지토리를 이용하면 테스트에서 사용할 가짜 구현을 손쉽게 대체할 수 있다.

0개의 댓글