ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 소프트웨어 아키텍처 시리즈 11화 – 유즈케이스 중심 서비스 설계와 포트 단위 테스트 전략
    기술과 산업/아키텍처 2025. 6. 22. 12:59
    728x90

    소프트웨어 아키텍처에서 유즈케이스 중심 설계는 도메인과 시스템 간의 실질적 연결 고리입니다. 이 글에서는 헥사고날 아키텍처 기반에서 유즈케이스를 어떻게 설계하고, 포트 단위로 어떻게 테스트해야 하는지를 실전 중심으로 설명합니다.

     

     

    유즈케이스, 구조 설계의 출발점

     

    많은 프로젝트에서 흔히 설계 초기에 “기능 목록”만 정의한 채 개발을 시작하는 경우가 많습니다. 하지만 이는 아키텍처 관점에서는 위험한 방식입니다.

    왜냐하면:

     

    • 기능 중심 사고는 관심사의 경계를 흐리기 쉽고
    • 비즈니스 로직과 UI 처리를 혼재시키는 구조로 이어지기 때문입니다.

     

    그래서 유즈케이스 기반 설계는 매우 중요한 출발점입니다.

     

    유즈케이스는 사용자가 시스템에 기대하는 ‘일의 흐름’이며,
    아키텍처에서는 이를 Application Layer의 핵심 단위로 구현하게 됩니다.

     


     

    유즈케이스를 중심으로 구조를 설계한다는 의미

     

    단순히 ‘기능 메서드 하나 만들자’가 아니라, 다음의 흐름을 갖습니다:

     

    1. 사용자의 액션 정의 → 유즈케이스 도출
    2. 유즈케이스별 Input, Output 모델 정의
    3. 포트(인터페이스)로 유즈케이스 추상화
    4. 유스케이스 구현은 애플리케이션 레이어가 담당
    5. 도메인 서비스와 협력, 외부 리소스는 포트 통해 접근

     


     

    예: 상품 주문 유즈케이스

     

     

    유즈케이스 명: 

    PlaceOrder

     

    설명: 사용자가 장바구니에 담긴 상품을 기반으로 주문을 생성하고 결제까지 요청하는 흐름

     

    입력 모델:

    public class PlaceOrderCommand {
        private List<CartItem> items;
        private PaymentMethod paymentMethod;
        private Address deliveryAddress;
    }

    출력 모델:

    public class PlaceOrderResult {
        private UUID orderId;
        private String paymentStatus;
    }

    유즈케이스 포트:

    public interface PlaceOrderUseCase {
        PlaceOrderResult handle(PlaceOrderCommand command);
    }

    유즈케이스 구현:

    @Service
    public class PlaceOrderService implements PlaceOrderUseCase {
        private final OrderFactory orderFactory;
        private final PaymentGateway paymentGateway;
        private final OrderRepository orderRepository;
    
        public PlaceOrderResult handle(PlaceOrderCommand command) {
            Order order = orderFactory.createFromCart(command.items, command.deliveryAddress);
            PaymentResult payment = paymentGateway.requestPayment(order.calculateTotal(), command.paymentMethod);
    
            if (payment.isApproved()) {
                order.markAsPaid();
            }
    
            orderRepository.save(order);
            return new PlaceOrderResult(order.getId(), payment.getStatus());
        }
    }

     


     

    Application Layer의 특징

     

    • 도메인 로직을 직접 구현하지 않는다
    • → 유스케이스 흐름만 제어하고, 로직은 도메인 모델에게 위임
    • 입출력 모델을 명확히 분리한다
    • → ViewModel과 도메인 모델을 혼합하지 않음
    • 외부 연동은 포트를 통해서만 수행한다
    • → 기술 의존성으로부터 분리 가능

     


     

    포트 단위 테스트 전략

     

    Application Layer는 외부 시스템과의 연결을 포트를 통해 수행하므로, Mock 기반 단위 테스트가 매우 용이합니다.

     

     

    테스트 준비

    @ExtendWith(MockitoExtension.class)
    public class PlaceOrderServiceTest {
    
        @Mock
        private OrderFactory orderFactory;
    
        @Mock
        private PaymentGateway paymentGateway;
    
        @Mock
        private OrderRepository orderRepository;
    
        @InjectMocks
        private PlaceOrderService placeOrderService;

     

    핵심 테스트 예시

    @Test
    void 주문_생성_성공시_결제요청후_저장까지_수행된다() {
        // given
        PlaceOrderCommand command = new PlaceOrderCommand(...);
        Order fakeOrder = new Order(...);
        when(orderFactory.createFromCart(...)).thenReturn(fakeOrder);
        when(paymentGateway.requestPayment(...)).thenReturn(PaymentResult.approved());
    
        // when
        PlaceOrderResult result = placeOrderService.handle(command);
    
        // then
        verify(orderRepository).save(fakeOrder);
        assertEquals("APPROVED", result.getPaymentStatus());
    }

    테스트의 특징:

     

    • 외부 시스템 없이 도메인 흐름만 검증
    • 포트를 통해 유즈케이스 단위로 제어
    • 코드 변경 없이 검증 가능 → 리팩토링 내성 강함

     


     

    실제로 자주 발생하는 설계 실수

     

    • Application Layer가 모든 책임을 떠안음 → 도메인 로직 직접 구현, 상태 변경
    • 도메인과 포트 구분이 명확하지 않음 → 코드 복잡도 증가
    • Input/Output 모델이 Controller 수준에 머묾 → 테스트 불가능한 구조

     


     

    정리: 유즈케이스는 ‘구조의 핵’이다

     

    도메인은 비즈니스의 내용이고, 유즈케이스는 그 흐름입니다.

    이 흐름을 기준으로 설계해야 시스템은 다음을 만족할 수 있습니다:

     

    • 도메인과 기술을 분리한 설계 가능
    • 의도에 맞는 단위 테스트 작성 용이
    • 기능 중심이 아닌 사용 시나리오 중심의 구조화

     


     

    마무리하며 – 흐름을 코드로 설계하는 일

     

    유즈케이스는 단순한 기능 구현이 아니라,

    비즈니스가 사용자와 어떻게 상호작용하는지를 코드로 설계하는 작업입니다.

    그리고 그 유즈케이스 하나하나가, 결국 시스템의 아키텍처를 실현하는 단위입니다.

     

    잘 설계된 유즈케이스는 팀 내 커뮤니케이션, 테스트, 유지보수, 확장성까지 영향을 미칩니다.

    그 시작은, 단순한 책임 분리에서부터입니다.

    728x90
Designed by Tistory.