-
도메인 주도 개발5 - Entity와 Value2Web 개발/도메인 주도 개발 2023. 11. 12. 16:06
Entity와 Value2
엔티티 식별자와 벨류 타입
엔티티 식별자의 실제 데이터는 String과 같은 문자열로 구성된 경우가 많다. 신용카드의 번호도 16개의 숫자로 구성된 문자열이며 많은 온라인 서비스에서 회원을 구분할 대 사용하는 이메일 주소도 문자열이다.
식별자에 밸류타입 사용
public class Order{ //OrderNo 타입 자체로 id가 주문번호임을 알 수 있다. private OrderNo id; ... public OrderNo getId(){ return id; } }
Money가 단순한 숫자가 아닌 도메인의 '돈'을 의미하는 것처럼 이런 식별자는 단순한 문자열이 아니라 도메인에서 측별한 의미를 지니는 경우가 많이 때문에 식별자를 위한 밸류 타입을 사용해서 의미가 잘 드러나도록 할 수 있다. 예를 들어 주문번호를 표현하기 위해 Order의 식별자 타입으로 String 대신 OrderNo 밸류 타입을 사용하면 타입을 통해 해당 필드가 주문번호라는 것을 알 수 있다.
OrderNo 대신에 String 타입을 사용한다면 'id'라는 이름만으로 해당 필드가 주문번호인지를 알 수 없다. 필드의 의미가 드러나도록 하려면 'id'라는 필드 이름 대신 'orderNo'라는 필드이름을 사용해야 한다. 반면에 식별자를 위해 OrderNo 타입 자체로 주문번호라는 것을 알 수 있으므로 필드 이름이 id여도 실제 의미를 찾는 것은 어렵지 않다.
도메인 모델에 set메서드 넣지 않기
package com.example.domain.purchase; public class UserInfo { private String id; private String name; public UserInfo() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
도메인 모델이 get/set 메서드를 무조건 추가하는 것은 좋지 않은 버릇이다. 특히 set메서드는 도메인의 핵심 개념이나 의도를 코드에서 사라지게한다.
public class Order{ ... public void setShippingInfo(ShippingInfo newShipping){...} public void setOrderState(OrderState state){...} }
위 코드와 같이 Order의 메서드를 set 메서드로 변경해 보자 앞서 게시글에서 changeShippingInfo()가 배송지 정보를 새로 변경한다는 의미를 가졌다면 set ShippingInfo()메서드는 단순히 배송지 값을 설정한다는 것을 의미한다. completePayment()는 결제를 완료했다는 의미를 갑는 반면에 setOrderState()는 단순히 주문 상태 값을 설정한다는 것을 의미한다.
구현할 때에도 completePayment()는 결제 완료 처리 코들르 구현하니가 결제 완료와 관련된 도메인 지식을 코드로 구현하는것이 자연스럽다. setOrderState()는 단순히 상태값만 변경할지 아니면 상태값에 따라 다른 처리를 위한 코드를 함께 구현할지 애매하다. 습관적으로 작성한 set메서드는 필드 값만 변견하고 끝나기 때문에 상태 변경과 관련된 도메인 지식이 코드에서 사라지게 된다.
//set 메서드로 데이터를 전달하도록 구현하면 //처음 Order를 생성하는 시점에 order는 완전하지 않다. Order order = new Order(); //set 메서드로 필요한 모든 값을 전달해야 한다. order.setOrderLine(lines); order.setShippingInfo(shippingInfo); //주문자(Orderer)를 설정하지 않은 상태에서 주문 완료처리 order.setSate(OrderState.PREPARING);
set 메서드의 또 다른 문제는 도메인 객체를 생성할 때 온전하지 않은 상태가 될 수 있다. 위 코드는 주문자 설정을 누락하고 있다 주문자 정보를 담고 있는 필드인 orderer가 null인 상황에서 order.setState()메서드를 호출해서 상품 준비 중 상태로 바꾼 것이다. orderer가 정상인지 확인하기 위해 orderer가 null인지 검사하는 코드를 setState()메서드에 위치하는 것도 맞지 않다.
Order order = new Order(orderer,line,shippingInfo,OrderState.PREPARING);
도메인 객체가 불완전한 상태로 사용된느 것을 막으려면 생성 시점에 필요한 것을 전달해 준어야 한다. 즉 생성자를 통해 필요한 데이터를 모두 받아야 한다.
public class Order{ public Order(Orderer orderer, List<Orderline> orderLines, ShippingInfo shippingInfo, OrderState state){ setOrderer(orderer); setOrderLine(orderLines); ...// 다른 값 설정 } private void setOrderer(Orderer orderer){ if(orderer == null) throw new IllegalArgumentException("no orderer"); this.order = orderer; } private void setOrderLine(List<OrderLine>orderLines){ verifyAtLeasOneOrMoreOrderLine(orderLines); this.orderLines = orderLines; calculateTotalAmounts(); } private void verifyAtLeastOneOrMoreOrderLines(List<OrderLine>orderLines){ if(orderLines == null || orderLines.isEnmpty()){ throw new IllegalArgumentException("no OrderLine"); } } private void calculateTotalAmounts(){ this.totalAmounts = orderLines.stream().mapToInt(x-> x.getAmounts()).sum(); } }
생성자로 필요한 것을 모두 받으므로 위 처럼 생성자를 호출하는 시점에 필요한 데이터가 올바른지 검사할 수 있다.
위 코드의 set메서드는 앞서 언급한 set 메서드와 중요한 차이점이 있는데 그것은 바로 접근 범위가 private이라는 점이다. 위 코드에서 set 메서드는 크래스 내부에서 데이터를 변경할 목적으로 사용된다. private이기 때문에 외분에서 데이터를 변경할 목적으로 set 메서드를 사용할 수 없다.
불변 밸류 타입을 사용하면 자연스럽게 밸류 타입에는 set 메서를 구현하지 않는다. set메서드를 구현해야 할 특별한 이유가 없다면 불변 타입의 장점을 살릴 수 있도록 밸류 타입은 불변으로 구현한다.
[출처 - 도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지, 최범균 저]
https://www.hanbit.co.kr/store/books/look.php?p_code=B4309942517
'Web 개발 > 도메인 주도 개발' 카테고리의 다른 글
도메인 주도 개발3 - 도메인 모델 도출 (1) 2023.10.10 도메인 주도 개발2 - 도메인 모델 패턴 (0) 2023.10.10 도메인 주도 개발1 - 도메인 모델 (0) 2023.10.06