욱'S 노트

DDD - 엔티티, VO, 서비스 본문

Methdology/Domain Driven Design

DDD - 엔티티, VO, 서비스

devsun 2016. 1. 27. 14:44

엔티티 VS VO


어떠한 객체가 상태를 가진다면 엔티티와 VO라고 생각할 수 있다. 둘의 차이점은 연속성(continuity)와 식별성(identity)를 가지는 가에 있다. 연속성과 식별성이 필요하다면 엔티티이다. 여기에서 중요한 점이 영속성이 엔티티와 VO을 나누는 개념은 아니다. VO도 엔티티와 함께 데이터베이스에 저장될 수 있다.


연관관계


각각의 엔티티는 탐색 가능한 연관관계를 가질 수가 있다. 일대일, 일대다, 다대일, 다대다이다. 사실 현실 세계의 모든 엔티티들은 양방향 디펜더시를 가진다. 하지만 연관관계를 쉽게 다루기 위해선 단방향으로 관계를 단순화할 필요가 있다. -> 범균님은 무조건 양방향 디펜던시는 제거해야 한다고 한다.


탐색방향을 부여하고, 한정자를 추가하고 중요하지 않는 연관관계는 제거하도록 노력하다. 


엔티티 


어떤 객체가 일차적으로 식별성을 가질 경우 엔티티로 고려하자. 손정욱이라는 사람의 키는 변경되고 주소도 변경될 것이다.(연속성) 하지만 한국에서 손정욱이라는 사람은 동일하다. 주의할 점은 자바의 ==연산자로 객체가 동일하다는 것과는 다른 의미이다. 엔티티의 생명주기를 잘 관리하자. 도메인 모델의 핵심이 바로 엔티티이다. 식별할 수 있는 고유키가 없다면 일반적인 해법은 유일한 아이디를 생성하는 것이다. 


VO


VO는 식별성이 없다. 이러한 객체는 사물의 어떤 특징을 묘사한다. 주소는 VO인가? 엔티티인가? 정답은 상황에 따라 다르다. 쇼핑몰에 주문한 단순히 넣는 텍스트값으로 본다면 VO이다. 그렇지만 택배시스템과 같이 주소가 관리되고 택배경호에 활용되어 유일하게 식별되어야 한다면 엔티티이다. 주의할 점은 VO는 immutable 해야 한다. 모든 필드의 값이 같다면 두 VO는 같은 것이다. Person이라는 엔티티에 Name이라는 VO가 있다고 가정해보자. VO는 성이 손이고 이름이 정욱이라는 객체이다. Person의 이름이 변경된다면 Person의 VO가 변경되는 것이지 VO의 내부상태 성과 이름이 변경되는 것이 아니라는 것이다. 이에 VO는 쉽게 공유하고 복사해서 사용할 수 있다. 성능에 문제가 있다면 FlyWeight 패턴의 사용을 고려하라.


서비스


어플리케이션 계층이 서비스가 아니다. DDD에서 서비스는 어플리케이션 서비스, 도메인 서비스, 인프라스트럭처 서비스로 구분된다. 개념적으로 어떠한 객체에도 속하지 않는 연산이 존재할 수 있다. 이러할 경우 억지로 엔티티에 책임을 부여하려하지 말고 서비스를 모델에 명시적으로 포함하자.


서비스는 다른 객체와의 관계를 강조한다. 엔티티나 VO와 달리 클라언트에 무엇을 제공하느냐에 초점을 맞춘다.


도메인 서비스와 어플리케이션 서비스, 인프라스트럭처 서비스는 어떻게 구분될까?

이체 서비스는 계좌와 원장 객체간의 실제 도메인로직을 수행한다. 이는 도메인 서비스이다. 응용서비스는 사용자로부터 요청을 이용하여 도메인서비스를 호출하고 추가적으로 인프라스트럭처 서비스(메시지 전송)를 호출한다. 인프라스트럭처 서비스는 지정한 곳으로 이메일을 보내는 것과 같은 핵심 도메인과 벗어난 서비스를 제공한다.


public TrackingId bookNewCargo(final UnLocode originUnLocode,
final UnLocode destinationUnLocode,
final Date arrivalDeadline) {
final TrackingId trackingId = cargoRepository.nextTrackingId();
final Location origin = locationRepository.find(originUnLocode);
final Location destination = locationRepository.find(destinationUnLocode);
final RouteSpecification routeSpecification = new RouteSpecification(origin, destination, arrivalDeadline);

final Cargo cargo = new Cargo(trackingId, routeSpecification);

cargoRepository.store(cargo);
logger.info("Booked new cargo with tracking id " + cargo.trackingId().idString());

return cargo.trackingId();
}

위는 어플리케이션 서비스이다. 인자로 전달된 객체는 모두 VO이다. 사용자 요청이 엔티티일 수 없다. 그리고 결과도 VO로 제한된다. 엔티티가 결과로 노출된다면 우리의 도메인 계층은 보호될 수 없을 것이다. 일반적으로 어플리케이션 서비스에서는 도메인 계층 및 인프라스트럭처 계층에 존재하는 객체들을 사용하여 그들의 상호작용을 담당한다. 트랜잭션 서비스의 트랜잭션 시작 및 커밋을 호출했다면 인프라스트럭쳐 서비스를 이용했다고 볼 수 있다.


public interface RoutingService {

/**
* @param routeSpecification route specification
* @return A list of itineraries that satisfy the specification. May be an empty list if no route is found.
*/
List<Itinerary> fetchRoutesForSpecification(RouteSpecification routeSpecification);

}

위는 도메인 서비스의 예제이다. 주어진 경로 스펙에 따른 경로를 찾아준다. 실제 구현은 일반적으로 인프라스트럭처에 의존한다.


public interface GraphTraversalService extends Remote {

/**
* @param originUnLocode origin UN Locode
* @param destinationUnLocode destination UN Locode
* @param limitations restrictions on the path selection, as key-value according to some API specification
* @return A list of transit paths
* @throws RemoteException RMI problem
*/
List<TransitPath> findShortestPath(String originUnLocode,
String destinationUnLocode,
Properties limitations) throws RemoteException;

}

마지막이 인프라스트럭쳐 서비스이다. 도메인 및 응용 서비스 계층에서 호출할 수 있다.



'Methdology > Domain Driven Design' 카테고리의 다른 글

DDD - Specification  (0) 2016.01.27
DDD - Aggregate, Factory, Repository  (0) 2016.01.27
DDD - 도메인의 격리  (0) 2016.01.27
DDD - 모델 주도 설계  (0) 2016.01.27
DDD - 의사소통과 언어 사용  (0) 2016.01.27
Comments