ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜์—์„œ Port vs CQRS ํŒจํ„ด ๋น„๊ต

2026. 1. 3. 16:12ใ†Backend

๋ฐ˜์‘ํ˜•

 

 

ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜์˜ Application Layer๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ๋Š”
์ „ํ†ต์ ์ธ Input/Output Port ๋ฐฉ์‹๊ณผ CQRS๋ฅผ ์ ์šฉํ•œ ๋ฐฉ์‹์˜ ์‹ค๋ฌด์  ์ฐจ์ด์ ๊ณผ ์„ ํƒ ๊ธฐ์ค€์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๋‹จ์ˆœ Input/Output Port๋Š” ํ•˜๋‚˜์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ์ฝ๊ธฐ์™€ ์“ฐ๊ธฐ๋ฅผ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•˜๋Š” ์ „ํ†ต์  ๋ฐฉ์‹์ด๋ฉฐ
CQRS ์ ์šฉ์€ Command(์“ฐ๊ธฐ)์™€ Query(์ฝ๊ธฐ)๋ฅผ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ฐ๊ฐ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ์ด๋‹ค.
์‹ค๋ฌด์—์„œ๋Š” ๋„๋ฉ”์ธ ๋ณต์žก๋„์™€ ์„ฑ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์„ ํƒํ•œ๋‹ค.

๋‹จ์ˆœ CRUD ์ค‘์‹ฌ์ด๋ฉด Input/Output Port๋กœ ์ถฉ๋ถ„ํ•˜๊ณ ,
๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ์กฐํšŒ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•˜๋ฉด CQRS๋ฅผ ๊ณ ๋ คํ•ด๋ณผ๋งŒ ํ•˜๋‹ค.

 

 

 

์‹ค๋ฌด ์˜ˆ์‹œ

1. ๋‹จ์ˆœ Input/Output Port ๊ตฌ์กฐ

๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ

application/
โ”œโ”€โ”€ port/
โ”‚   โ”œโ”€โ”€ in/
โ”‚   โ”‚   โ””โ”€โ”€ OrderUseCase.java
โ”‚   โ””โ”€โ”€ out/
โ”‚       โ”œโ”€โ”€ LoadOrderPort.java
โ”‚       โ””โ”€โ”€ SaveOrderPort.java
โ””โ”€โ”€ service/
    โ””โ”€โ”€ OrderService.java
 

 

 

์ฝ”๋“œ ์˜ˆ์‹œ

// Input Port - ๋น„์ฆˆ๋‹ˆ์Šค ์œ ์Šค์ผ€์ด์Šค ์ •์˜
public interface OrderUseCase {
    OrderResponse createOrder(CreateOrderCommand command);
    OrderResponse getOrder(Long orderId);
    OrderResponse updateOrderStatus(Long orderId, OrderStatus status);
}

// Service - ์ฝ๊ธฐ/์“ฐ๊ธฐ ํ˜ผ์žฌ
@Service
@RequiredArgsConstructor
public class OrderService implements OrderUseCase {
    private final LoadOrderPort loadOrderPort;
    private final SaveOrderPort saveOrderPort;
    
    @Transactional
    public OrderResponse createOrder(CreateOrderCommand command) {
        // ์“ฐ๊ธฐ ๋กœ์ง
        Order order = Order.create(command);
        saveOrderPort.save(order);
        
        // ์ฝ๊ธฐ ๋กœ์ง (์ƒ์„ฑ ํ›„ ์กฐํšŒ)
        return OrderResponse.from(order);
    }
    
    @Transactional(readOnly = true)
    public OrderResponse getOrder(Long orderId) {
        // ์ฝ๊ธฐ ๋กœ์ง
        Order order = loadOrderPort.loadById(orderId);
        return OrderResponse.from(order);
    }
}

 

 

 

2. CQRS ํŒจํ„ด ์ ์šฉ ๊ตฌ์กฐ

๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ

application/
โ”œโ”€โ”€ port/
โ”‚   โ”œโ”€โ”€ in/
โ”‚   โ”‚   โ”œโ”€โ”€ command/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ CreateOrderUseCase.java
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ UpdateOrderStatusUseCase.java
โ”‚   โ”‚   โ””โ”€โ”€ query/
โ”‚   โ”‚       โ”œโ”€โ”€ GetOrderQuery.java
โ”‚   โ”‚       โ””โ”€โ”€ SearchOrdersQuery.java
โ”‚   โ””โ”€โ”€ out/
โ”‚       โ”œโ”€โ”€ command/
โ”‚       โ”‚   โ””โ”€โ”€ OrderCommandPort.java
โ”‚       โ””โ”€โ”€ query/
โ”‚           โ””โ”€โ”€ OrderQueryPort.java
โ””โ”€โ”€ service/
    โ”œโ”€โ”€ command/
    โ”‚   โ””โ”€โ”€ OrderCommandService.java
    โ””โ”€โ”€ query/
        โ””โ”€โ”€ OrderQueryService.java
 

์ฝ”๋“œ ์˜ˆ์‹œ

// Command Port - ์“ฐ๊ธฐ ์ „์šฉ
public interface CreateOrderUseCase {
    OrderId execute(CreateOrderCommand command);
}

// Query Port - ์ฝ๊ธฐ ์ „์šฉ
public interface GetOrderQuery {
    OrderDetailView execute(Long orderId);
}

// Command Service - ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ง‘์ค‘
@Service
@RequiredArgsConstructor
public class OrderCommandService implements CreateOrderUseCase {
    private final OrderCommandPort commandPort;
    
    @Transactional
    public OrderId execute(CreateOrderCommand command) {
        // ๋„๋ฉ”์ธ ๋กœ์ง์—๋งŒ ์ง‘์ค‘
        Order order = Order.create(command);
        commandPort.save(order);
        return order.getId();
    }
}

// Query Service - ์กฐํšŒ ์ตœ์ ํ™”
@Service
@RequiredArgsConstructor
public class OrderQueryService implements GetOrderQuery {
    private final OrderQueryPort queryPort; // QueryDSL, Native Query ๊ฐ€๋Šฅ
    
    @Transactional(readOnly = true)
    public OrderDetailView execute(Long orderId) {
        // DTO ์ง์ ‘ ๋ฐ˜ํ™˜์œผ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”
        return queryPort.findOrderDetailById(orderId);
    }
}
 

 
 
 
 
 

Port vs CQRS ํŒจํ„ด ์ƒ์„ธ ๋น„๊ต

์™œ ๋ถ„๋ฆฌํ•˜๋Š”๊ฐ€?

1. ์ฑ…์ž„์˜ ๋ช…ํ™•์„ฑ

  • Input/Output: UseCase๊ฐ€ ์ฝ๊ธฐ/์“ฐ๊ธฐ ๋ชจ๋‘ ๋‹ด๋‹นํ•˜์—ฌ ์ฑ…์ž„์ด ํ˜ผ์žฌ
  • CQRS: Command๋Š” ์ƒํƒœ ๋ณ€๊ฒฝ๋งŒ, Query๋Š” ์กฐํšŒ๋งŒ ๋‹ด๋‹นํ•˜์—ฌ ๋‹จ์ผ ์ฑ…์ž„ ์›์น™ ์ค€์ˆ˜

 

2. ์„ฑ๋Šฅ ์ตœ์ ํ™”

// Input/Output: Entity ๊ธฐ๋ฐ˜ ์กฐํšŒ
Order order = loadOrderPort.loadById(orderId);
// → ๋ถˆํ•„์š”ํ•œ ์—ฐ๊ด€๊ด€๊ณ„ ๋กœ๋”ฉ ๊ฐ€๋Šฅ์„ฑ

// CQRS: DTO ์ง์ ‘ ์กฐํšŒ
OrderDetailView view = queryPort.findOrderDetailById(orderId);
// → ํ•„์š”ํ•œ ์ปฌ๋Ÿผ๋งŒ SELECT, JOIN ์ตœ์ ํ™”
 

3. ํ™•์žฅ์„ฑ

  • Input/Output: ์กฐํšŒ ๋กœ์ง ๋ณต์žก๋„ ์ฆ๊ฐ€ ์‹œ Service๊ฐ€ ๋น„๋Œ€ํ•ด์ง
  • CQRS: Query Service ๋…๋ฆฝ ํ™•์žฅ, ์ฝ๊ธฐ ์ „์šฉ DB ๋ถ„๋ฆฌ ๊ฐ€๋Šฅ

 

์‹ค๋ฌด ์„ ํƒ ๊ธฐ์ค€

1. Input/Output Port ์ ํ•ฉํ•œ ๊ฒฝ์šฐ

  • ๋‹จ์ˆœ CRUD ๋„๋ฉ”์ธ (์‚ฌ์šฉ์ž ๊ด€๋ฆฌ, ์„ค์ • ๊ด€๋ฆฌ)
  • ์กฐํšŒ์™€ ์ˆ˜์ •์ด 1:1 ๋Œ€์‘
  • ํŒ€ ๊ทœ๋ชจ๊ฐ€ ์ž‘๊ณ  ๋น ๋ฅธ ๊ฐœ๋ฐœ ํ•„์š”
  • ํŠธ๋ž˜ํ”ฝ์ด ๋‚ฎ๊ณ  ์„ฑ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ ๋‹จ์ˆœ

์˜ˆ์‹œ: ์‚ฌ๋‚ด ๊ด€๋ฆฌ์ž ๋„๊ตฌ, ์„ค์ • ๊ด€๋ฆฌ ์‹œ์Šคํ…œ

2. CQRS ์ ํ•ฉํ•œ ๊ฒฝ์šฐ

  • ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง (์ฃผ๋ฌธ, ๊ฒฐ์ œ, ์ •์‚ฐ)
  • ์กฐํšŒ ์š”๊ตฌ์‚ฌํ•ญ์ด ๋‹ค์–‘ํ•˜๊ณ  ๋ณต์žก (๋Œ€์‹œ๋ณด๋“œ, ๋ฆฌํฌํŠธ)
  • ์ฝ๊ธฐ/์“ฐ๊ธฐ ๋น„์œจ ์ฐจ์ด ํผ (์ฝ๊ธฐ 90%, ์“ฐ๊ธฐ 10%)
  • ์ด๋ฒคํŠธ ์†Œ์‹ฑ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ

์˜ˆ์‹œ: ์ด์ปค๋จธ์Šค ์ฃผ๋ฌธ ์‹œ์Šคํ…œ, ๊ด‘๊ณ  ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ํ”Œ๋žซํผ

 

์ฃผ์˜์‚ฌํ•ญ

Input/Output Port

  • Service ๋น„๋Œ€ํ™” ๊ฒฝ๊ณ : ๋ฉ”์„œ๋“œ 10๊ฐœ ์ด์ƒ ์‹œ ๋ถ„๋ฆฌ ๊ณ ๋ ค
  • ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„ ํ˜ผ๋™: readOnly ํŠธ๋žœ์žญ์…˜ ๋ˆ„๋ฝ ์ฃผ์˜
  • Output Port ๊ณผ๋„ํ•œ ๋ถ„๋ฆฌ: LoadPort, SavePort๋ฅผ Repository๋กœ ํ†ตํ•ฉ ๊ณ ๋ ค

CQRS

  • ๊ณผ๋„ํ•œ ๋ถ„๋ฆฌ: ๋‹จ์ˆœ ๋„๋ฉ”์ธ์— ์ ์šฉ ์‹œ ์˜ค๋ฒ„์—”์ง€๋‹ˆ์–ด๋ง
  • ์ฝ”๋“œ ์ค‘๋ณต: Command/Query ๊ฐ„ ์œ ์‚ฌ ๋กœ์ง ๋ฐœ์ƒ ๊ฐ€๋Šฅ
  • ํ•™์Šต ๊ณก์„ : ํŒ€์› ๋ชจ๋‘๊ฐ€ ํŒจํ„ด ์ดํ•ด ํ•„์š”

 

 

๊ฒฐ๋ก 

ํŒจํ„ด ์ ์šฉ์— ์ •๋‹ต์€ ์—†์œผ๋ฉฐ, ์‹ค๋ฌด์—์„œ๋Š” ๋„๋ฉ”์ธ ๋ณต์žก๋„์™€ ์„ฑ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์„ ํƒํ•จ.
ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ์—๋Š” Input/Output Port๋กœ ์‹œ์ž‘ํ•˜๊ณ , ๋„๋ฉ”์ธ ๋ณต์žก๋„๊ฐ€ ์ฆ๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์กฐํšŒ ์„ฑ๋Šฅ ์ด์Šˆ ๋ฐœ์ƒ ์‹œ CQRS๋กœ ์ ์ง„์  ์ „ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•จ!

 
 
 
 
๋ฐ˜์‘ํ˜•

'Backend' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

DDD ์›์น™(Domain Driven Design)์ด๋ž€?  (0) 2026.01.03
ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜๋ž€?  (0) 2026.01.03