-
AGGREGATE와 REPOSITORY 4부JAVA/DDD 2021. 1. 5. 16:39
- OrderLineItem은 상품 정보를 알고 있는 책임을 지닌 Product 클래스와 연관 관계를 가지며, 상품의 수량을 속성으로 포함한다.
- OrderLineItem의 생성자에 전달된 productName은 Product ENTRY POINT를 검색하기 위해 사용하는 검색 키이다.
- Product은 REFERENCE OBJECT인 동시에 ENTRY POINT이므로 productName을 가지는 Product 인스턴스는 시스템 내에서 유일해야 한다.
- 따라서 Product를 관리하는 ProductRepository로부터 해당 인스턴스를 얻어 OrderLineItem의 product 속성에 할당한다.
- getPrice() 메서드는 현재 주문 항목의 가격을 반환하는 메서드로 상품 가격에 상품 수량을 곱한 금액을 반환한다.
123456789101112131415161718192021OrderLineItem.javapublic class OrderLineItem {private Product product;private int quantity;private ProductRepository productRepository = new ProductRepository();public OrderLineItem(String productName, int quantity) {this.product = productRepository.find(productName);this.quantity = quantity;}public Money getPrice() {return product.getPrice().multiply(quantity);}public Product getProduct() {return product;}}cs - Product는 상품 명과 상품의 가격을 알 책임을 지닌 ENTRY POINT로 상품 명을 검색 키로 사용한다
12345678910111213141516171819202122232425public class Product extends EntryPoint {private Money price;private String name;public Product(String name, long price) {super(name);this.price = new Money(price);}public Product(String name, Money price) {super(name);this.price = price;}public Money getPrice() {return price;}public String getName() {return name;}}cs - OrderLineItem.getPrice() 메소드를 구현했으므로 Order에 전체 주문 가격을 구할 수 있는 메서드를 추가할 수 있다.
- Order.getPrice() 메소드는 주문 항목들의 전체 가격을 더한 금액을 반환한다.
12345678910Order.java
public Money getPrice() {Money result = new Money(0);for(OrderLineItem item : lineItems) {result = result.add(item.getPrice());}return result;}cs - 고객의 주문 한도액을 초과하지 않는 정상적인 주문 처리 시나리오를 테스트했으므로 이번에는 주문 총액이 고객의 주문 한도액을 초과하는 경우를 테스트해보자.
- 주문 총액이 고객의 주문 한도액을 초과하는 경우 with() 메소드는 OrderLimitExceededException 예외를 던져야 한다.
12345678910111213OrderTest.javapublic class OrderTest {public void testOrderLimitExceed() {try {customer.newOrder("CUST-01-ORDER-01").with("상품1", 20).with("상품2", 50);fail();} catch (OrderLimitExceededException ex) {assertTrue(true);}}}cs - 테스트가 정상적으로 통과하는 것을 알 수 있다.
- 다음은 주문 시에 동일한 상품을 두 번으로 나누어서 구매하는 경우를 테스트해 보자.
- 다음과 같이 고객이 “상품1”을 두 번의 주문 항목으로 나누어 구매할 경우 주문 가격이 정확한지를 검증하는 테스트를 작성한다.
1234567891011OrderTest.javapublic class OrderTest {public void testOrderWithEqualProductsPrice() throws Exception{Order order = customer.newOrder("CUST-01-ORDER-01").with("상품1", 5).with("상품2", 20).with("상품1", 5);orderRepository.save(order);assertEquals(new Money(110000), order.getPrice());}}cs - 테스트를 통과한다.
- 동일한 상품을 여러 개의 주문 항목으로 나누어도 주문 총액을 정확하게 계산한다.
동일한 상품에 대한 별도의 주문 항목은 어떻게 취급해야 할까?
- 상품이 동일하므로 하나의 주문 항목으로 보아야 할까, 아니면 별도의 독립적인 주문 항목으로 취급해야 할까?
- 고민할 필요 없다. 고객에게 물어보면 금방 답이 나오게 된다.
- 주문 업무를 담당하는 고객에게 물어보니 동일한 상품을 나누어 요청하더라도 업무 상으로는 이들을 취합하여 동일한 주문 항목으로 처리한다고 한다.
- 새로운 도메인 규칙을 알게 되었으니 구현에 앞서 주문 항목의 개수를 검증하기 위한 테스트를 작성하자.
1234567891011OrderTest.javapublic class OrderTest {public void testOrdreLineItems() throws Exception {Order order = customer.newOrder("CUST-01-ORDER-01").with("상품1", 5).with("상품2", 20).with("상품1", 5);orderRepository.save(order);assertEquals(2,order.getOrderLineItemSize());}}cs Order 클래스에 주문 항목의 개수를 반환하는 getOrderLineItemSize() 메소드를 추가한다.
123456Order.javapublic class Order {public int getOrderLineItemSize() {return lineItems.size();}}cs - 테스트는 실패한다.
- 동일한 상품이더라도 개별적으로 추가되는 경우에는 별도의 주문 항목으로 취급하는 것 같다.
- 요구사항의 변덕은 비일비재하나 테스트가 존재하면 코드의 어떤 부분이 망가져 버렸는지 즉각적으로 피드백받을 수 있다.
- 기존 코드가 망가지는 것을 막을 수는 없더라도 망가졌을 때 비상 경고음이 울리도록 조치를 취해 두는 것이 여러모로 안전하다. 회귀 테스트를 믿자.
Order의 with() 메소드에서 이미 등록된 상품을 주문하면 두 주문 항목을 합치도록 해야 할 것 같다.
123456789101112131415161718public class Order {private Order with(OrderLineItem lineItem) throws OrderLimitExceededException {if (isExceedLimit(customer, lineItem)) {throw new OrderLimitExceededException();}for(OrderLineItem item : lineItems) {if (item.isProductEqual(lineItem)) {item.merge(lineItem);return this;}}lineItems.add(lineItem);return this;}}cs - 주문 항목을 추가할 때 OrderLineItem.isProducEqual() 메소드를 호출하여 현재까지 등록된 주문 항목 내에 동일한 상품에 대한 주문 정보가 있는지 체크한다
- . 존재할 경우 하나의 주문 항목으로 병합하도록 OrderLineItem.merge() 메소드를 호출한다.
- 이제 OrderLineItem에 isProducEqual() 메소드와 merge() 메서드를 구현하자.
12345678910public class OrderLineItem {public boolean isProductEqual(OrderLineItem lineItem) {return product == lineItem.product;}public OrderLineItem merge(OrderLineItem lineItem) {quantity += lineItem.quantity;return this;}}cs - 이제 테스트는 성공한다.
참조
'JAVA > DDD' 카테고리의 다른 글
DDD - Dependency Injection과 AOP 1부 (0) 2021.01.11 AGGREGATE와 REPOSITORY 5부 (0) 2021.01.08 Aggregate와 Repository 3부 (0) 2020.12.30 AGGREGATE와 REPOSITORY 2부 (0) 2020.12.29 AGGREGATE와 REPOSITORY 1부 (0) 2020.12.28