[Spring] Spring 핵심 원리 : 순수한 Java 애플리케이션 - 주문 도메인
1. 설계
도메인 협력관계

상품 주문
1) 클라이언트는 주문 서비스에 주문 생성을 요청한다.
2) 할인 정책을 적용하기 위해서는 회원 등급이 필요하기 때문에 주문 서비스는 회원 저장소에서 회원 정보를 조회한다.
3) 회원 등급에 따른 할인 여부를 할인 정책에 위임한다.
4) 할인 결과를 포함한 주문 정보를 주문 저장소에 저장한다.
주문 내역 조회
1) 클라이언트는 주문 서비스에 주문 내역을 요청한다.
2) 주문 서비스는 주문 번호를 통해 주문 저장소에서 주문 내역을 조회한다.
3) 조회한 주문 내역을 반환한다.
클래스 다이어그램

2. 개발
주문 엔티티
public class Order {
private Long orderId;
private Long memberId;
private String itemName;
private int itemPrice;
private int discountPrice;
public Order(Long memberId, String itemName, int itemPrice, int discountPrice) {
this.memberId = memberId;
this.itemName = itemName;
this.itemPrice = itemPrice;
this.discountPrice = discountPrice;
}
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public Long getMemberId() {
return memberId;
}
public String getItemName() {
return itemName;
}
public int getItemPrice() {
return itemPrice;
}
public int getDiscountPrice() {
return discountPrice;
}
public int calculate() {
return itemPrice - discountPrice;
}
@Override
public String toString() {
return "Order{" +
"orderId=" + orderId +
", memberId=" + memberId +
", itemName='" + itemName + '\'' +
", itemPrice=" + itemPrice +
", discountPrice=" + discountPrice +
'}';
}
}
할인 정책 인터페이스와 구현체
public interface DiscountPolicy {
int discount(Member member, int itemPrice);
}
// 정액 할인 정책
public class FixDiscountPolicy implements DiscountPolicy {
private final int discountAmount = 1000;
@Override
public int discount(Member member, int itemPrice) {
if (member.getGrade() == Grade.VIP) {
return discountAmount;
} else {
return 0;
}
}
}
// 정율 할인 정책
public class RateDiscountPolicy implements DiscountPolicy {
private final double discountRate = 0.1;
@Override
public int discount(Member member, int itemPrice) {
if (member.getGrade() == Grade.VIP) {
return (int) (itemPrice * discountRate);
} else {
return 0;
}
}
}
discount 메서드는 회원 엔티티와 상품 가격을 매개변수로 받아서 회원 등급이 VIP이면 상품 가격에 따른 할인 가격을 반환하고, VIP가 아니면 0을 반환한다. 회원 등급이 아니라 회원 엔티티 전체를 매개변수로 받는 이유는 확장성 때문이다. 추후 할인 정책을 적용하는 데 있어 회원 등급뿐만 아니라 다른 정보도 필요할 수 있다는 점을 고려한다.
주문 서비스 인터페이스와 구현체
public interface OrderService {
Order createOrder(Long memberId, String itemName, int itemPrice);
Order findOrder(Long orderId);
}
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository = new MemoryOrderRepository();
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
Order order = new Order(memberId, itemName, itemPrice, discountPrice);
orderRepository.saveOrder(order);
return order;
}
@Override
public Order findOrder(Long orderId) {
return orderRepository.findById(orderId);
}
}
클라이언트가 주문 생성을 요청하여 createOrder 메서드가 호출되면 memberRepository를 통해 회원 정보를 조회하고, discountPolicy로 할인 정책을 적용한다. 만약 정률 할인 정책(RateDiscountPolicy)으로 변경된다 해도 createOrder 메서드의 코드는 수정할 필요가 없다. 참조변수 discountPolicy는 인터페이스 타입이기 때문이다. memberRepository와 orderRepository도 마찬가지로 구현체가 변경되어도 메서드 내의 코드는 수정할 필요가 없다.
주문 저장소 인터페이스와 메모리 주문 저장소 구현체
public interface OrderRepository {
void saveOrder(Order order);
Order findById(Long orderId);
}
public class MemoryOrderRepository implements OrderRepository {
private static Map<Long, Order> store = new HashMap<>();
private static long sequence = 0L;
@Override
public void saveOrder(Order order) {
order.setOrderId(++sequence);
store.put(order.getOrderId(), order);
}
@Override
public Order findById(Long orderId) {
return store.get(orderId);
}
}
주문 정보를 저장할 때는 주문 엔티티를 매개변수로 받아서 orderId에 시퀀스를 저장한다. 시퀀스는 주문 정보가 저장될 때 1씩 증가된 후 orderId에 대입된다. 사용자가 orderId를 직접 입력하지 않는다. MemoryMemberRepository의 saveMember 메서드와는 다른 점이다.
3. 실행
public class OrderApp {
public static void main(String[] args) {
MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();
Member savedMember = memberService.signUp(new Member(1L, "memberA", Grade.VIP));
Order savedOrder = orderService.createOrder(savedMember.getMemberId(), "3series", 50000);
Order foundOrder = orderService.findOrder(savedOrder.getOrderId());
System.out.println("savedOrder = " + savedOrder.toString());
System.out.println("foundOrder = " + foundOrder.toString());
}
}
/* 실행결과
savedOrder = Order{orderId=1, memberId=1, itemName='3series', itemPrice=50000, discountPrice=1000}
foundOrder = Order{orderId=1, memberId=1, itemName='3series', itemPrice=50000, discountPrice=1000}
*/