주문 도메인 개발

주문 엔티티 비즈니스 로직 추가

@Entity
@Table(name = "orders")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {

    @Id @GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "delivery_id")
    private Delivery delivery;

    private LocalDateTime orderDate; // 주문 시간

    @Enumerated(EnumType.STRING)
    private OrderStatus status; // 주문 상태 [ORDER, CANCEL]

    /**
     * 연관 관계 편의 메서드
     */
    public void setMember(Member member) {
        this.member = member;
        member.getOrders().add(this);
    }

    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }

    public void setDelivery(Delivery delivery) {
        this.delivery = delivery;
        delivery.setOrder(this);
    }

    /**
     * 생성 메서드
     */
    public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) {
        Order order = new Order();
        order.setMember(member);
        order.setDelivery(delivery);

        for (OrderItem orderItem : orderItems) {
            order.addOrderItem(orderItem);
        }
        
        order.setStatus(OrderStatus.ORDER);
        order.setOrderDate(LocalDateTime.now());

        return order;
    }

    /**
     * 비즈니스 로직
     */
    // 주문 취소
    public void cancel() {
        if (delivery.getStatus() == DeliveryStatus.COMP) {
            throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다.");
        }
        this.setStatus(OrderStatus.CANCEL);

        orderItems.forEach(OrderItem::cancel);
/*
        for (OrderItem orderItem : orderItems) {
            orderItem.cancel();
        }
*/
    }

    /**
     * 조회 로직
     */
    // 전체 주문 가격 조회
    public int getTotalPrice() {
        return orderItems.stream()
                         .mapToInt(OrderItem::getTotalPrice)
                         .sum();
    }
}

생성 메서드

  • 주문 엔티티를 생성할 때 사용한다.

  • 주문 회원, 배송정보, 주문 상품의 정보를 받아서 실제 주문 엔티티를 생성한다.

  • 주문에 대한 변화가 생기면 이 메서드만 변경하면 된다.

주문 취소

  • 주문 취소시 사용한다.

  • 주문 상태를 취소로 변경하고 주문상품에 주문 취소를 알린다.

  • 이미 배송을 완료한 상품이면 예외가 발생한다.

전체 주문 가격 조회

  • 주문 시 사용한 전체 주문 가격을 조회한다.

  • 전체 주문 가격을 알려면 각각의 주문 상품 가격을 알아야 한다.

  • 연관된 주문 상품들의 가격을 조회해서 더한 값을 반환한다.

  • 실무에서는 주로 주문에 전체 주문 가격 필드를 두고 역정규화 한다.

주문 상품 엔티티 비즈니스 로직 추가

생성 메서드

  • 주문 상품, 가격, 수량 정보를 사용해서 주문상품 엔티티를 생성한다.

  • 주문한 수량만큼 상품의 재고를 줄인다.

주문 취소

  • 취소한 주문 수량 만큼 상품의 재고를 증가시킨다.

레포지토리

서비스

주문

  • 주문하는 회원 식별자, 상품 식별자, 주문 수량 정보를 받아서 실제 주문 엔티티를 생성한 후 저장한다.

  • OrderDeliveryOrderItem 필드는 cascade = CascadeType.ALL로 되어있기 때문에 Order만 저장을 해도 Delivery, OrderItem도 같이 저장된다.

  • OrderOrderItem 생성 메서드는 static으로 되어있다. 그런데 다른 누군가가 new로 생성을 해서 setter로 값을 변경하려고 할 수도 있다.

    • 일관성과 유지보수성을 위해 기본 생성자를 막아두는 것이 좋다.

    • JPA는 protected까지 기본 생성자 범위를 허용한다.

취소

  • 주문 식별자를 받아서 주문 엔티티를 조회한 후 주문 엔티티에 주문 취소를 요청한다.

참고 비즈니스 로직 대부분이 엔티티에 있다. 서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할을 한다. 이렇게 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 도메인 모델 패턴이라고 한다. 반대로 엔티티에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것을 트랜잭션 스크립트 패턴이라 한다.

테스트 코드

Last updated