책/DDD: 도메인 주도 개발

[도메인 주도 개발] Ch1.도메인 모델 시작하기

금호박 2024. 3. 18. 00:19

도메인

: 소프트웨어로 해결하고자 하는 문제 영역(domain)

  • 도메인은 여러 하위 도메인으로 구성된다.
    • 예를 들어 온라인 서점 도메인에서,주문 하위 도메인 : 고객의 주문 처리
    • 카탈로그 하위 도메인 : 고객에게 구매할 수 있는 상품 목록 제공
  • 한 하위 도메인은 다른 하위 도메인과 연동하여 완전한 기능을 제공한다.
    • 고객이 물건을 구매하면 주문, 결제, 혜택 하위 도메인의 기능이 엮이게 된다.
  • 소프트웨어가 도메인의 모든 기능을 제공하진 않는다.
    • 많은 온라인 쇼핑몰이 자체적으로 배송 시스템을 구축하기보다는 외부 배송 업체의 시스템을 사용한다.
    • 결제 시스템의 경우에도 마찮가지이다. 직접 구현하는 경우보다 결제 대행업체를 이용해서 처리하는 경우가 많다.
  • 도메인마다 고정된 하위 도메인이 존재하는 것은 아니다.
    • 어떤 쇼핑몰은 고객에게 다양한 혜택을 제공하지만, 모든 쇼핑몰이 그러한 혜택을 제공하는 것은 아니다.
  • 하위 도메인을 어떻게 구성할지 여부는 상황에 따라 달라진다.

도메인 전문가와 개발자 간 지식 공유

Garbage in, Garbage out : 잘못된 요구사항이 들어가면 잘못된 제품이 나온다.

  • 도메인 전문가의 요구를 개발자가 제대로 이해하는 것은 매우 중요하다.
    • 예를 들어 회계 담당자는 정산 금액 계산을 자동화하는 기능을 개발자에게 요구할 수 있다. 이것을 제대로 이해하지 않고 개발이 이루어지면, 필요없거나 유용함이 떨어지는 시스템을 만들 수 있다. 또는 수정해야 할 코드가 많아져서 일정 등에 문제가 생기기도 한다.
  • 요구사항을 제대로 이해하기 위한 방법
    1. 개발자와 전문가가 직접 대화하기 → 정보의 왜곡, 손실이 줄어든다.
    2. 개발자도 도메인 지식을 갖추어야 한다.
    3. 전문가나 관련자가 요구한 내용이 항상 올바른 것은 아니다. 실제 필요한 요구를 표현하지 못하는 경우도 있기 때문에 전문가와의 대화를 통해 진짜 요구사항을 찾아내야 한다.

도메인 모델

: 특정 도메인을 개념적으로 표현한 것.

  • 도메인 모델을 표현하는 방법은 다양하다.
    • 객체를 이용한 도메인 모델 → 기능과 데이터 함께 확인 가능.
    • 상태 다이어그램 도메인 모델
    → 표현 방식은 중요하지 않고, 도메인을 이해할 수 있도록 하는 것이 중요하다.
  • 도메인 모델은 도메인을 이해하기 위한 모델이기에 도메인 모델을 이용해서 바로 코드를 작성할 수 있는 것은 아니다.                          → 구현 기술에 맞는 구현 모델이 따로 필요하다.
  • 어떤 도메인에 하위 도메인이 있을 경우, 하위 도메인에 대한 도메인 모델은 따로 만들어야 한다.

도메인 모델 패턴

  • 사용자 인터페이스(표현) : 사용자의 요청을 처리하고 사용자에게 정보를 보여 준다. 사용자는 소프트웨어를 사용하는 사람일 수도 있고, 외부 시스템일 수도 있다.
  • 응용 : 사용자가 요청한 기능을 실행한다. 업무 로직을 집접 구현하지 않으며 도메인 계층을 조합해서 기능을 실행한다.
  • 도메인 : 시스템이 제공할 도메인 규칙을 구현한다.                                                    주문 도메인의 ‘주문 취소는 배송 전에만 가능’과 같은 규칙을 구현한 코드는 도메인 계층에 위치하게 된다.
  • 인프라스트럭처 : 데이터베이스나 메시징 시스템과 같은 외부 시스템과의 연동을 처리한다.
  • 이러한 방식의 도메인 모델은 아키텍처 상의 도메인 계층을 객체 지향 기법으로 구현하는 패턴을 말한다.
  • 핵심을 구현한 코드는 도메인 모델에만 위치하기 때문에 규칙이 바뀌거나 규칙을 확장해야 할 때 다른 코드에 영향을 덜 주고 변경 내역을 모델에 반영할 수 있게 된다.

 

도메인 모델 추출

  • 도메인을 모델링 할 때 요구사항을 분석하여 모델을 구성하는 핵심 구성요소, 규칙, 기능을 찾아야 한다.

엔티티와 밸류

도출한 모델은 엔티티 밸류 로 구분한다.

엔티티

  • 각 엔티티는 엔티티마다의 고유한 식별자를 갖는다.
    • 이 식별자는 바뀌지 않고 고유하기 때문에 두 엔티티 간의 식별자가 같으면 두 엔티티는 서로 같다.
    • 엔티티를 생성하고 속성을 바꾸고 삭제할 때까지 식별자는 유지된다.
    주문에서 배송지 주소가 바뀌거나 상태가 바뀌더라도 주문번호가 바뀌지 않는다.
  • 엔티티의 식별자 생성 방식 : 생성 시점은 도메인의 특징과 사용하는 기술에 따라 달라진다.
    1. 특정 규칙에 따라 생성(주문번호, 카드번호 등)
    2. UUID나 Nano ID와 같은 고유 식별자 생성기 사용
    3. 값을 직접 입력(회원의 이메일, 아이디 등)
    4. 일련번호 사용(시퀀스나 DB의 자동 증가 칼럼 사용)

밸류 타입

  • 밸류 타입은 개념적으로 완전한 하나를 표현할 때 사용한다.
    • 예를 들어, 이 클래스의 receiverName 필드와 receiverPhoneNumber 필드는 서로 다른 데이터를 담고 있지만 개념적으로는 받는 사람을 의미함. Receiver 라는 도메인 개념을 보다 완전하게 표현할 수 있다.
      public class ShippingInfo{
      	private Receiver receiver;
      	...
      }
      
      ShippingInfo를 구성하는 데이터가 무엇인지를 더 쉽게 알 수 있다.
    • public class Reciever{ private String name; private String phoneNumber; public Reciever(String name, String phoneName){ this.name = name; this.phoneNumber = phoneNumber; } public String getName(){ return name; } public String getPhoneNumber(){ return phoneNumber; } }
    • 이것을 기존의 ShippingInfo 클래스에 적용하면,
    • 이것을 밸류 타입을 사용하면,
    • public class ShippingInfo{ /* 받는 사람 */ private String recieverName; private String receiverPhoneNumber; ... }
  • 밸류 타입이 꼭 두 개 이상의 데이터를 가져야 하는 것은 아니다.
  • 밸류 타입을 이용하면 코드의 의미를 더 잘 이해할 수 있다. (코드의 가독성이 향상된다.)
  • 두 밸류 객체를 비교할 때는 모든 속성이 같은지 비교한다.
  • 밸류 타입을 위한 기능을 추가하는 것도 가능하다.
  • 밸류 타입은 일반적으로 불변 타입으로 구현한다.
    • 불변 객체는 참조 투명성과 스레드에 안전한 특징을 가지고 있다.
    • setter() 을 사용하지 않는다.
    밸류 객체의 데이터를 변경할 때에는 기존 데이터를 변경하기보다는 변경한 데이터를 새로 갖는 새로운 밸류 객체를 생성한다.
    • 이렇게 구현하는 가장 큰 이유는 안전한 코드를 작성하기 위해서이다.
    • 만약 밸류 타입에 setValue() 를 적용하여 값을 변경할 수 있도록 한다면, 참조 투명성과 같은 문제를 겪을 수 있다.
  • public class Money{ private int value; public Money add(Money money){ return new Money(this.value + money.value); /* 새로운 Money를 생성함 */ } /* value를 변경할 수 있는 메서드 없음. */ }

엔티티 식별자와 벨류 타입

  • 엔티티 식별자의 실제 데이터는 String과 같은 문자열로 구성된 경우가 많다.
  • 식별자를 위한 밸류 타입을 사용해서 의미가 잘 드러나도록 할 수도 있다.
    • 주문번호를 표현하기 위해 Order의 식별자 타입으로 String 대신 OrderNo 밸류를 사용하면 타입을 통해 해당 필드가 주문번호라는 것을 알 수 있다.

도메인 모델에 set 메서드 넣지 않기

  • set 메서드는 도메인의 핵심 개념이나 의도를 코드에서 사라지게 한다.
    • set 메서드는 필드값만 변경하고 끝나기 때문에 상태 변경과 관련된 도메인 지식이 코드에서 사라지게 된다.
    • public class Order{ ... **/* 단순히 배송지 값을 설정한다는 의미만을 전달*/ public void setShippingInfo(Shipping newShipping){ ... }** /* 이렇게 구현할 경우, 배송지 정보를 새로 변경한다는 의미를 가진다. */ public void changeShippingInfo(Shipping new Shipping){ ... } }
    • set 메서드를 사용하면, 도메인 객체를 생성할 때 온전하지 않은 상태가 될 수 있다.                                                                  → 이처럼 객체가 불완전한 상태로 사용되는 것을 막기 위해서는 생성 시점에 필요한 것을 모두 전달해 주어야 한다.
      Order order = new Order(orderer, lines, shippingInfo, OrderState.PREPARING)
      
    • : 생성자를 통해 필요한 데이터를 모두 받아야 한다.
    • 예를 들어,이 경우, Order 를 처음 생성하는 시점에 order는 불완전하다. 또 위의 코드에서는 주문자 설정이 누락되어있다. 그럼에도 setState()를 호출하여 상품 준비 중 상태로 바꾸었다. 즉, 객체가 불완전한 상태에서 사용되고 있다.
    • Order order = new Order(); order.setOrderLine(lines); order.setShippingInfo(shippingInfo); order.setState(OrderState.PREPARING);

도메인 용어와 유비쿼터스 언어

  • 클래스, 필드, 메서드 등의 이름을 작성할 때 알맞은 용어를 선택하기 위해 노력해야 한다~

 

원본 노션 링크 : https://www.notion.so/Ch1-b69f903b4dce4477af8dbefee288a9b4