ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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() 메서드는 현재 주문 항목의 가격을 반환하는 메서드로 상품 가격에 상품 수량을 곱한 금액을 반환한다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    OrderLineItem.java
     
    public 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로 상품 명을 검색 키로 사용한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public 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() 메소드는 주문 항목들의 전체 가격을 더한 금액을 반환한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Order.java
    public
     Money getPrice() {
        
        Money result = new Money(0);
     
        for(OrderLineItem item : lineItems) {
            result = result.add(item.getPrice());
        }
     
        return result;
    }
    cs
    • 고객의 주문 한도액을 초과하지 않는 정상적인 주문 처리 시나리오를 테스트했으므로 이번에는 주문 총액이 고객의 주문 한도액을 초과하는 경우를 테스트해보자.
    • 주문 총액이 고객의 주문 한도액을 초과하는 경우 with() 메소드는 OrderLimitExceededException 예외를 던져야 한다.

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    OrderTest.java
    public 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”을 두 번의 주문 항목으로 나누어 구매할 경우 주문 가격이 정확한지를 검증하는 테스트를 작성한다.

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    OrderTest.java
    public 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
    • 테스트를 통과한다. 
    • 동일한 상품을 여러 개의 주문 항목으로 나누어도 주문 총액을 정확하게 계산한다.

     

    동일한 상품에 대한 별도의 주문 항목은 어떻게 취급해야 할까? 

    • 상품이 동일하므로 하나의 주문 항목으로 보아야 할까, 아니면 별도의 독립적인 주문 항목으로 취급해야 할까? 
    • 고민할 필요 없다. 고객에게 물어보면 금방 답이 나오게 된다.
    •  주문 업무를 담당하는 고객에게 물어보니 동일한 상품을 나누어 요청하더라도 업무 상으로는 이들을 취합하여 동일한 주문 항목으로 처리한다고 한다. 
    • 새로운 도메인 규칙을 알게 되었으니 구현에 앞서 주문 항목의 개수를 검증하기 위한 테스트를 작성하자.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    OrderTest.java
    public 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() 메소드를 추가한다.

     

    1
    2
    3
    4
    5
    6
    Order.java
    public class Order {
        public int getOrderLineItemSize() {
            return lineItems.size();
        }
    }
    cs
    • 테스트는 실패한다.
    • 동일한 상품이더라도 개별적으로 추가되는 경우에는 별도의 주문 항목으로 취급하는 것 같다. 
    • 요구사항의 변덕은 비일비재하나 테스트가 존재하면 코드의 어떤 부분이 망가져 버렸는지 즉각적으로 피드백받을 수 있다. 
    • 기존 코드가 망가지는 것을 막을 수는 없더라도 망가졌을 때 비상 경고음이 울리도록 조치를 취해 두는 것이 여러모로 안전하다. 회귀 테스트를 믿자.

     

     

    Order with() 메소드에서 이미 등록된 상품을 주문하면 두 주문 항목을 합치도록 해야 할 것 같다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public 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() 메서드를 구현하자.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class OrderLineItem {
        public boolean isProductEqual(OrderLineItem lineItem) {
            return product == lineItem.product;
        }
        
        public OrderLineItem merge(OrderLineItem lineItem) {
            quantity += lineItem.quantity;
            return this;
        }
    }
    cs

     

    • 이제 테스트는 성공한다.

     

     

     

     

    참조


    이터니티 - Domain-Driven Design의 적용

    '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
Designed by Tistory.