Domain Driven Development 01
This commit is contained in:
54
pom.xml
Normal file
54
pom.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.4.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>demo</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>demo</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
13
src/main/java/com/example/demo/DemoApplication.java
Normal file
13
src/main/java/com/example/demo/DemoApplication.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.example.demo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
21
src/main/java/com/example/demo/common/ErrorMessages.java
Normal file
21
src/main/java/com/example/demo/common/ErrorMessages.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.example.demo.common;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum ErrorMessages {
|
||||
|
||||
ALREADY_PAID("already paid"),
|
||||
NOT_READY("not preparing state"),
|
||||
NOT_SHIPPED("not shipped state"),
|
||||
NOT_DELIVERING("not delivering state"),
|
||||
ALREADY_SHIPPED("already shipped"),
|
||||
NO_ORDER_LINE("no OrderLine"),
|
||||
NO_SHIPPING_INFO("no Shipping Information");
|
||||
|
||||
String msg;
|
||||
|
||||
ErrorMessages(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
}
|
||||
19
src/main/java/com/example/demo/order/Address.java
Normal file
19
src/main/java/com/example/demo/order/Address.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.example.demo.order;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class Address {
|
||||
private String address1;
|
||||
private String address2;
|
||||
private String zipCode;
|
||||
|
||||
public boolean isBlank() {
|
||||
return StringUtils.isEmpty(address1) || StringUtils.isEmpty(address2) || StringUtils.isEmpty(zipCode);
|
||||
}
|
||||
}
|
||||
20
src/main/java/com/example/demo/order/Money.java
Normal file
20
src/main/java/com/example/demo/order/Money.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.example.demo.order;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class Money {
|
||||
private int value;
|
||||
|
||||
public Money(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Money add(Money money) {
|
||||
return new Money(this.value + money.getValue());
|
||||
}
|
||||
|
||||
public Money multiply(Money money) {
|
||||
return new Money(this.value * money.getValue());
|
||||
}
|
||||
}
|
||||
121
src/main/java/com/example/demo/order/Order.java
Normal file
121
src/main/java/com/example/demo/order/Order.java
Normal file
@@ -0,0 +1,121 @@
|
||||
package com.example.demo.order;
|
||||
|
||||
import com.example.demo.common.ErrorMessages;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 주문 정보
|
||||
*/
|
||||
@Getter
|
||||
public class Order {
|
||||
|
||||
private OrderNo id;
|
||||
private List<OrderLine> orderLines;
|
||||
private OrderState state;
|
||||
private ShippingInfo shippingInfo;
|
||||
private Money totalAmounts;
|
||||
|
||||
public Order(List<OrderLine> orderLines, ShippingInfo shippingInfo) {
|
||||
id = new OrderNo(UUID.randomUUID().toString());
|
||||
setOrderLines(orderLines);
|
||||
setShippingInfo(shippingInfo);
|
||||
state = OrderState.PAYMENT_WAITING;
|
||||
}
|
||||
|
||||
private void setOrderLines(List<OrderLine> orderLines) {
|
||||
if (isEmptyLOrderLines(orderLines)) {
|
||||
throw new IllegalStateException(ErrorMessages.NO_ORDER_LINE.getMsg());
|
||||
}
|
||||
|
||||
this.orderLines = orderLines;
|
||||
calculateTotalAmounts();
|
||||
}
|
||||
|
||||
private boolean isEmptyLOrderLines(List<OrderLine> orderLines) {
|
||||
return orderLines == null || orderLines.isEmpty();
|
||||
}
|
||||
|
||||
private void setShippingInfo(ShippingInfo shippingInfo) {
|
||||
if (isBlankShippingInfo(shippingInfo)) {
|
||||
throw new IllegalStateException(ErrorMessages.NO_SHIPPING_INFO.getMsg());
|
||||
}
|
||||
|
||||
this.shippingInfo = shippingInfo;
|
||||
}
|
||||
|
||||
private boolean isBlankShippingInfo(ShippingInfo shippingInfo) {
|
||||
return shippingInfo == null || shippingInfo.isBlank();
|
||||
}
|
||||
|
||||
public void payment() {
|
||||
if (state != OrderState.PAYMENT_WAITING) {
|
||||
throw new IllegalStateException(ErrorMessages.ALREADY_PAID.getMsg());
|
||||
}
|
||||
|
||||
this.state = OrderState.PREPARING;
|
||||
}
|
||||
|
||||
public void shipped() {
|
||||
if (state != OrderState.PREPARING) {
|
||||
throw new IllegalStateException(ErrorMessages.NOT_READY.getMsg());
|
||||
}
|
||||
|
||||
this.state = OrderState.SHIPPED;
|
||||
}
|
||||
|
||||
public void startDelivery() {
|
||||
if (state != OrderState.SHIPPED) {
|
||||
throw new IllegalStateException(ErrorMessages.NOT_SHIPPED.getMsg());
|
||||
}
|
||||
|
||||
this.state = OrderState.DELIVERING;
|
||||
}
|
||||
|
||||
public void completeDelivery() {
|
||||
if (state != OrderState.DELIVERING) {
|
||||
throw new IllegalStateException(ErrorMessages.NOT_DELIVERING.getMsg());
|
||||
}
|
||||
|
||||
this.state = OrderState.DELIVERING_COMPLETED;
|
||||
}
|
||||
|
||||
public void changeShippingInfo(ShippingInfo newShippingInfo) {
|
||||
verifyBeforeShipping();
|
||||
this.shippingInfo = newShippingInfo;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
verifyBeforeShipping();
|
||||
this.state = OrderState.CANCELED;
|
||||
}
|
||||
|
||||
private void verifyBeforeShipping() {
|
||||
if (state != OrderState.PAYMENT_WAITING && state != OrderState.PREPARING) {
|
||||
throw new IllegalStateException(ErrorMessages.ALREADY_SHIPPED.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateTotalAmounts() {
|
||||
this.totalAmounts = new Money(orderLines.stream()
|
||||
.mapToInt(x -> x.getAmounts().getValue()).sum());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (this == obj) return true;
|
||||
if (obj.getClass() != Order.class) return false;
|
||||
if (this.id.getValue().isEmpty()) return false;
|
||||
return this.id.equals(((Order) obj).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int hash = 1;
|
||||
return prime * hash + (id.getValue().isEmpty()? 0 : id.hashCode());
|
||||
}
|
||||
}
|
||||
26
src/main/java/com/example/demo/order/OrderLine.java
Normal file
26
src/main/java/com/example/demo/order/OrderLine.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.example.demo.order;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class OrderLine {
|
||||
private Product product;
|
||||
private Money price;
|
||||
private int quantity;
|
||||
private Money amounts;
|
||||
|
||||
public OrderLine(Product product, Money price, int quantity) {
|
||||
this.product = product;
|
||||
this.price = price;
|
||||
this.quantity = quantity;
|
||||
this.amounts = calculateAmounts();
|
||||
}
|
||||
|
||||
private Money calculateAmounts() {
|
||||
return new Money(price.getValue() * quantity);
|
||||
}
|
||||
|
||||
public Money getAmounts() {
|
||||
return amounts;
|
||||
}
|
||||
}
|
||||
10
src/main/java/com/example/demo/order/OrderNo.java
Normal file
10
src/main/java/com/example/demo/order/OrderNo.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.example.demo.order;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class OrderNo {
|
||||
private String value;
|
||||
}
|
||||
28
src/main/java/com/example/demo/order/OrderState.java
Normal file
28
src/main/java/com/example/demo/order/OrderState.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package com.example.demo.order;
|
||||
|
||||
/**
|
||||
* 주문 상태
|
||||
* 결제 대기 -> 준비중 -> 배송준비 -> 배송중 -> 배송완료 // 취소됨
|
||||
*/
|
||||
public enum OrderState {
|
||||
PAYMENT_WAITING {
|
||||
public boolean isShippingChangeable() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
PREPARING {
|
||||
public boolean isShippingChangeable() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
SHIPPED,
|
||||
DELIVERING,
|
||||
DELIVERING_COMPLETED,
|
||||
CANCELED;
|
||||
|
||||
public boolean isShippingChangeable() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
11
src/main/java/com/example/demo/order/Product.java
Normal file
11
src/main/java/com/example/demo/order/Product.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.example.demo.order;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class Product {
|
||||
private String id;
|
||||
private ProductType type;
|
||||
}
|
||||
8
src/main/java/com/example/demo/order/ProductType.java
Normal file
8
src/main/java/com/example/demo/order/ProductType.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package com.example.demo.order;
|
||||
|
||||
public enum ProductType {
|
||||
MOUSE,
|
||||
KEYBOARD,
|
||||
MONITOR,
|
||||
SPEAKER
|
||||
}
|
||||
18
src/main/java/com/example/demo/order/Receiver.java
Normal file
18
src/main/java/com/example/demo/order/Receiver.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.example.demo.order;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class Receiver {
|
||||
private String name;
|
||||
private String phone;
|
||||
|
||||
public boolean isBlank() {
|
||||
return StringUtils.isEmpty(name) || StringUtils.isEmpty(phone);
|
||||
}
|
||||
}
|
||||
22
src/main/java/com/example/demo/order/ShippingInfo.java
Normal file
22
src/main/java/com/example/demo/order/ShippingInfo.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.example.demo.order;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 배송지 정보
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ShippingInfo {
|
||||
private Receiver receiver;
|
||||
private Address address;
|
||||
|
||||
public boolean isBlank() {
|
||||
return receiver == null || receiver.isBlank() || address == null || address.isBlank();
|
||||
}
|
||||
}
|
||||
1
src/main/resources/application.properties
Normal file
1
src/main/resources/application.properties
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
164
src/test/java/com/example/demo/OrderTests.java
Normal file
164
src/test/java/com/example/demo/OrderTests.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.demo.order.*;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest
|
||||
public class OrderTests {
|
||||
|
||||
private ProductType[] productTypes = ProductType.values();
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void noShippingInfo() {
|
||||
|
||||
List<OrderLine> orderLines = createOrderLines(3);
|
||||
ShippingInfo shippingInfo = new ShippingInfo();
|
||||
|
||||
Order order = new Order(orderLines, shippingInfo);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void noOrderLine() {
|
||||
|
||||
List<OrderLine> orderLines = createOrderLines(0);
|
||||
ShippingInfo shippingInfo = createShippingInfo();
|
||||
|
||||
Order order = new Order(orderLines, shippingInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void order() {
|
||||
// 주문
|
||||
Order order = createOrder();
|
||||
assertThat(order.getState()).isEqualTo(OrderState.PAYMENT_WAITING);
|
||||
|
||||
// 금액 지불
|
||||
order.payment();
|
||||
assertThat(order.getState()).isEqualTo(OrderState.PREPARING);
|
||||
|
||||
// 출고
|
||||
order.shipped();
|
||||
assertThat(order.getState()).isEqualTo(OrderState.SHIPPED);
|
||||
|
||||
// 배송 시작
|
||||
order.startDelivery();
|
||||
assertThat(order.getState()).isEqualTo(OrderState.DELIVERING);
|
||||
|
||||
// 배송 완료
|
||||
order.completeDelivery();
|
||||
assertThat(order.getState()).isEqualTo(OrderState.DELIVERING_COMPLETED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orderCancelBeforePayment() {
|
||||
Order order = createOrder();
|
||||
// 주문 후 취소
|
||||
order.cancel();
|
||||
assertThat(order.getState()).isEqualTo(OrderState.CANCELED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orderCancelBeforeShipping() {
|
||||
Order order = createOrder();
|
||||
order.payment();
|
||||
// 결제 후 취소
|
||||
order.cancel();
|
||||
assertThat(order.getState()).isEqualTo(OrderState.CANCELED);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void failOrderCancelInShipped() {
|
||||
Order order = createOrder();
|
||||
order.payment();
|
||||
order.shipped();
|
||||
order.cancel();
|
||||
assertThat(order.getState()).isEqualTo(OrderState.CANCELED);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void failOrderCancelInDelivering() {
|
||||
Order order = createOrder();
|
||||
order.payment();
|
||||
order.shipped();
|
||||
order.startDelivery();
|
||||
order.cancel();
|
||||
assertThat(order.getState()).isEqualTo(OrderState.CANCELED);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void failOrderCancelInDeliveringComplete() {
|
||||
Order order = createOrder();
|
||||
order.payment();
|
||||
order.shipped();
|
||||
order.startDelivery();
|
||||
order.completeDelivery();
|
||||
order.cancel();
|
||||
assertThat(order.getState()).isEqualTo(OrderState.CANCELED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeShippingInfo() {
|
||||
Order order = createOrder();
|
||||
|
||||
ShippingInfo info = order.getShippingInfo();
|
||||
ShippingInfo created = createShippingInfo();
|
||||
|
||||
assertThat(info.getReceiver().equals(created.getReceiver())).isEqualTo(true);
|
||||
assertThat(info.getAddress().equals(created.getAddress())).isEqualTo(true);
|
||||
|
||||
order.changeShippingInfo(ShippingInfo.builder()
|
||||
.receiver(new Receiver("KIM", "010-1234-5678"))
|
||||
.address(new Address("Seoul", "Korea", "12345"))
|
||||
.build());
|
||||
|
||||
info = order.getShippingInfo();
|
||||
assertThat(info.getReceiver().equals(created.getReceiver())).isEqualTo(false);
|
||||
assertThat(info.getAddress().equals(created.getAddress())).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void failChangeShippingInfo() {
|
||||
Order order = createOrder();
|
||||
order.payment();
|
||||
order.shipped();
|
||||
|
||||
order.changeShippingInfo(ShippingInfo.builder()
|
||||
.receiver(new Receiver("KIM", "010-1234-5678"))
|
||||
.address(new Address("Seoul", "Korea", "12345"))
|
||||
.build());
|
||||
}
|
||||
|
||||
public Order createOrder() {
|
||||
List<OrderLine> orderLines = createOrderLines(3);
|
||||
ShippingInfo shippingInfo = createShippingInfo();
|
||||
return new Order(orderLines, shippingInfo);
|
||||
}
|
||||
|
||||
public ShippingInfo createShippingInfo() {
|
||||
return ShippingInfo.builder()
|
||||
.receiver(new Receiver("MIN", "010-1234-5678"))
|
||||
.address(new Address("Incheon", "Korea", "12345"))
|
||||
.build();
|
||||
}
|
||||
|
||||
public List<OrderLine> createOrderLines(int orderLineCount) {
|
||||
|
||||
List<OrderLine> orderLines = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < orderLineCount; i++) {
|
||||
Product product = new Product(UUID.randomUUID().toString(), productTypes[i % productTypes.length]);
|
||||
Money price = new Money(10000 * i);
|
||||
|
||||
orderLines.add(new OrderLine(product, price, i));
|
||||
}
|
||||
|
||||
return orderLines;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user