JAVA/DDD
AGGREGATE와 REPOSITORY 4부
100win10
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 |
- 이제 테스트는 성공한다.
참조