소프트웨어로 해결하고자 하는 문제 영역, 한 도메인은 다시 하위 도메인으로 나눌 수 있다.
특정 도메인을 위한 소프트웨어라고 해서 반드시 모든 기능을 직접 구현해야 하는것은 아니다.
도메인마다 고정된 하위 도메인이 존재하는것은 아니며 하위 도메인을 어떻게 구성 할지는 상황에 따라 달라진다.
홍보, 정산, 배송등 각 영역에는 전문가가 있다.
이들은 해당 도메인에 대한 지식과 경험을 바탕으로 기능 개발을 요구한다.
개발자는 이런 요구사항을 분석하고 설계하여 코드 작성, 테스트, 배포를 한다.
이 과정에서 요구사항은 첫 단추와 같으며 잘못 개발한 코드는 고치는데 많은 노력이 들기 때문에
초기에 요구사항을 올바르게 이해하는것이 매우 중요하다.
정보가 왜곡되고 손실 되지 않도록 개발자와 전문가가 직접 대화하는것이 좋다.
도메인 전문가만큼은 아니겠지만 이해관계자와 개발자도 도메인 지식을 갖춰야 한다.
관련 인원들이 같은 지식을 공유하고 직접 소통할수록 도메인 전문가가 원하는 제품을 만들 가능성이 높아진다.
"Garbage in, Garbage out" 잘못된 값은 잘못된 결과가 나온다.
잘못된 요구사항이 들어가면 잘못된 제품이 나온다.
전문가나 관련자가 요구한 내용이 항상 올바른 것은 아니며 보인들이 실제로 원하는 것을 정확하게
표현하지 못할 때도 있다.
따라서 요구사항을 이해할때는 왜 이런 기능을 요구하는지 또는 실제 원하는 게 무엇인지 생각하고
대화를 통해 진짜로 원하는 것을 찾아야한다.
기본적으로 도메인 모델은 특정 도메인을 개념적으로 표현한것이다.
도메인 모델을 사용하면 여러 관계자들이 동일한 모습으로 도메인을 이해하고 도메인 지식을 공유하는데 도움이 된다.
도메인 모델을 표현할때 클래스 다이어그램, 상태 다이어그램 UML 표기법만 사용해야 하는것은 아니다.
관계가 중요한 도메인이라면 그래프를 이용해서 도메인을 모델링 할 수 있다.
도메인을 이해하는 데 도음이 된다면 표현 방식이 무엇인지는 중요하지 않다.
도메인 모델은 기본적으로 도메인 자체를 이해하기 위한 개념 모델이다.
개념 모델을 이용해서 바로 코드를 작성할 수 있는것은 아니기에 구현 기술에 맞게 구현 모델이 따로 필요하다.
도메인은 다수의 하위 도메인으로 구성 되므로 각 하위 도메인이 다루는 영역이 서로 다르다면
같은 용어라도 의미가 달라질 수 있다.
도메인에 따라 용어 의미가 결정되므로 여러 하위 도메인을 하나의 다이어그램에 모델링하면 안된다.
모델의 각 구성요소는 특정 도메인으로 한정할 때 비로소 의미가 완전해지기 때문에 별도로 모델을 만들어야 한다.
일반적인 애플리케이션의 아키텍처는 4개의 영역으로 구성된다.
영역 | 설명 |
---|---|
사용자 인터페이스 또는 표현 | 사용자의 요청을 처리하고 사용자에게 정보를 보여준다. 여기서 사용자는 소프트웨어를 사용하는 외부 시스템 일수도 있다. |
응용 | 사용자가 요청한 기능을 실행한다. 업무 로직을 직접 구현하지 않으며 도메인 계층을 조합해서 기능을 실행한다. |
도메인 | 시스템이 제공할 도메인 규칙을 구현한다. |
인프라스트럭처 | 데이터베이스나 메세징 시스템과 같은 외부 시스템과의 연동을 처리한다. |
앞서 살펴본 도메인 모델이 도메인 자체를 이해하는 데 필요한 개념 모델이라면
지금 살펴볼 도메인 모델은 마틴 파울러가 쓴 "엔터프라이즈 애플리케이션 아키텍처 패턴" 책의 도메인 모델 패턴을 의미한다.
도메인 모델은 아키텍처 상의 도메인 계층을 객체 지향 기법으로 구현하는 패턴을 의미한다.
도메인 계층은 도메인의 핵심 규칙을 구현하며 이를 객체 지향 기법으로 구현하는 패턴이 도메인 모델 패턴이다.
핵심 규칙을 구현한 코드는 도메인 모델에만 위치하기 때문에 규칙이 바뀌거나 규칙을 확장해야할때
다른 코드에 영향을 덜 주고 변경 내역을 모델에 반영할수 있게 된다.
개념 모델은 순수하게 문제를 분석한 결과물이며 DB, 트랙잭션 처리, 성능, 구현기술등을 고려하고 있지 않기 때문에
실제 코드를 작성할때 그대로 사용할 수 없다.
따라서 개념모델을 구현 가능한 형태의 모델로 전환하는 과정을 거치게 된다.
개념 모델을 만들때 처음부터 완벽하게 도메인을 표현하는 모델을 만드는것은 실제로 불가능하다.
소프트웨어를 개발하는 동안 도메인을 더 잘 이해하게 되며 도메인 지식이 쌓이면서 새로운 통찰에 따라
전혀 다른 의미로 해석할 수도 있다.
따라서 처음부터 완벽한 개념 모델을 만들기보다는 전반적인 개요를 알 수 있는 수준으로 작성해야한다.
초기엔 전체 윤곽을 이해하는데 집중하고 구현 과정에서 개념 모델을 구현 모델로 발전시켜 나가야한다.
아무리 뛰어난 개발자도 도메인에 대한 이해없이 코딩을 할수 없다.
기획서, 유스케이스 등과 같은 요구사항과 관련자의 대화를 통해 도메인 모델 초안을 만들고 나서
비로소 코드를 작성할 수 있다.
도메인을 모델링할 때 기본이 되는 작업은 요구사항 으로부터 모델을 구성하는 핵심 구성요소, 규칙, 기능을 찾는것이다.
도메인 모델의 필드 메서드 등을 통해 규칙을 구현한다.
생성자에서 메서드를 호출하여 생성 조건에 대해서 규칙을 지정할수 있으며
변경시에 validation 을 통하여 규칙을 지정할수도 있다.
요구사항에서 도메인 모델을 점진적으로 만들어가며 깊게 파악하면서 구현 내용이 바뀐다.
문서화를 하는 주된 이유는 지식을 공유하기 위함이며 좋은 코드는 그 자체로 문서가 된다.
도메인 지식이 잘 묻어나도록 코드를 작성하지 않으면 코드의 동작은 해석할 수 있어도
도메인 관점에서 왜 그렇게 작성했는지 이해하는데는 도움이되지 않는다.
단순히 보기 좋게 코드를 작성하는것이 아니라 도메인 관점에서 코드가 도메인을 잘 표현해야
비로소 코드의 가독성이 높아지고 문서로서 코드가 의미를 갖는다.
도출한 모델은 크게 엔티티와 밸류로 구분할 수 있다.
앤티티와 밸류를 제대로 구분해야 도메인을 올바르게 설계하고 구현할 수 있다.
이 둘의 차이를 명확하게 이해하는것은 도메인을 구현하는 데 있어 중요하다.
엔티티의 가장큰 특징은 식별자를 가진다는것이다.
식별자는 엔티티 객체마다 고유해서 각 엔티티는 서로 다른 식별자를 가진다.
엔티티를 생성하고 속성을 바꾸고 삭제할 때까지 식별자는 유지된다.
따라서 두 엔티티 객체의 식별자가 같으면 두 엔티티는 같다고 판단할 수 있다.
식별자를 이용해서 equals()
, hashCode()
메서드를 구현할 수 있다.
엔티티의 식별자를 생성하는 시점은 도메인의 특징과 사용하는 기술에 따라 달라진다.
흔히 식별자는 다음 중 한 가지 방식으로 사용한다.
흔히 사용하는 규칙중 현재 시간과 다른값을 조합하는것도 있다.
날짜와 시간을 이용해서 식별자를 생성할때 주의할점은 같은 시간 동시에
식별자를 생성해도 같은 식별자가 만들어지면 안된다는 점이다.
회원 아이디나 이메일과 같이 사용자가 직접 입력하는값의 경우 중복해서
입력하지 않도록 사전에 방지하는것이 좋다.
자동증가 컬럼의 경우 DB 테이블에 데이터를 삽입해야 비로소 값을 알수 있다.
이것은 엔티티 객체를 생성할때 식별자를 전달할 수 없음을 의미한다.
특정의미의 도메인을 묶어 밸류타입으로 사용함으로써 개념적으로 완전한 하나를 표현할수있다.
// before
public class ShippingInfo {
private String receiverName;
private String receiverPhoneNumber;
private String shippingAddress1;
private String shippingAddress2;
private String shippingZipcode;
}
// after
public class ShippingInfo {
private Receiver receiver;
private Address address;
}
밸류 타입이 꼭 두 개 이상의 데이터를 가져야 하는것은 아니다.
의미를 명확하게 표현하기 위해 밸류 타입을 사용하는 경우도 있다.
밸류 타입의 또 다른 장점은 밸류 타입을 위한 추가 기능을 추가할 수 있다는것이다.
// 돈을 의미하는 타입을 만들어 사용할수도 있다.
public class Money {
private int value;
public Money(int value) {
this.value = value;
}
// getter...
// 추가 기능
public Money add(Money money) {
return new Money(this.value + money.value);
}
}
Money 처럼 데이터 변경 기능을 제공하지 않는 타입을 불변 이라고한다.
밸류타입을 불변으로 만드는 가장 중요한 이유는 안전한 코드 작성을 할수 있다는데 있다.
만약 Money 가 value 를 수정할수 있다면 참조 관계에서 문제가 발생할수 있다.
따라서 이를 방지하기 위해 deep copy 를 해야 하는데 불변이면 이러한 문제가 없어진다.
불변 객체는 참조 투명성과 스레드에 안전한 특징을 갖고 있다.
도메인에서 특별한 의미를 지니고 있도록하기 위해 밸류 타입을 사용할수도 있다.
주문번호 식별자를 String 타입으로하여 id
라고 한다면 해당필드가 주문번호인지 알 수 없지만
OrderNo 라는 밸류 타입을 사용한다면 필드이름이 id
여도 실제 의미를 쉽게 찾을수 있다.
도메인 모델에 get/set 메서드를 무조건 추가하는것은 좋지 않은 버릇이다.
특히 set 메서드는 도메인의 핵심 개념이나 의도를 코드에서 사라지게 한다.
set 메서드의 또다른 문제는 도메인 객체를 생성할때 온전하지 않은 상태가 될 수 있다는 점이다.
도메인 객체가 불완전한 상태로 사용되는 것을 막으려면 생성 시점에 전달해주어야 한다.
또한 그 시점에 필요한 데이터가 올바른지 검사할 수 있다.
불변 밸류타입을 사용하면 자연스럽게 set 메서드를 구현하지 않는다.
불변 타입의 장점을 살릴 수 있도록 밸류 타입은 불변으로 구현한다.
DTO 의 경우 get/set 메서드를 제공해도 도메인 객체의 데이터 일관성에 영향을 줄 가능성이 높지 않다.
요즘 프레임워크 등은 set 메서드가 아닌 private 필드에 직접 값을 할당할 수 있는 기능을 제공하므로
해당 기능을 최대한 활용하자.
코드를 작성할때 도메인에서 사용하는 용어는 매우 중요하다.
도메인에서 사용하는 용어를 코드에 반영하지 않으면 개발에게 코드의 의미를 해석해야하는 부담을 준다.
// bad
public enum OrderState {
STEP1, STEP2, ...
}
// good
public enum OrderState {
PAYMENT_WAITING, PREPARING, ...
}
이는 코드의 가독성을 높혀 분석하고 이해하는 시간을 줄여준다.
최대한 도메인 용어를 사용해서 도메인 규칙을 코드로 작성하게 되면 버그도 줄어든다.
에릭 에반스는 도메인 주도 설계(DDD) 에서 언어의 중요함을 강조하기 위해
유비쿼터스 언어(Ubiquitous Language)
라는 용어를 사용했다.
전문가, 관계자, 개발자가 도메인과 관련된 공통의 언어를 만들고 이를 대화, 문서, 도메인 문서, 코드, 테스트 등
모든 곳에서 같은 용어를 사용하여 소통과정에 발생하는 용어의 모호함을 줄이고 개발자는 도메인과 코드 사이에서
불필요한 해석 과정을 줄일수 있다.
시간이 지날수록 도메인에 대한 이해가 높아지면 새롭게 내용을 잘 표현할 수 있는 용어를 찾고
이를 다시 공통의 언어로 만들어 사용한다.
도메인 용어를 만들때 영어로 지어야 하기 때문에 적절한 영단어를 찾기 힘든 경우도 많다.
반대로 미세한 차이를 몰라 선택하기 어려울 수도 있다.
이는 쉽지 않은 일이므로 시간을 들여 찾는 노력을 해야한다.
도메인 용어에 알맞은 단어를 찾는 시간을 아까워하지 말자.