관리 메뉴

csct3434

[도메인 주도 개발 시작하기] 01. 도메인 모델 시작하기 본문

개발 서적/도메인 주도 개발 시작하기

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

csct3434 2024. 6. 11. 06:30

도메인이란?

  • 도메인 : 소프트웨어로 해결하고자 하는 문제 영역
  • 한 도메인은 여러 하위 도메인으로 구성된다 (ex : 온라인 서점 - 회원, 주문, 카탈로그, 리뷰, 혜택, 배송, 결제, 정산)
  • 한 하위 도메인은 다른 하위 도메인과 연동하여 완전한 기능을 제공한다. (ex : 고객이 물건을 구매하면 주문-혜택-결제-배송 하위 도메인의 기능이 엮이게 됨)

요구사항의 이해

  • 요구사항 분석 및 설계는 개발에 있어서 첫 단추 (Garbage In, Garbage Out)
  • 개발과 관련된 도메인 전문가, 관계자, 개발자가 같은 지식을 공유하고 직접 소통할수록 도메인 전문가가 원하는 제품을 만들 가능성이 높아짐
    • 개발자와 각 도메인의 전문가가 직접 대화해야 정보가 왜곡되고 손실되는 것을 방지할 수 있음
    • 도메인 전문가 만큼은 아니더라도 이해관계자와 개발자도 도메인 지식을 갖추는 것이 좋음
  • 전문가나 관련자가 소프트웨어 전문가는 아니기 때문에, 개발자는 요구사항을 이해할 때 그들이 왜 이런 기능을 요구하는지 혹은 실제로 원하는 게 무엇인지 생각하고 대화를 통해 이를 찾아가는 자세가 필요

도메인 모델

  • 도메인 모델 : 특정 도메인을 개념적으로 표현한 것, 도메인 자체를 이해하기 위한 개념 모델
  • 도메인의 각 하위 도메인이 다루는 영역은 서로 다르기 때문에 같은 용어라도 하위 도메인마다 의미가 달라질 수 있다
  • 모델의 각 구성요소는 특정 도메인으로 한정할 때 비로소 의미가 완전해지기 때문에, 각 하위 도메인은 별도의 다이어그램으로 모델링 해야 한다

도메인 모델 패턴

public class Order {

    private OrderState state;
    private ShippingInfo shippingInfo;

    public void changeShippingInfo(ShippingInfo newShippingInfo) {
        if (!isShippingChangeable()) {
            throw new IllegalArgumentException("can't change shipping in " + state);
        }
        this.shippingInfo = newShippingInfo;
    }

    private boolean isShippingChangeable() {
        return state == OrderState.PAYMENT_WAITING
            || state == OrderState.PREPARING;
    }

    public enum OrderState {
        PAYMENT_WAITING, PREPARING, SHIPPED, DELIVERING, DELIVERY_COMPLETED;
    }
}
  • 도메인 모델 패턴 : 도메인 규칙을 객체 지향 기법으로 구현하는 패턴
  • 핵심 규칙을 구현한 코드는 도메인 모델에만 위치하기 때문에 규칙이 바뀌거나 규칙을 확장해야 할 때 다른 코드에 영향을 덜 주고 변경 내역을 모델에 반영할 수 있게 된다.
  • 주문 도메인의 '출고 전에 배송지를 변경할 수 있다'라는 규칙과 '주문 취소는 배송 전에만 할 수 있다'라는 규칙을 구현한 코드는 예시와 같다.

개념 모델과 구현 모델

  • 개념 모델은 순수하게 문제를 분석한 결과물이다
  • 개념 모델은 데이터베이스, 트랜잭션 처리, 성능, 구현 기술과 같은 것은 고려하고 있지 않기 때문에 실제 코드를 작성할 때 개념 모델을 있는 그대로 사용할 수 없다.
  • 시간이 지남에 따라 도메인에 대한 새로운 지식이 쌓이면서 모델을 보완하거나 변경하는 일이 발생하기 때문에 처음부터 완벽한 개념 모델을 만들기보다는 전반적인 개요를 알 수 있는 수준으로 개념 모델을 작성해야 한다.
  • 프로젝트 초기에는 개요 수준의 개념 모델로 도메인에 대한 전체 육곽을 이해하는데 집중하고, 구현하는 과정에서 개념 모델을 구현 모델로 점진적으로 발전시켜 나가야 한다.

도메인 모델 도출

  • 제아무리 뛰어난 개발자라 할지라도 도메인에 대한 이해 없이 코딩을 시작할 수는 없다. 도메인을 이해하고 이를 바탕으로 도메인 도메인 모델 초안을 만들어야 비로소 코드를 작성할 수 있다.
  • 도메인을 모델링할 때 기본이 되는 작업은 모델을 구성하는 핵심 구성요소, 규칙, 기능을 찾는 것이다.

엔티티와 밸류

  • 엔티티와 밸류를 제대로 구분해야 도메인을 올바르게 설계하고 구현할 수 있다.

엔티티

  • 엔티티의 가장 큰 특징은 식별자를 가진다는 것이다.
  • 엔티티를 생성하고 속성을 바꾸고 삭제할 때까지 식별자는 유지된다.
  • 두 엔티티 객체를 비교할 때는 식별자가 같은지를 비교한다.

밸류 타입

  • 밸류 타입은 개념적으로 완전한 하나를 표현할 때 사용한다.
  • 밸류 타입은 불변 클래스로 구현하는 방식이 선호되는데, 가장 중요한 이유는 안전한 코드를 작성할 수 있다는 데 있다.
  • 밸류 객체를 비교할 때는 모든 속성이 같은지를 비교한다.
  • 예를 들어, 받는 사람을 위한 밸류 타입인 Receiver와 배송지 주소를 나타내는 Address 밸류 타입을 다음과 같이 작성할 수 있다.
public class ShippingInfo {
    private Receiver receiver;
    private Address address;
}

public class Receiver {
    private String name;
    private String phoneNumber;
}

public class Address {
    private String address1;
    private String address2;
    private String zipCode;
}

 

 

 

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

  • 도메인 모델에 get/set 메서드를 무조건 추가하는 것은 좋지 않는 버릇이다.
  • 특히 set 메서드는 도메인의 핵심 개념이나 의도를 코드에서 사라지게 한다.
    • changeShippingInfo()가 배송지 정보를 새로 변경한다는 의미를 가졌다면 setShippingInfo() 메서드는 단순히 배송지 값을 설정한다는 것을 의미한다.
    • completePayment()는 결제를 완료했다는 의미를 갖는 반면에 setOrderState()는 단순히 주문 상태 값을 설장한다는 것을 의미한다.
  • set 메서드의 또 다른 문제는 도메인 객체가 온전하지 않은 상태가 될 수 있다는 점이다.

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

  • 도메인에서 사용하는 용어를 최대한 코드에 반영한다면 가독성을 높여서 코드를 분석하고 이해하는 시간을 줄일 수 있다.
  • 또한 코드를 도메인 용어로 해석하거나 도메인 용어를 코드로 해석하는 과정에서 발생하는 버그도 줄일 수 있다.
    • OrderState : STEP1, STEP2, STEP3 -> verifyStep1OrStep2()
    • OrderState : PAYMENT_WAITING, PREPARING, SHIPPED -> verifyNotYetShipped();
  • 유비쿼터스 언어 : 전문가, 관계자, 개발자가 대화, 문서, 도메인 모델, 코드, 테스트 등 모든 곳에서 일관되게 사용하기 위해 만든 도메인과 관련된 공통의 언어
  • 유비쿼터스 언어를 사용하면 소통 과정에서 발생하는 용어의 모호함을 줄일 수 있고 개발자는 도메인과 코드 사이에서 불필요한 해석 과정을 줄일 수 있다.