1.1 도메인이란?
개발자 입장에서 바라보면 온라인 서점은 구현해야 할 소프트웨어의 대상이 된다. 온라인 서점은 책을 판매하는 데 필요한 상품 조회, 구매, 결제, 배송 추적 등 기능을 제공해야 한다. 이때 온라인 서점은 소프트웨어로 해결하고자 하는 문제 영역, 즉 도메인에 해당한다.
예로 온라인 서점 도메인은 몇 개의 하위 도메인으로 나눌 수 있다. 하위 도메인을 어떻게 구성할지 여부는 상황에 따라 달라진다.
1.2 도메인 전문가와 개발자 간 지식 공유
코딩에 앞서 요구사항을 올바르게 이해하는 것이 중요하다. 간단한 방법으로 개발자와 전문가가 직접 대화하는 것이다. 도메인 전문가만큼은 아니겠지만 이해관계자와 개발자도 도메인 지식을 갖춰야 한다. 전문가나 관련자가 요구한 내용이 항상 올바른 것은 아니며, 때론 본인들이 실제로 원하는 것을 정확하게 표현하지 못할 때도 있다. 개발자는 요구사항을 이해할 때 왜 이런 기능을 요구하는지 또는 실제로 원하는 게 무엇인지 생각하고 전문가와 대화를 통해 진짜로 원하는 것을 찾아야 한다.
1.3 도메인 모델
도메인 모델에는 다양한 정의가 존재하는데, 기본적으로 도메인 모델은 특정 도메인을 개념적으로 표현한 것이다.
객체 기반 주문 도메인 모델 -> 도메인을 이해하려면 도메인이 제공하는 기능과 도메인의 주요 데이터 구성을 파악해야 하는데, 이런 면에서 기능과 데이터를 함께 보여주는 객체 모델은 도메인을 모델링하기에 적합하다.
외에도 상태 다이어그램, 클래스 다이어그램 같은 UML 표기법과 계산 규칙이 중요하다면 수학 공식을 활용해서 도메인 모델을 만들 수 있다. 도메인을 이해하는 데 도움이 된다면 표현 방식이 중요하지 않다. 도메인 모델은 기본적으로 도메인 자체를 이해하기 위한 개념 모델이다. 개념 모델을 이용해 바로 코드를 작성할 수 있는 것은 아니기에 구현 기술에 맞는 구현 모델이 따로 필요하다.
1.4 도메인 모델 패턴
일반적인 애플리케이션의 아키텍처는 4가지 영역을 구성된다.
- 사용자 인터페이스 (UI) OR 표현 (Presentation) : 사용자의 요청을 처리하고 사용자에게 정보를 보여준다.
- 응용 (Application) : 사용자가 요청한 기능을 실행한다. 업무 로직을 직접 구현하지 않으며 도메인 계층을 조합해서 기능을 실행한다.
- 도메인 : 시스템이 제공할 도메인 규칙을 구현한다.
- Infrastructure : 데이터베이스나 메시징 시스템과 같은 외부 시스템과의 연동을 처리한다.
도메인 모델을 아키텍처 상의 도메인 계층을 객체 지향 기법으로 구현하는 패턴을 말한다. 도메인 계층은 도메인의 핵심 규칙을 구현한다. 주문 도메인의 경우 '출고 전에 배송지를 변경할 수 있다'라는 규칙과 '주문 취소는 배송 전에만 할 수 있다'라는 규칙을 구현한 코드가 도메인 계층에 위치하게 된다. 이런 도메인 규칙을 객체 지향 기법으로 구현하는 패턴이 도메인 모델 패턴이다.
개념 모델은 순수하게 문제를 분석한 결과물이다. 개념 모델은 데이터베이스, 트랜잭션 처리, 성능, 구현 기술과 같은 것을 고려하고 있지 않기 때문에 실제 코드를 작성할 때 개념 모델을 있는 그대로 사용할 수 없다. 그래서 개념 모델을 구현 가능한 형태의 모델로 전환하는 과정을 거친다.
처음부터 완벽한 개념 모델을 만들기보다는 전반적인 개요를 알 수 있는 수준으로 개념 모델을 작성해야 한다. 프로젝트 초기에는 개요 수준의 개념 모델로 도메인에 대한 전체 윤곽을 이해하는 데 집중하고, 구현하는 과정에서 개념 모델을 구현 모델로 점진적으로 발전시켜야 한다.
1.5 도메인 모델 도출
도메인을 모델링할 때 기본이 되는 작업은 모델로 구성하는 핵심 구성요소, 규칙, 기능을 찾는 것이다. 이 과정은 요구사항에서 출발한다.
- 최소 한 종류 이상의 상품을 주문해야 한다.
- 한 상품을 한 개 이상 주문할 수 있다.
- 총 주문 금액은 각 상품의 구매 가격 합을 모두 더한 금액이다.
- 각 상품의 구매 가격 합은 상품 가격에 구매 개수를 곱한 값이다.
- 주문할 때 배송지 정보를 반드시 지정한다.
- 배송지 정보는 받는 사람 이름, 전화번호, 주소로 구성된다.
- 출고를 하면 배송지를 변경할 수 없다.
- 출고 전에 주문을 취소할 수 있다.
- 고객이 결제를 완료하기 전에는 상품을 준비하지 않는다.
이 요구사항에서 알 수 있는 것은 주문은 '출고 상태로 변경하기', '배송지 정보 변경하기', '주문 취소하기', '결제 완료하기' 기능을 제공한다는 것이다. 상세 구현까지 할 수준은 아니지만 Order에 관련 기능을 메서드로 추가할 수 있다.
1.6 엔티티와 밸류
도메인 모델은 크게 Entity와 Value로 구분할 수 있다. Entity와 Value를 제대로 구분해야 도메인을 올바르게 설계하고 구현할 수 있기 때문에 둘의 차이를 명확하게 이해하는 것은 중요하다.
1.6.1 Entity
가장 큰 특징은 식별자를 가진다는 것이다. 식별자는 엔티티 객체마다 고유해서 각 엔티티는 서로 다른 식별자를 갖는다. 엔티티의 식별자는 바뀌지 않으며, 엔티티를 생성하고 속성을 바꾸고 삭제할 때까지 식별자는 유지된다. 그래서 두 엔티티 객체의 식별자가 같으면 서로 같다고 판단할 수 있다. 엔티티를 구현한 클래스는 다음과 같이 식별자를 이용해 equlas(), hashCode() 메서드를 구현할 수 있다.
1.6.2 엔티티의 식별자 생성
엔티티의 식별자를 생성하는 시점은 도메인의 특징과 사용하는 기술에 따라 달라진다. 흔히 아래 중 한 가지 방식으로 생성한다.
1) 특정 규칙에 따라 생성
2) UUID나 Nano ID와 같은 고유 식별자 생성기 사용
3) 값을 직접 입력
4) 일련번호 사용(시퀀스나 DB의 자동 증가 칼럼 사용)
날짜와 시간을 이용해서 식별자를 생성할 때 주의할 점은 같은 시간에 동시에 식별자를 생성해도 같은 식별자가 만들어지면 안되는 점이다.
UUID를 사용해서 식별자를 생성할 수 있다. 회원의 아이디나 이메일과 같은 식별자는 직접 값을 입력한다. 때문에 중복되지 않도록 입력하는 것이 중요하다. 일련번호를 식별자로 이용하기도 한다. 주로 데이터베이스가 제공하는 자동 증가 기능을 사용한다. 자동 증가 칼럼을 제외한 다른 방식은 식별자를 먼저 만들고 엔티티 객체를 생성할 때 식별자를 전달할 수 있다.
자동 증가 칼럼은 DB 테이블에 데이터를 삽입해야 값을 알 수 있기에, 테이블에 데이터를 추가하기 전에는 식별자를 알 수 없다. 이것은 엔티티 객체를 생성할 때 식별자를 전달할 수 없음을 의미한다. 아직 설명하지 않았지만, Repository는 도메인 객체를 데이터베이스에 저장할 때 사용하는 구성요소이다.
1.6.3 밸류 타입
밸류 타입은 개념적으로 완전한 하나를 표현할 때 사용한다. 예로 받는 사람을 위한 밸류 타입인 Receiver를 작성할 수 있다. '받는 사람'이라는 도메인 개념으로 표현한다. 밸류 타입을 사용함으로써 개념적으로 완전한 하나를 잘 표현할 수 있는 것이다. 꼭 벨류 타입이 2개 이상의 데이터를 가져야 하는 것은 아니다. 의미를 명확하게 표현하기 위해 밸류 타입을 사용하는 경우도 있다. 밸류 타입의 또 다른 장점은 밸류 타입을 위한 기능을 추가할 수 있다는 것이다.
밸류 객체의 데이터를 변경할 때는 기존 데이터를 변경하기보다는 변경한 데이터를 갖는 새로운 밸류 객체를 생성하는 방식을 선호한다. 데이터 변경 기능을 제공하지 않는 타입을 불변이라고 한다. 밸류 타입을 불변으로 구현하는 여러 이유가 있는데 가장 중요한 이유는 안전한 코드를 작성할 수 있다는 데 있다. 불변 객체는 참조 투명성과 스레드에 안전한 특징을 갖고 있다.
1.6.4 엔티티 식별자와 밸류 타입
엔티티 식별자의 실제 데이터는 String과 같은 문자열로 구성된 경우가 많다. 예로 Money가 도메인의 '돈'을 의미하는 것처럼 단순한 문자열이 아니라 특별한 의미를 지니는 경우가 많기 때문에 식별자를 위한 밸류 타입을 사용해서 의미가 잘 드러나도록 할 수 있다.
1.6.5 도메인 모델에 set 메서드 넣지 않기
도메인 모델에 get/set 메서드를 무조건 추가하는 것은 좋지 않은 버릇이다. 도메인의 핵심 개념이나 의도를 코드에서 사라지게 한다. set 메서드는 필드값만 변경하고 끝나기 때문에 상태 변경과 관련된 도메인 지식이 코드에서 사라지게 된다. 도메인 객체가 불완전한 상태로 사용되는 것을 막으려면 생성 시점에 필요한 것을 전달해 주어야 한다. 즉, 생성자를 통해 필요한 데이터를 모두 받아야 한다.
set 메서드의 중요한 것은 접근 범위가 private이라는 점이다. 해당 코드에서 set은 클래스 내부에서 데이터를 변경할 목적으로 사용되며, private이기 때문에 외부에서 데이터를 변경할 목적으로 set 메서드를 사용할 수 없다. 불변 타입을 사용하면 자연스럽게 밸류 타입에는 set 메서드를 구현하지 않는다.
DTO에 get/set은 도메인 객체의 데이터 일관성에 영향을 줄 가능성이 높지 않다. 요즘 개발 프레임워크나 개발 도구는 set 메서드가 아닌 private 필드에 직접 값을 할당할 수 있는 기능을 제공하고 있어 set 메서드가 없어도 프레임워크를 이용해서 데이터를 전달받을 수 있다. 해당 기능을 최대한 활용하면 DTO도 불변 객체가 되어 불변의 장점을 DTO까지 확장할 수 있다.
1.7 도메인 용어와 유비쿼터스 언어
도메인에서 사용하는 용어를 코드에 반영하지 않으면 그 코드는 개발자에게 코드의 의미를 해석해야 하는 부담을 준다. 에릭 에반스는 도메인 주도 설계에서 언어의 중요함을 강조하기 위해 유비쿼터스 언어라는 용어를 사용했다. 전문가, 관계자, 개발자가 도메인과 관련된 공통의 언어를 만들고 이를 대화, 문서, 도메인 모델, 코드, 테스트 등 모든 곳에서 같은 용어를 사용한다. 소통 과정에서 발생하는 용어의 모호함을 줄일 수 있고 개발자는 도메인과 코드 사이에서 불필요한 해석 과정을 줄일 수 있다.
시간이 지날수록 도메인에 대한 이해가 높아지는데 새롭게 이해한 내용을 잘 표현할 수 있는 용어를 찾아내고 이를 다시 공통의 언어로 만들어 다 같이 사용한다. 새로 발견한 용어는 코드나 문서에도 반영해 산출물에 최신 모델을 적용한다. 도메인 용어는 좋은 코드를 만드는 데 매우 중요한 요소임에 틀림없지만 국내 개발자는 이 점에 있어 불리한 점이 있다. 바로 영어다. 분야의 특성상 알파벳과 숫자를 사용하는 클래스, 필드, 메서드 등의 이름을 작성하게 되는데 이것은 도메인 용어를 영어로 해석하는 노력이 필요함을 뜻한다.
'독서 > 도메인 주도 개발 시작하기' 카테고리의 다른 글
6장 응용 서비스와 표현 영역 (0) | 2024.12.03 |
---|---|
5장 스프링 데이터 JPA를 이용한 조회 기능 (0) | 2024.12.02 |
4장 리포지터리와 모델 구현 (0) | 2024.12.01 |
3장 애그리거트 (0) | 2024.11.29 |
2장 아키텍처 개요 (1) | 2024.11.28 |