
Giới thiệu
Trong monolith, một business transaction có thể bao gồm nhiều database operations trong cùng một ACID transaction. Trong microservices, mỗi service có database riêng — không thể dùng distributed transaction truyền thống (2PC) vì nó tạo tight coupling và ảnh hưởng performance. Saga Pattern là giải pháp thay thế.
1. Vấn đề với Distributed Transactions
1.1 Two-Phase Commit (2PC) — Tại sao không phù hợp
2PC Flow:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Transaction │ │ Service A │ │ Service B │
│ Coordinator │ │ (Order) │ │ (Payment) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│── Phase 1: PREPARE ──────────────────▶│
│◀── VOTE YES ────────────────────────── │
│── Phase 1: PREPARE ──▶│ │
│◀── VOTE YES ──────── │ │
│ │ │
│── Phase 2: COMMIT ──▶│ │
│── Phase 2: COMMIT ──────────────────▶│
│ │ │
Vấn đề của 2PC trong microservices:
| Vấn đề | Giải thích |
|---|---|
| Single Point of Failure | Coordinator chết → tất cả participant bị lock |
| Performance bottleneck | Lock resources trong suốt quá trình 2PC |
| Tight coupling | Tất cả services phải available cùng lúc |
| Not scalable | Lock contention tăng theo số participant |
| Network partition | Nếu mạng đứt giữa Phase 1 và Phase 2 → inconsistent state |
1.2 CAP Theorem nhắc lại
Trong hệ thống phân tán, chỉ có thể đạt được 2 trong 3:
- Consistency: Mọi read đều trả về write mới nhất
- Availability: Mọi request đều nhận được response
- Partition tolerance: Hệ thống tiếp tục hoạt động khi mạng bị chia cắt
Microservices chọn AP (Availability + Partition tolerance) → chấp nhận Eventual Consistency.
2. Saga Pattern
2.1 Concept
Saga là chuỗi local transactions, mỗi transaction thực hiện bởi một service. Nếu một step fail, saga thực hiện compensating transactions để undo các step trước đó.
Saga = T1 → T2 → T3 → ... → Tn
Nếu Ti fail:
Compensate: C(i-1) → C(i-2) → ... → C1
Ví dụ Order Saga:
T1: Create Order (status: PENDING)
T2: Reserve Payment
T3: Reserve Inventory
T4: Confirm Order (status: CONFIRMED)
Nếu T3 fail:
C2: Refund Payment
C1: Cancel Order (status: CANCELLED)
2.2 Compensating Transactions
Mỗi transaction trong saga phải có compensating transaction tương ứng:
| Step | Action | Compensating Action |
|---|---|---|
| T1 | Create Order | Cancel Order |
| T2 | Reserve Payment | Refund Payment |
| T3 | Reserve Inventory | Release Inventory |
| T4 | Schedule Shipping | Cancel Shipping |
| T5 | Send Confirmation Email | Send Cancellation Email |
Lưu ý quan trọng:
- Compensating transactions phải là idempotent (chạy nhiều lần cho cùng kết quả)
- Compensating transactions không thể fail (phải retry cho đến khi thành công)
- Một số actions không thể compensate (ví dụ: gửi email đã gửi) → dùng "semantic undo" (gửi email hủy)
3. Choreography Saga
3.1 Concept
Mỗi service publish event khi hoàn thành local transaction, service tiếp theo subscribe và xử lý:
┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Order │ │ Payment │ │ Inventory │ │ Shipping │
│ Service │ │ Service │ │ Service │ │ Service │
└────┬─────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
│── OrderCreated ▶│ │ │
│ │── PaymentReserved ▶│ │
│ │ │── InventoryReserved ▶│
│ │ │ │── ShipmentScheduled
│◀───────────────────────────────────────────────────────── │
│ OrderConfirmed │ │ │
│ │ │ │
│ === FAILURE === │ │ │
│ │ │── InventoryFailed ▶│
│ │◀─ CompensatePayment─ │
│◀─ PaymentRefunded─ │ │
│ OrderCancelled │ │ │
3.2 Implementation Example
// Order Service — publishes OrderCreated
@Service
public class OrderService {
@Transactional
public Order createOrder(CreateOrderCommand cmd) {
Order order = new Order(cmd.getCustomerId(), cmd.getItems());
order.setStatus(OrderStatus.PENDING);
orderRepository.save(order);
// Publish event
eventPublisher.publish(new OrderCreatedEvent(
order.getId(),
order.getCustomerId(),
order.getItems(),
order.getTotalAmount()
));
return order;
}
// Compensating handler
@EventHandler
public void on(PaymentFailedEvent event) {
Order order = orderRepository.findById(event.getOrderId());
order.setStatus(OrderStatus.CANCELLED);
order.setFailureReason(event.getReason());
orderRepository.save(order);
}
}
// Payment Service — listens to OrderCreated
@Service
public class PaymentService {
@EventHandler
public void on(OrderCreatedEvent event) {
try {
Payment payment = paymentGateway.reserve(
event.getCustomerId(),
event.getTotalAmount()
);
eventPublisher.publish(new PaymentReservedEvent(
event.getOrderId(),
payment.getId()
));
} catch (InsufficientFundsException e) {
eventPublisher.publish(new PaymentFailedEvent(
event.getOrderId(),
"Insufficient funds"
));
}
}
}
3.3 Ưu nhược điểm
| Ưu điểm | Nhược điểm |
|---|---|
| Loose coupling giữa services | Khó theo dõi flow phức tạp |
| Đơn giản cho saga ít steps | Cyclic dependencies có thể xảy ra |
| Không có single point of failure | Khó debug khi có lỗi |
| Tự nhiên với event-driven architecture | Testing phức tạp |
4. Orchestration Saga
4.1 Concept
Một Saga Orchestrator trung tâm điều phối toàn bộ workflow, gửi command cho từng service và xử lý response:
┌──────────────────────┐
│ Order Saga │
│ Orchestrator │
│ │
│ State Machine: │
│ CREATED │
│ → PAYMENT_PENDING │
│ → INVENTORY_PENDING │
│ → SHIPPING_PENDING │
│ → CONFIRMED │
│ or → COMPENSATING │
│ → CANCELLED │
└────────┬─────────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌──────▼──────┐ ┌─────▼──────┐ ┌──────▼──────┐
│ Payment │ │ Inventory │ │ Shipping │
│ Service │ │ Service │ │ Service │
└─────────────┘ └────────────┘ └─────────────┘
4.2 State Machine Implementation
public class OrderSaga {
public enum State {
CREATED,
PAYMENT_PENDING,
PAYMENT_RESERVED,
INVENTORY_PENDING,
INVENTORY_RESERVED,
SHIPPING_PENDING,
CONFIRMED,
COMPENSATING_INVENTORY,
COMPENSATING_PAYMENT,
CANCELLED
}
@Autowired
private SagaRepository sagaRepository;
public void start(CreateOrderCommand cmd) {
SagaState saga = new SagaState(cmd.getOrderId(), State.CREATED);
sagaRepository.save(saga);
// Step 1: Reserve Payment
saga.setState(State.PAYMENT_PENDING);
commandGateway.send(new ReservePaymentCommand(
cmd.getOrderId(), cmd.getAmount()
));
}
@SagaEventHandler
public void on(PaymentReservedEvent event) {
SagaState saga = sagaRepository.findByOrderId(event.getOrderId());
saga.setState(State.INVENTORY_PENDING);
// Step 2: Reserve Inventory
commandGateway.send(new ReserveInventoryCommand(
event.getOrderId(), saga.getItems()
));
}
@SagaEventHandler
public void on(InventoryReservedEvent event) {
SagaState saga = sagaRepository.findByOrderId(event.getOrderId());
saga.setState(State.SHIPPING_PENDING);
// Step 3: Schedule Shipping
commandGateway.send(new ScheduleShippingCommand(
event.getOrderId(), saga.getAddress()
));
}
@SagaEventHandler
public void on(ShipmentScheduledEvent event) {
SagaState saga = sagaRepository.findByOrderId(event.getOrderId());
saga.setState(State.CONFIRMED);
commandGateway.send(new ConfirmOrderCommand(event.getOrderId()));
}
// === COMPENSATION ===
@SagaEventHandler
public void on(InventoryReservationFailedEvent event) {
SagaState saga = sagaRepository.findByOrderId(event.getOrderId());
saga.setState(State.COMPENSATING_PAYMENT);
// Compensate: Refund Payment
commandGateway.send(new RefundPaymentCommand(
event.getOrderId(), saga.getPaymentId()
));
}
@SagaEventHandler
public void on(PaymentRefundedEvent event) {
SagaState saga = sagaRepository.findByOrderId(event.getOrderId());
saga.setState(State.CANCELLED);
commandGateway.send(new CancelOrderCommand(
event.getOrderId(), "Inventory not available"
));
}
}
4.3 Ưu nhược điểm
| Ưu điểm | Nhược điểm |
|---|---|
| Dễ hiểu workflow (centralized) | Orchestrator là single point of failure |
| Dễ debug và monitor | Risk of god class (quá nhiều logic) |
| Dễ thêm/bớt steps | Coupling giữa orchestrator và services |
| Compensation logic rõ ràng | Cần persistent saga state |
5. So sánh Choreography vs Orchestration
| Tiêu chí | Choreography | Orchestration |
|---|---|---|
| Coupling | Rất loose | Medium (qua orchestrator) |
| Complexity | Tăng theo số service | Tập trung trong orchestrator |
| Visibility | Khó nhìn overall flow | Rõ ràng trong state machine |
| Failure handling | Phân tán | Tập trung |
| Testing | Khó (distributed) | Dễ hơn (test orchestrator) |
| Scalability | Tốt | Tốt (orchestrator stateless) |
| Khuyến nghị | ≤ 4 services trong saga | > 4 services hoặc complex flow |
6. Error Handling & Dead Letter Queue
6.1 Dead Letter Queue (DLQ)
Messages không thể xử lý sau nhiều lần retry được chuyển vào DLQ:
┌─────────────┐
│ Main Queue │
│ (order.cmds)│
└──────┬──────┘
│
┌──────▼──────┐
│ Consumer │
│ (Service) │
└──────┬──────┘
│
Success? ─┼─ No (after max retries)
│ │
▼ ▼
┌─────┐ ┌──────────┐
│ ACK │ │ DLQ │
└─────┘ │(order. │
│ cmds.dlq)│
└──────────┘
│
┌────▼────┐
│ Alert + │
│ Manual │
│ Review │
└─────────┘
6.2 Idempotency
Đảm bảo xử lý message nhiều lần cho cùng kết quả:
@Service
public class PaymentService {
@EventHandler
public void on(ReservePaymentCommand cmd) {
// Idempotency check
String idempotencyKey = "payment:" + cmd.getOrderId();
if (processedStore.exists(idempotencyKey)) {
log.info("Already processed payment for order {}", cmd.getOrderId());
return; // Skip duplicate
}
Payment payment = processPayment(cmd);
// Mark as processed
processedStore.save(idempotencyKey, payment.getId());
}
}
6.3 Saga Timeout
public class OrderSaga {
@SagaTimeout(duration = "5m")
public void onTimeout(SagaState saga) {
log.warn("Saga timeout for order {}", saga.getOrderId());
// Compensate based on current state
switch (saga.getState()) {
case INVENTORY_PENDING:
compensatePayment(saga);
break;
case SHIPPING_PENDING:
compensateInventory(saga);
compensatePayment(saga);
break;
}
saga.setState(State.CANCELLED);
saga.setFailureReason("Saga timeout");
}
}
7. Best Practices
7.1 Saga Design Guidelines
1. Mỗi step phải có compensating action
2. Compensating actions phải idempotent
3. Sử dụng correlation ID (saga ID) xuyên suốt
4. Persist saga state (survive service restart)
5. Set timeout cho mỗi saga instance
6. Monitor saga metrics (success rate, duration, failure reasons)
7. DLQ cho messages không xử lý được
8. Tránh saga quá nhiều steps (> 7 steps → xem lại design)
7.2 Chọn Choreography hay Orchestration?
Flow đơn giản (2-4 services)?
└── Choreography
Flow phức tạp (> 4 services, conditional logic)?
└── Orchestration
Cần visibility cao vào business process?
└── Orchestration
Muốn minimize coupling?
└── Choreography
Team experience với event-driven?
├── Nhiều → Choreography
└── Ít → Orchestration
Tóm tắt
- 2PC không phù hợp cho microservices do tight coupling và lock contention
- Saga Pattern sử dụng chuỗi local transactions + compensating transactions
- Choreography: Services giao tiếp qua events, decentralized
- Orchestration: Saga Orchestrator điều phối centralized qua state machine
- Compensating transactions phải idempotent và không được fail
- Sử dụng DLQ, timeout và idempotency cho error handling