Facade Pattern을 사용하여 시스템 응집도와 재사용성을 어떻게 개선할 수 있을까?
이전 글에서 OrderService에서 의존하고 있는 컴포넌트가 너무 많아 응집도가 떨어지고 강결합이 발생하는 문제가 있었다고 언급을 했었습니다.
그래서 이번엔 facade pattern을 E-commerce 서비스에 적용하여 리팩터링 한 내용을 정리해보려고 합니다.
기존 OrderService 코드
public class OrderService {
private final UserReader userReader;
private final UserPointManager userPointManager;
private final UserPointValidator userPointValidator;
private final ProductReader productReader;
private final ProductUpdater productUpdater;
private final ProductValidator productValidator;
private final OrderValidator orderValidator;
private final OrderProcessor orderProcessor;
private final OrderUpdater orderUpdater;
private final OrderItemAppender orderItemAppender;
}
위 코드를 보면 한눈에 봐도 OrderService가 의존하고 있는 컴포넌트가 매우 많은 것을 볼 수 있습니다.
이 구조는 모두 주문을 하기 위해 필요한 컴포넌트들(user관련 컴포넌트, product 관련 컴포넌트, order 관련 컴포넌트, orderItem 관련 컴포넌트 등등)이지만 주문과 관련된 여러 기능을 모두 OrderService 하나에서 관리하도록 해 응집도가 낮고 책임이 불명확해지는 문제가 있었습니다.
Facade pattern 이란?
facade pattern은 디자인 패턴 중 하나로 클라이언트에게 복잡한 시스템을 숨기고 중간에서 간단한 인터페이스를 제공하는 다리 역할을 하는 디자인 패턴을 말합니다.
이 패턴은 시스템의 복잡성을 단일 클래스로 추상화하여 클라이언트가 시스템을 더 쉽게 사용할 수 있도록 도와줍니다.
예를 들어, 상품을 주문 하는 시스템이 있다고 가정할 때 상품을 주문하기 위해서는 상품 재고 관련 로직, 결제 관련 로직, 배송 관련 로직이 존재할 텐데 이러한 복잡한 로직은 클라이언트에게 숨기고 상품을 주문하는 클라이언트는 "주문"이라는 메서드만 호출해도 모든 주문 관련 작업을 수행할 수 있도록 할 수 있습니다.
Facade pattern 적용을 통해 클라이언트는 복잡한 구현 로직에 대한 내용을 알 필요 없다는 장점이 존재하고, 응집도가 높아져 서비스를 재사용할 수 있는 형태가 되어 코드를 재사용 및 유지보수 하기 용이해집니다.
또한 Facade 클래스만 확인해도 큰 비즈니스 흐름을 확인할 수 있습니다.
Facade pattern 적용하여 기존 코드 리팩터링
OrderUseCase라는 facade 객체를 생성하여 클라이언트는 이 OrderUseCase 컴포넌트를 의존하여 주문을 하도록 변경하였습니다.
Controller
@RestController
@RequestMapping("orders")
public class OrderController {
private final OrderUseCase orderUseCase;
public OrderController(OrderUseCase orderUseCase) {
this.orderUseCase = orderUseCase;
}
@PostMapping("/{userId}")
@ResponseStatus(HttpStatus.CREATED)
public OrderResponse order(@PathVariable Long userId, @Valid @RequestBody OrderRequest request) {
OrderPaidResult orderPaidResult = orderUseCase.order(userId, request);
return OrderResponse.from(orderPaidResult);
}
}
이후 OrderUseCase facade 클래스에서는 주문을 하기 위해 필요한 Service를 의존하고 있습니다.
아래 코드를 보면 이 OrderUseCase의 order 메서드만 확인해도 주문을 하기 위한 큰 흐름은 한눈에 파악할 수 있습니다. (사용자 확인 -> 상품 확인 -> 주문 상품 재고 확인 -> 주문 -> 결제 -> 상품 주문 재고 차감)
Facade
public class OrderUseCase {
private final UserService userService;
private final ProductService productService;
private final StockService stockService;
private final OrderService orderService;
private final PaymentService paymentService;
@Transactional
public OrderPaidResult order(Long userId, OrderRequest request) {
User user = userService.getUser(userId);
List<Product> products = productService.getProductsByIds(request.products());
List<Stock> stocks = stockService.getStocksByProductIds(products);
Order order = orderService.order(user, products, request);
Payment payment = paymentService.pay(user, order, request);
stockService.decreaseProductStock(stocks, request);
return OrderPaidResult.of(order, payment);
}
}
이제 이전에 보았던 OrderService의 코드를 확인하면 확연히 의존하고 있는 컴포넌트가 줄어들고 Order 관련 컴포넌트만 의존하는 것을 확인할 수 있습니다.
Service
public class OrderService {
private final OrderValidator orderValidator;
private final OrderProcessor orderProcessor;
private final OrderUpdater orderUpdater;
private final OrderItemAppender orderItemAppender;
}
기존에 OrderService의 order 메서드에서 하고 있던 사용자 확인, 상품 재고 확인, 결제 등등 로직들이 사라지고 해당 로직들은 각 Servcie(ProductServce, PaymentService 등등)에서 처리하도록 변경했기 때문에 책임이 명확해졌습니다.
장점
- 응집도 향상: 각 서비스 클래스는 자신의 책임에만 집중할 수 있게 되어 응집도가 향상됩니다.
- 재사용성 증가: Facade pattern을 적용하여 코드의 재사용성을 높입니다. 예를 들어 비슷한 주문 기능이 필요할 때 OrderUseCase 클래스를 재사용할 수 있습니다.
- 클라이언트의 사용 용이성: 클라이언트는 복잡한 주문 로직을 몰라도 OrderUseCase의 메서드를 호출하는 것만으로 주문 프로세스를 완료할 수 있습니다.
결론
파사드 패턴을 적용함으로써, 복잡한 시스템의 사용법을 단순화하고 서브시스템의 응집도와 전체적인 코드의 재사용성을 개선할 수 있었습니다. 특히 OrderService의 응집도는 높아지고, 테스트도 매우 간단해졌습니다.
이번에 facade pattern을 처음 적용해 보았는데 기존 layered architecture에서 순환참조를 방지하기 위해 각 계층 간 참조를 못하기 때문에 Service 클래스를 재사용해야 하는 경우 Controller에서 참조하여 Controller가 매우 뚱뚱해지는 현상이 있었는데 이러한 문제들도 Facade pattern을 적용하여 해결할 수 있다는 것을 배울 수 있었습니다.
'성장이야기 > TIL' 카테고리의 다른 글
[E-commerce] 트랜잭셔널 아웃박스 패턴을 활용한 이벤트 기반 주문 처리 시스템 설계 및 구현(With Kafka) (1) | 2024.05.17 |
---|---|
[E-commerce] 주문 결제를 이벤트 기반 아키텍처로 구축하기 (0) | 2024.04.16 |
[E-commerce] 주문 결제 트랜잭션 단위를 어떻게 가져가야 할까 (0) | 2024.04.10 |
Domain과 Entity의 두 얼굴? (0) | 2024.03.31 |
Actions Runner Controller 를 이용해 self-hosted runner로 배포하기 (0) | 2024.03.19 |