Skip to the content.

Application Layer — UseCase & Transaction Orchestration

이 문서는 application-layer요약 가이드입니다.

핵심 원칙, 컴포넌트 역할, CQRS 흐름, 패키징 구조, 그리고 각 디렉터리별 상세 가이드 링크를 제공합니다.


1) 핵심 원칙 (한눈에)

금지사항


2) CQRS 컴포넌트 역할 분리

Command 측 컴포넌트

컴포넌트 역할 어노테이션 의존성
CommandUseCase (Port-In) 인터페이스 정의 Interface -
CommandService Port-In 구현, 흐름 조율 @Service CommandFactory, Facade/TransactionManager, Assembler
CommandFactory Command → Domain, Bundle 생성 @Component Domain 객체만
CommandFacade 2개 이상 TransactionManager 조합 @Component TransactionManager 2개 이상
TransactionManager 단일 Port 트랜잭션, persist 담당 @Component PersistencePort 1개

Query 측 컴포넌트

컴포넌트 역할 어노테이션 의존성
QueryUseCase (Port-In) 인터페이스 정의 Interface -
QueryService Port-In 구현, 흐름 조율 @Service QueryFactory, QueryFacade/ReadManager, Assembler
QueryFactory Query → Criteria 변환 @Component Domain Criteria만
QueryFacade 2개 이상 ReadManager 조합 @Component ReadManager 2개 이상
ReadManager 단일 Port 조회, 읽기 전용 @Component QueryPort 1개

공통 컴포넌트

컴포넌트 역할 어노테이션 의존성
Assembler Domain/Bundle → Response 변환 @Component Domain, Response DTO

3) 컴포넌트 사용 기준

Command 흐름

CommandService (UseCase 구현체)
├─ 객체 생성 필요 → CommandFactory 사용
├─ 단일 TransactionManager 사용 → TransactionManager 직접 호출
├─ 여러 TransactionManager 사용 → CommandFacade 호출
└─ Response 변환 → Assembler 사용

CommandFactory
├─ Command → Domain 변환
└─ Bundle 생성 (여러 객체 묶음)

CommandFacade
├─ 2개 이상 TransactionManager 조합 필수
├─ @Transactional 메서드 단위
└─ persist*() 메서드 네이밍

TransactionManager
├─ 단일 PersistencePort persist() 호출
├─ @Transactional 메서드 단위
└─ @Component 어노테이션

Query 흐름

QueryService (UseCase 구현체)
├─ Criteria 변환 필요 → QueryFactory 사용
├─ 단일 ReadManager 사용 → ReadManager 직접 호출
├─ 여러 ReadManager 사용 → QueryFacade 호출
└─ Response 변환 → Assembler 사용

QueryFactory
└─ Query → Criteria 변환

QueryFacade
├─ 2개 이상 ReadManager 조합 필수
├─ @Transactional 금지 (읽기 전용)
└─ fetch*() 메서드 네이밍

ReadManager
├─ 단일 QueryPort 조회 호출
├─ @Transactional 금지 (읽기 전용)
└─ @Component 어노테이션

4) 전체 흐름

Command 흐름 (복잡한 경우 - Facade 사용)

┌─────────────────────────────────────────────────────────────────────────────┐
│ Controller                                                                  │
│   PlaceOrderRequest (REST DTO)                                             │
│       ↓                                                                     │
│   RequestMapper.toCommand(request)                                          │
│       ↓                                                                     │
│   PlaceOrderCommand (Application DTO)                                       │
└───────────────────────────────────────────┬─────────────────────────────────┘
                                            ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ CommandService (PlaceOrderService)                                         │
│                                                                             │
│   1. Order order = commandFactory.create(command)                          │
│      └─ CommandFactory: Command → Domain 변환                              │
│                                                                             │
│   2. order.place()                                                          │
│      └─ Domain 메서드: 비즈니스 로직 수행                                   │
│                                                                             │
│   3. OrderPersistBundle bundle = commandFactory.createBundle(order)        │
│      └─ CommandFactory: Bundle 생성 (Order, History, Outbox)               │
│                                                                             │
│   4. Order saved = commandFacade.persistOrderBundle(bundle)                │
│      └─ CommandFacade: 여러 TransactionManager 조합                        │
│                                                                             │
│   5. return assembler.toResponse(saved)                                     │
│      └─ Assembler: Domain → Response 변환                                   │
└───────────────────────────────────────────┬─────────────────────────────────┘
                                            ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ CommandFacade (OrderCommandFacade) — @Transactional                        │
│                                                                             │
│   1. Order saved = orderManager.persist(bundle.order())                     │
│      └─ ID 획득                                                             │
│                                                                             │
│   2. OrderPersistBundle enriched = bundle.enrichWithId(saved.id())          │
│      └─ ID Enrichment                                                       │
│                                                                             │
│   3. historyManager.persist(enriched.history())                             │
│   4. outboxManager.persist(enriched.outboxEvent())                          │
│                                                                             │
│   return saved                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Command 흐름 (단순한 경우 - TransactionManager 직접)

CommandService (UseCase 구현체)
├─ commandFactory.create(command)
├─ domain.action()
├─ transactionManager.persist(domain)  // 직접 호출
└─ assembler.toResponse(saved)

Query 흐름 (복잡한 경우 - QueryFacade 사용)

┌─────────────────────────────────────────────────────────────────────────────┐
│ Controller                                                                  │
│   OrderDetailRequest (REST DTO)                                            │
│       ↓                                                                     │
│   RequestMapper.toQuery(request)                                            │
│       ↓                                                                     │
│   OrderDetailQuery (Application DTO)                                        │
└───────────────────────────────────────────┬─────────────────────────────────┘
                                            ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ QueryService (GetOrderDetailService)                                       │
│                                                                             │
│   1. OrderDetailCriteria criteria = queryFactory.createDetailCriteria(query)│
│      └─ QueryFactory: Query → Criteria 변환                                │
│                                                                             │
│   2. OrderDetailQueryBundle bundle = queryFacade.fetchOrderDetail(criteria)│
│      └─ QueryFacade: 여러 ReadManager 조합                                  │
│                                                                             │
│   3. return assembler.toDetailResponse(bundle)                              │
│      └─ Assembler: Bundle → Response 변환                                   │
└───────────────────────────────────────────┬─────────────────────────────────┘
                                            ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ QueryFacade (OrderQueryFacade) — @Transactional 금지                       │
│                                                                             │
│   1. Order order = orderReadManager.findBy(criteria)                        │
│   2. List<Item> items = itemReadManager.findBy(order.id())                  │
│   3. Customer customer = customerReadManager.findBy(order.customerId())     │
│                                                                             │
│   return new OrderDetailQueryBundle(order, items, customer)                 │
└─────────────────────────────────────────────────────────────────────────────┘

Query 흐름 (단순한 경우 - ReadManager 직접)

QueryService (UseCase 구현체)
├─ queryFactory.createCriteria(query)  // 필요 시
├─ readManager.findBy(criteria)        // 직접 호출
└─ assembler.toResponse(domain)

5) 패키징 구조 (CQRS 분리)

application/
│
├─ common/
│    ├─ config/
│    │    └─ TransactionEventRegistry.java
│    └─ dto/
│         ├─ PageResponse.java
│         └─ SliceResponse.java
│
└─ order/                              # ← 예시 BC (Bounded Context)
   │
   ├─ assembler/
   │  └─ OrderAssembler.java            # Domain/Bundle → Response
   │
   ├─ dto/
   │  ├─ command/
   │  │   ├─ PlaceOrderCommand.java
   │  │   └─ CancelOrderCommand.java
   │  ├─ query/
   │  │   ├─ OrderDetailQuery.java
   │  │   └─ OrderSearchQuery.java
   │  ├─ response/
   │  │   ├─ OrderResponse.java
   │  │   └─ OrderDetailResponse.java
   │  └─ bundle/
   │      ├─ OrderPersistBundle.java      # Command용 Bundle
   │      └─ OrderDetailQueryBundle.java  # Query용 Bundle
   │
   ├─ facade/
   │  ├─ command/
   │  │   └─ OrderCommandFacade.java      # 2+ TransactionManager 조합
   │  └─ query/
   │      └─ OrderQueryFacade.java        # 2+ ReadManager 조합
   │
   ├─ factory/
   │  ├─ command/
   │  │   └─ OrderCommandFactory.java     # Command → Domain, Bundle 생성
   │  └─ query/
   │      └─ OrderQueryFactory.java       # Query → Criteria 변환
   │
   ├─ manager/
   │  ├─ command/
   │  │   ├─ OrderTransactionManager.java
   │  │   └─ OrderHistoryTransactionManager.java
   │  └─ query/
   │      ├─ OrderReadManager.java
   │      └─ OrderItemReadManager.java
   │
   ├─ port/
   │  ├─ in/
   │  │   ├─ command/
   │  │   │   ├─ PlaceOrderUseCase.java
   │  │   │   └─ CancelOrderUseCase.java
   │  │   └─ query/
   │  │       ├─ GetOrderDetailUseCase.java
   │  │       └─ SearchOrdersUseCase.java
   │  └─ out/
   │      ├─ command/
   │      │    └─ OrderPersistencePort.java
   │      └─ query/
   │           └─ OrderQueryPort.java
   │
   ├─ service/
   │  ├─ command/
   │  │   ├─ PlaceOrderService.java
   │  │   └─ CancelOrderService.java
   │  └─ query/
   │      ├─ GetOrderDetailService.java
   │      └─ SearchOrdersService.java
   │
   ├─ listener/
   │  └─ OrderEventListener.java        # @TransactionalEventListener
   │
   └─ scheduler/
      └─ OrderCleanupScheduler.java

6) 디렉터리별 상세 가이드 링크

assembler/

dto/

facade/

factory/

manager/

service/

event/

port/

listener/

scheduler/


7) 컴포넌트 어노테이션 규칙

컴포넌트 어노테이션 @Transactional 이유
CommandService @Service ❌ 금지 Facade/Manager 책임
QueryService @Service ❌ 금지 읽기 전용, 불필요
CommandFactory @Component ❌ 금지 변환만, 트랜잭션 불필요
QueryFactory @Component ❌ 금지 변환만, 트랜잭션 불필요
CommandFacade @Component ✅ 메서드 단위 여러 Manager 조합
QueryFacade @Component ❌ 금지 읽기 전용, 불필요
TransactionManager @Component ✅ 메서드 단위 persist 담당
ReadManager @Component ❌ 금지 읽기 전용, 불필요
Assembler @Component ❌ 금지 변환만, 트랜잭션 불필요

8) 체크리스트

CommandService 구현 시

QueryService 구현 시

CommandFacade 구현 시

QueryFacade 구현 시

TransactionManager 구현 시

ReadManager 구현 시


9) 관련 문서


작성자: Development Team 최종 수정일: 2025-12-05 버전: 3.0.0