오늘 뭐했냐/개발에 대한 주저리

23.10.03 도메인 주도 설계 (Domain-driven design, DDD)

스스로에게 2023. 10. 5. 00:15

도메인 주도 설계

도메인 패턴을 중심에 놓고 설계하는 방식을 일컫는다.

 

도메인 

요구사항이나  소프트웨어로 해결하려고 하는 문제 영역을 나타낸다.

 

주요 구성 요소 : 

  • 유비쿼터스 언어(Ubiquitous Language)
    • 팀 내에서 사용되는 공통 언어를 의미합니다.
    • 모든 사람이 같은 용어로 도메인의 복잡성을 이해하고 의사소통 할 수 있도록 합니다.
  • 엔터티 (Entity)
    • 고유한 식별자(ID)를 가지며, 시간이 지나도 변하지 않는 특성을 갖습니다.
    • 도메인 내에서 식별 가능한 객체로, 그 상태나 행동이 비즈니스 로직에 따라 변할 수 있습니다.

  • 값 객체 (Value Object)
    • 고유한 식별자를 갖지 않습니다.
    • 불변성(Immutable)을 갖으며, 속성의 조합으로 구별됩니다.
    • 도메인 내에서 특정 값을 나타내는 객체로, 엔터티와 달리 변경 불가능한 특성을 갖습니다.

  • 집합 (Aggregate)
    • 도메인 객체들의 묶음으로, 한 개의 루트 엔터티와 여러 값을 객체나 엔터티로 구성됩니다.
    • 집합의 루트 엔터티를 통해서만 외부와의 상호 작용이 가능합니다.
  • 바운디드 컨텍스트 (Bounded Context)
    • 도메인 모델의 경계를 정의하는 개념입니다.
    • 동일한 용어나 개념도 다른 컨텍스트에서는 다른 의미나 행동을 가질 수 있습니다.
    • 서로 다른 바운디드 컨텍스트 간의 통신은 특정 프로토콜(예: API)을 통해 이루어집니다.

  • 도메인 이벤트 (Domain Event)
    • 도메인의 특정 사건이 발생했을 때 생성되는 이벤트입니다.
    • 이벤트는 상태 변화를 나타내며, 다른 부분들에게 해당 사건을 알리기 위해 사용될 수 있습니다.

  • 리포지토리 (Repository)
    • 엔터티의 저장과 관련된 로직을 캡슐화하는 인터페이스나 클래스입니다.
    • 특정 집합의 루트 엔터티를 위한 저장소로, 해당 엔터티의 조회나 저장 등의 연산을 제공합니다.

  • 서비스 (Service)
    • 도메인 로직을 포함하되, 엔터티나 값 객체로 적절히 표현되지 않는 도메인의 연산을 제공하는 객체입니다.
    • 서비스는 도메인 로직의 구현을 캡슐화하며, API 등의 인터페이스를 통해 외부에 제공될 수 있습니다.

아직은 잘모르겠다. 그래서 여기서 의문이 들었던 부분에 대해서 정리하고 나중에 공부하다가 다시 이해되는 내용이 있다면 다시 글을 쓰려고 한다. 

 

바운디드 컨텍스트에 대해서 옷으로 설명을 하면 옷을 판매하는 판매자와 구매하는 구매자가 서로 다른 정보나 행위가 필요하다. 옷의 기본적인 모델명, 사이즈, 색상는 서로 공통으로 사용되지만 구매자는 구매 수량, 장바구니, 구매가 등이 필요하고 판매자는 공급가, 재고 수량 등이 필요하기에 같은 옷이라는 도메인을 어떻게 바라보냐에 따라 달라지는데 이 달라지는 부분을 바운디트 컨텍스트라고 한다.

 

값 객체와 엔터티의 차이는 계좌라는 엔터티는 예로 들면은 계좌 번호라는 고유 식별자를 가지고 금액이라는 속성과 입출금이란 행위를 가진다. 그리고 여기서 값 객체는 돈이 된다. 하지만 돈은 바뀌지 않나 하고 생각이 들었는데 돈이 가지는 수치가 아니라 돈 자체가 값 객체가 되는 것이다. 

 

// 값 객체 
class Money {
  constructor(amount, currency = "USD") {
    this._amount = amount;
    this._currency = currency;
  }

  get amount() {
    return this._amount;
  }

  get currency() {
    return this._currency;
  }

  add(otherMoney) {
    if (this.currency !== otherMoney.currency) {
      throw new Error("Currencies do not match");
    }
    return new Money(this.amount + otherMoney.amount, this.currency);
  }
}

// 엔터티
class Account {
  constructor(initialMoney) {
    this._balance = initialMoney;
  }

  get balance() {
    return this._balance;
  }

  deposit(money) {
    this._balance = this._balance.add(money);
  }
}

 

그리고 값 객체는 값으로 동일하다 판단하고 엔터티는 고유식별자로 동일하다 판단한다.

 

추가 자료 

  1. 엔터티 (Entity)
    • 고유한 식별자: 엔터티는 고유한 식별자(ID)를 갖습니다. 이 식별자를 통해 특정 엔터티를 도메인 내에서 구별하고 추적합니다.
    • 변경 가능성: 엔터티의 상태는 시간이 지나며 변경될 수 있습니다. 예를 들어, 사용자의 주소나 연락처 정보가 바뀔 수 있습니다.
    • 라이프사이클: 엔터티는 특정 라이프사이클을 가집니다. 예를 들어, 사용자는 등록되고, 정보를 업데이트하며, 나중에 탈퇴할 수 있습니다.

  2. 값 객체 (Value Object)
    • 고유한 식별자 부재: 값 객체는 고유한 식별자를 갖지 않습니다. 대신, 그것의 속성 값들의 조합으로 구별됩니다.
    • 불변성: 값 객체는 한번 생성되면 그 상태가 변하지 않습니다. 변경이 필요한 경우, 새로운 값 객체를 생성해야 합니다.
    • 라이프사이클 부재: 값 객체는 일반적으로 라이프사이클을 갖지 않습니다. 대신, 필요에 따라 생성되고 소멸됩니다.

예를 들어:

  • 사용자(User)는 엔터티입니다. 각 사용자는 고유한 사용자 ID를 갖고, 그 정보(이름, 주소 등)는 시간이 지나면서 바뀔 수 있습니다.
  • 주소(Address)는 값 객체일 수 있습니다. 주소는 특정 번지, 도로명, 도시 등의 조합으로 구별되며, 주소 자체로는 고유한 식별자를 갖지 않습니다. 주소 정보가 바뀌면 새로운 주소 객체를 생성합니다.

 

직접 사용해보고 경험해야지 더  확실히 알  수 있을 것 같고 내가 생각한 핵심은 개발자와 기획자의 소통을 도메인을 모델링하고 그것을 통해서 함께 개발하는 것이다.