먼저 기존의 전략 패턴을 사용한 주문 로직을 살펴보겠다
기존 코드
public interface OrderStrategy<T extends CreateOrderRequest> {
Order createOrder(T orderRequest, Member member, Address address) throws CustomException;
}
@Component
@RequiredArgsConstructor
public class CartOrderStrategy implements OrderStrategy<OrderByCartRequest>{
private final CartItemRepository cartItemRepository;
private final OrderItemFactory orderItemFactory;
private final ProductVariantService productVariantService;
@Override
public Order createOrder(OrderByCartRequest request, Member member, Address address) throws CustomException {
Order order = Order.of(request, member, address);
for (OrderByCartRequest.CartItemRequest cartItemRequest : request.getCartItems()) {
CartItem cartItem = cartItemRepository.findByIdWithStore(cartItemRequest.getCartItemId())
.orElseThrow(() -> new CustomException(ErrorCode.CART_ITEM_NOT_FOUND));
ProductVariant productVariant = productVariantService.reduceStockForOrder(cartItem.getProductVariant().getId(), cartItem.getQuantity());
OrderItem orderItem = orderItemFactory.createOrderItem(order, productVariant, member, cartItem.getQuantity(), cartItemRequest.getCouponId());
order.addOrderItem(orderItem);
cartItemRepository.delete(cartItem);
}
return order;
}
}
@Component
@RequiredArgsConstructor
public class ProductOrderStrategy implements OrderStrategy<OrderByProductRequest> {
private final ProductVariantService productVariantService;
private final OrderItemFactory orderItemFactory;
@Override
public Order createOrder(OrderByProductRequest request, Member member, Address address) throws CustomException {
Order order = Order.of(request, member, address);
ProductVariant productVariant = productVariantService.reduceStockForOrder(request.getProductVariantId(), request.getQuantity());
OrderItem orderItem = orderItemFactory.createOrderItem(order, productVariant, member, request.getQuantity(), request.getCouponId());
order.addOrderItem(orderItem);
return order;
}
}
OrderStrategy 인터페이스를 상속하여 장바구니에서 주문, 상품을 직접 주문하는 전략들이 있다
코드를 보면 중복되는 부분이 많다
- 주문 생성
- 상품 재고, 판매량 변경
- 주문 상품 생성
- 주문 -> 주문 상품 추가
이 공통되는 부분들을 하나로 합쳐보겠다
리팩토링
공통코드 통합
이제 공통된 부분을 하나로 합쳐주어야하는데 문제가있다
3번 주문 상품을 생성하는것이 문제다 왜냐
상품을 직접 주문할땐 하나의 OrderItem만 생성하면되지만
장바구니 상품을 주문할땐 여러건의 OrderItem을 생성해주어야 한다
CartOrderStrategy.class를 보면 알겠지만 for문을 돌려 cartItem에서 OrderItem을 생성해주고있다
각각의 Request를 다시 살펴보겠다
@Getter
public class OrderByCartRequest extends CreateOrderRequest{
@NotEmpty(message = "상품을 하나이상 선택해주세요.")
private final List<CartItemRequest> cartItems;
public OrderByCartRequest(Long addressId, Payment payment, String orderMessage, List<CartItemRequest> cartItems) {
super(addressId, payment, orderMessage);
this.cartItems = cartItems;
}
@Getter
@AllArgsConstructor
public static class CartItemRequest {
private final Long cartItemId;
private final Long couponId;
}
}
OrderByCartReqeust에선 CartItemRequest 클래스에 cartIemId와 couponId를 List 형태로 받고있고
수량을 따로 받지않고 장바구니에 추가한 수량을 사용하여 주문한다
public class OrderByProductRequest extends CreateOrderRequest{
@NotNull(message = "상품을 선택해주세요.")
private final Long productVariantId;
@Min(value = 1, message = "수량은 1개 이상이어야 합니다.")
private final int quantity;
private final Long couponId;
public OrderByProductRequest(Long addressId, Payment payment, String orderMessage, Long productVariantId, int quantity, Long couponId) {
super(addressId, payment, orderMessage);
this.productVariantId = productVariantId;
this.quantity = quantity;
this.couponId = couponId;
}
}
OrderByProductReqeust에선 단건 상품 주문이기때문에 productVariantId와 quantity, couponId를 하나씩 받고있다
상품의 재고와 판매량을 변경하기위해선 productVariantId와 quantity가 필요하다
OrderByProductReqeust 에선 바로 Id와 quantity를 가져올수있지만
OrderByCartReqeust 에선 CartItem을 조회한후 productVariantId와 quantity를 가져와야한다
또 OrderItemFactory에서 OrderItem을 생성할때 쿠폰사용 유무를 체크하고 할인 금액을 계산하기때문에 couponId로 필요하다
아.. 머리가 너무 아프다
GPT의 도움을 받아보았다
AbstractOrderStrategy.class
@RequiredArgsConstructor
public abstract class AbstractOrderStrategy<T extends CreateOrderRequest> implements OrderStrategy<T> {
protected final ProductVariantService productVariantService;
protected final OrderItemFactory orderItemFactory;
@Override
public Order createOrder(T orderRequest, Member member, Address address) throws CustomException {
Order order = Order.of(orderRequest, member, address);
List<OrderItemData> orderItemDataList = getOrderItemData(orderRequest);
for (OrderItemData orderItemData : orderItemDataList) {
ProductVariant productVariant = productVariantService.reduceStockForOrder(orderItemData.getProductVariantId(), orderItemData.getQuantity());
OrderItem orderItem = orderItemFactory.createOrderItem(order, productVariant, member,
orderItemData.getQuantity(), orderItemData.getCouponId());
order.addOrderItem(orderItem);
doAfter(orderRequest);
}
return order;
}
protected abstract List<OrderItemData> getOrderItemData(T request) throws CustomException;
protected abstract void doAfter(T request);
@Getter
@AllArgsConstructor
protected static class OrderItemData {
private final Long productVariantId;
private final int quantity;
private final Long couponId;
public static OrderItemData from(Long productVariantId, int quantity, Long couponId) {
return new OrderItemData(
productVariantId,
quantity,
couponId
);
}
}
}
OrderStrategy 인터페이스를 상속받는 추상 클래스를 생성하였다
가장 머리가 아팠던 productVariantId, quantity, couponId를 어떻게 가져올것인가를 내부 정적 클래스를 사용하여 해결하였다
getOrderItemData()을 각각의 strategy에서 구현하여 List로 받아오는 방식이다
그리고 장바구니 주문시 후처리를 위해 doAfter()를 구현하게 하게하였다
또 현재 주문을 생성하는 정적 팩토리 메서드가 request별로 나뉘어있다 이것도 하나로 통합시켜주겠다
Order.class
/* 상품 주문 */
public static Order of(OrderByProductRequest request, Member member, Address address) {
return new Order(
member,
address,
request.getPayment(),
(request.getPayment().equals(Payment.TRANSFER) ? OrderStatus.PENDING : OrderStatus.PAID),
request.getOrderMessage()
);
}
/* 장바구니 주문 */
public static Order of(OrderByCartRequest request, Member member, Address address) {
return new Order(
member,
address,
request.getPayment(),
(request.getPayment().equals(Payment.TRANSFER) ? OrderStatus.PENDING : OrderStatus.PAID),
request.getOrderMessage()
);
}
먼저 Order 엔티티에 Order를 생성하는 정적 팩토리 메서드가 두개로 분리되어있다 제네릭을 사용하니 하나로 합친다
/* 주문 생성 */
public static <T extends CreateOrderRequest> Order of(T request, Member member, Address address) {
return new Order(
member,
address,
request.getPayment(),
(request.getPayment().equals(Payment.TRANSFER) ? OrderStatus.PENDING : OrderStatus.PAID),
request.getOrderMessage()
);
}
간단하게 하나로 만들어 주었다
이어서 바로 각각의 Strategy를 다시 구현해보겠다
@Component
public class ProductOrderStrategy extends AbstractOrderStrategy<OrderByProductRequest> {
public ProductOrderStrategy(ProductVariantService productVariantService, OrderItemFactory orderItemFactory) {
super(productVariantService, orderItemFactory);
}
@Override
protected List<OrderItemData> getOrderItemData(OrderByProductRequest request) {
return List.of(OrderItemData.from(
request.getProductVariantId(),
request.getQuantity(),
request.getCouponId()
));
}
@Override
protected void doAfter(OrderByProductRequest request) {}
}
ProductOrderStrategy는 간단하게 request에서 바로 값을 가져와 List를 반환해주면 끝이다
상품을 직접 주문하는 경우엔 후처리를 하지않으니 그냥 놔둔다
@Component
public class CartOrderStrategy extends AbstractOrderStrategy<OrderByCartRequest>{
private final CartItemRepository cartItemRepository;
public CartOrderStrategy(ProductVariantService productVariantService, OrderItemFactory orderItemFactory, CartItemRepository cartItemRepository) {
super(productVariantService, orderItemFactory);
this.cartItemRepository = cartItemRepository;
}
@Override
protected List<AbstractOrderStrategy.OrderItemData> getOrderItemData(OrderByCartRequest request) throws CustomException {
List<AbstractOrderStrategy.OrderItemData> orderItemDataList = new ArrayList<>();
for (OrderByCartRequest.CartItemRequest cartItemRequest : request.getCartItems()) {
CartItem cartItem = cartItemRepository.findByIdWithStore(cartItemRequest.getCartItemId())
.orElseThrow(() -> new CustomException(ErrorCode.CART_ITEM_NOT_FOUND));
OrderItemData orderItemData = OrderItemData.from(
cartItem.getProductVariant().getId(),
cartItem.getQuantity(),
cartItemRequest.getCouponId()
);
orderItemDataList.add(orderItemData);
}
return orderItemDataList;
}
@Override
protected void doAfter(OrderByCartRequest request) {
request.getCartItems().forEach(cartItemRequest ->
cartItemRepository.deleteById(cartItemRequest.getCartItemId()));
}
}
CartOrderStrategy는 CartItem을 먼저 조회한 후에 OrderItemData에 맞게 변환 후 반환한다
그리고 doAfter에서 장바구니에 상품들을 삭제시켜주면 완료이다
먼저 기존의 전략 패턴을 사용한 주문 로직을 살펴보겠다
기존 코드
public interface OrderStrategy<T extends CreateOrderRequest> {
Order createOrder(T orderRequest, Member member, Address address) throws CustomException;
}
@Component
@RequiredArgsConstructor
public class CartOrderStrategy implements OrderStrategy<OrderByCartRequest>{
private final CartItemRepository cartItemRepository;
private final OrderItemFactory orderItemFactory;
private final ProductVariantService productVariantService;
@Override
public Order createOrder(OrderByCartRequest request, Member member, Address address) throws CustomException {
Order order = Order.of(request, member, address);
for (OrderByCartRequest.CartItemRequest cartItemRequest : request.getCartItems()) {
CartItem cartItem = cartItemRepository.findByIdWithStore(cartItemRequest.getCartItemId())
.orElseThrow(() -> new CustomException(ErrorCode.CART_ITEM_NOT_FOUND));
ProductVariant productVariant = productVariantService.reduceStockForOrder(cartItem.getProductVariant().getId(), cartItem.getQuantity());
OrderItem orderItem = orderItemFactory.createOrderItem(order, productVariant, member, cartItem.getQuantity(), cartItemRequest.getCouponId());
order.addOrderItem(orderItem);
cartItemRepository.delete(cartItem);
}
return order;
}
}
@Component
@RequiredArgsConstructor
public class ProductOrderStrategy implements OrderStrategy<OrderByProductRequest> {
private final ProductVariantService productVariantService;
private final OrderItemFactory orderItemFactory;
@Override
public Order createOrder(OrderByProductRequest request, Member member, Address address) throws CustomException {
Order order = Order.of(request, member, address);
ProductVariant productVariant = productVariantService.reduceStockForOrder(request.getProductVariantId(), request.getQuantity());
OrderItem orderItem = orderItemFactory.createOrderItem(order, productVariant, member, request.getQuantity(), request.getCouponId());
order.addOrderItem(orderItem);
return order;
}
}
OrderStrategy 인터페이스를 상속하여 장바구니에서 주문, 상품을 직접 주문하는 전략들이 있다
코드를 보면 중복되는 부분이 많다
- 주문 생성
- 상품 재고, 판매량 변경
- 주문 상품 생성
- 주문 -> 주문 상품 추가
이 공통되는 부분들을 하나로 합쳐보겠다
리팩토링
공통코드 통합
이제 공통된 부분을 하나로 합쳐주어야하는데 문제가있다
3번 주문 상품을 생성하는것이 문제다 왜냐
상품을 직접 주문할땐 하나의 OrderItem만 생성하면되지만
장바구니 상품을 주문할땐 여러건의 OrderItem을 생성해주어야 한다
CartOrderStrategy.class를 보면 알겠지만 for문을 돌려 cartItem에서 OrderItem을 생성해주고있다
각각의 Request를 다시 살펴보겠다
@Getter
public class OrderByCartRequest extends CreateOrderRequest{
@NotEmpty(message = "상품을 하나이상 선택해주세요.")
private final List<CartItemRequest> cartItems;
public OrderByCartRequest(Long addressId, Payment payment, String orderMessage, List<CartItemRequest> cartItems) {
super(addressId, payment, orderMessage);
this.cartItems = cartItems;
}
@Getter
@AllArgsConstructor
public static class CartItemRequest {
private final Long cartItemId;
private final Long couponId;
}
}
OrderByCartReqeust에선 CartItemRequest 클래스에 cartIemId와 couponId를 List 형태로 받고있고
수량을 따로 받지않고 장바구니에 추가한 수량을 사용하여 주문한다
public class OrderByProductRequest extends CreateOrderRequest{
@NotNull(message = "상품을 선택해주세요.")
private final Long productVariantId;
@Min(value = 1, message = "수량은 1개 이상이어야 합니다.")
private final int quantity;
private final Long couponId;
public OrderByProductRequest(Long addressId, Payment payment, String orderMessage, Long productVariantId, int quantity, Long couponId) {
super(addressId, payment, orderMessage);
this.productVariantId = productVariantId;
this.quantity = quantity;
this.couponId = couponId;
}
}
OrderByProductReqeust에선 단건 상품 주문이기때문에 productVariantId와 quantity, couponId를 하나씩 받고있다
상품의 재고와 판매량을 변경하기위해선 productVariantId와 quantity가 필요하다
OrderByProductReqeust 에선 바로 Id와 quantity를 가져올수있지만
OrderByCartReqeust 에선 CartItem을 조회한후 productVariantId와 quantity를 가져와야한다
또 OrderItemFactory에서 OrderItem을 생성할때 쿠폰사용 유무를 체크하고 할인 금액을 계산하기때문에 couponId로 필요하다
아.. 머리가 너무 아프다
GPT의 도움을 받아보았다
AbstractOrderStrategy.class
@RequiredArgsConstructor
public abstract class AbstractOrderStrategy<T extends CreateOrderRequest> implements OrderStrategy<T> {
protected final ProductVariantService productVariantService;
protected final OrderItemFactory orderItemFactory;
@Override
public Order createOrder(T orderRequest, Member member, Address address) throws CustomException {
Order order = Order.of(orderRequest, member, address);
List<OrderItemData> orderItemDataList = getOrderItemData(orderRequest);
for (OrderItemData orderItemData : orderItemDataList) {
ProductVariant productVariant = productVariantService.reduceStockForOrder(orderItemData.getProductVariantId(), orderItemData.getQuantity());
OrderItem orderItem = orderItemFactory.createOrderItem(order, productVariant, member,
orderItemData.getQuantity(), orderItemData.getCouponId());
order.addOrderItem(orderItem);
doAfter(orderRequest);
}
return order;
}
protected abstract List<OrderItemData> getOrderItemData(T request) throws CustomException;
protected abstract void doAfter(T request);
@Getter
@AllArgsConstructor
protected static class OrderItemData {
private final Long productVariantId;
private final int quantity;
private final Long couponId;
public static OrderItemData from(Long productVariantId, int quantity, Long couponId) {
return new OrderItemData(
productVariantId,
quantity,
couponId
);
}
}
}
OrderStrategy 인터페이스를 상속받는 추상 클래스를 생성하였다
가장 머리가 아팠던 productVariantId, quantity, couponId를 어떻게 가져올것인가를 내부 정적 클래스를 사용하여 해결하였다
getOrderItemData()을 각각의 strategy에서 구현하여 List로 받아오는 방식이다
그리고 장바구니 주문시 후처리를 위해 doAfter()를 구현하게 하게하였다
또 현재 주문을 생성하는 정적 팩토리 메서드가 request별로 나뉘어있다 이것도 하나로 통합시켜주겠다
Order.class
/* 상품 주문 */
public static Order of(OrderByProductRequest request, Member member, Address address) {
return new Order(
member,
address,
request.getPayment(),
(request.getPayment().equals(Payment.TRANSFER) ? OrderStatus.PENDING : OrderStatus.PAID),
request.getOrderMessage()
);
}
/* 장바구니 주문 */
public static Order of(OrderByCartRequest request, Member member, Address address) {
return new Order(
member,
address,
request.getPayment(),
(request.getPayment().equals(Payment.TRANSFER) ? OrderStatus.PENDING : OrderStatus.PAID),
request.getOrderMessage()
);
}
먼저 Order 엔티티에 Order를 생성하는 정적 팩토리 메서드가 두개로 분리되어있다 제네릭을 사용하니 하나로 합친다
/* 주문 생성 */
public static <T extends CreateOrderRequest> Order of(T request, Member member, Address address) {
return new Order(
member,
address,
request.getPayment(),
(request.getPayment().equals(Payment.TRANSFER) ? OrderStatus.PENDING : OrderStatus.PAID),
request.getOrderMessage()
);
}
간단하게 하나로 만들어 주었다
이어서 바로 각각의 Strategy를 다시 구현해보겠다
@Component
public class ProductOrderStrategy extends AbstractOrderStrategy<OrderByProductRequest> {
public ProductOrderStrategy(ProductVariantService productVariantService, OrderItemFactory orderItemFactory) {
super(productVariantService, orderItemFactory);
}
@Override
protected List<OrderItemData> getOrderItemData(OrderByProductRequest request) {
return List.of(OrderItemData.from(
request.getProductVariantId(),
request.getQuantity(),
request.getCouponId()
));
}
@Override
protected void doAfter(OrderByProductRequest request) {}
}
ProductOrderStrategy는 간단하게 request에서 바로 값을 가져와 List를 반환해주면 끝이다
상품을 직접 주문하는 경우엔 후처리를 하지않으니 그냥 놔둔다
@Component
public class CartOrderStrategy extends AbstractOrderStrategy<OrderByCartRequest>{
private final CartItemRepository cartItemRepository;
public CartOrderStrategy(ProductVariantService productVariantService, OrderItemFactory orderItemFactory, CartItemRepository cartItemRepository) {
super(productVariantService, orderItemFactory);
this.cartItemRepository = cartItemRepository;
}
@Override
protected List<AbstractOrderStrategy.OrderItemData> getOrderItemData(OrderByCartRequest request) throws CustomException {
List<AbstractOrderStrategy.OrderItemData> orderItemDataList = new ArrayList<>();
for (OrderByCartRequest.CartItemRequest cartItemRequest : request.getCartItems()) {
CartItem cartItem = cartItemRepository.findByIdWithStore(cartItemRequest.getCartItemId())
.orElseThrow(() -> new CustomException(ErrorCode.CART_ITEM_NOT_FOUND));
OrderItemData orderItemData = OrderItemData.from(
cartItem.getProductVariant().getId(),
cartItem.getQuantity(),
cartItemRequest.getCouponId()
);
orderItemDataList.add(orderItemData);
}
return orderItemDataList;
}
@Override
protected void doAfter(OrderByCartRequest request) {
request.getCartItems().forEach(cartItemRequest ->
cartItemRepository.deleteById(cartItemRequest.getCartItemId()));
}
}
CartOrderStrategy는 CartItem을 먼저 조회한 후에 OrderItemData에 맞게 변환 후 반환한다
그리고 doAfter에서 장바구니에 상품들을 삭제시켜주면 완료이다