Giao diện
🔄 DISTRIBUTED TRANSACTIONS
Two-Phase Commit, Saga Pattern, và Eventual Consistency
📋 Prerequisites
📚 Kiến thức cần có
Trước khi bắt đầu module này, hãy đảm bảo bạn đã nắm vững:
- 🏛️ Monolith vs Microservices - Database per Service principle
- 📊 Consistency Models - ACID vs BASE trade-offs
- 📬 Async Messaging - Event-driven architecture
🎯 Mục tiêu
Sau khi hoàn thành module này, bạn sẽ:
- Hiểu vấn đề của transactions trong distributed systems
- Biết Two-Phase Commit (2PC) và tại sao nó không phù hợp cho Microservices
- Nắm vững Saga Pattern với hai approaches: Choreography và Orchestration
- Hiểu Idempotency và Compensation Transactions
🔥 The Problem: Transactions Across Services
Trong Monolith: ACID là đơn giản
Trong một Monolith với single database, transactions rất đơn giản:
typescript
// Monolith: Single database transaction
async function purchaseCourse(userId: string, courseId: string) {
const transaction = await db.beginTransaction();
try {
// 1. Trừ tiền user
await transaction.query(
'UPDATE wallets SET balance = balance - ? WHERE user_id = ?',
[coursePrice, userId]
);
// 2. Tạo order
await transaction.query(
'INSERT INTO orders (user_id, course_id, status) VALUES (?, ?, ?)',
[userId, courseId, 'completed']
);
// 3. Cấp quyền truy cập course
await transaction.query(
'INSERT INTO enrollments (user_id, course_id) VALUES (?, ?)',
[userId, courseId]
);
await transaction.commit(); // ✅ All or nothing
} catch (error) {
await transaction.rollback(); // ✅ Automatic rollback
throw error;
}
}ACID guarantees:
- Atomicity: Tất cả operations thành công hoặc tất cả rollback
- Consistency: Database luôn ở trạng thái hợp lệ
- Isolation: Transactions không ảnh hưởng lẫn nhau
- Durability: Committed data không bị mất
Trong Microservices: ACID không còn khả thi
Khi chuyển sang Microservices với Database per Service, mỗi service có database riêng:
┌─────────────────────────────────────────────────────────────────┐
│ MICROSERVICES ARCHITECTURE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Order Service│ │Payment Svc │ │Course Svc │ │
│ │ │ │ │ │ │ │
│ │ createOrder │───▶│ chargeUser │───▶│ grantAccess │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ PostgreSQL │ │ MySQL │ │ MongoDB │ │
│ │ (Orders) │ │ (Payments) │ │ (Courses) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ❌ Không thể dùng single transaction cho 3 databases! │
└─────────────────────────────────────────────────────────────────┘⚠️ Vấn đề cốt lõi
Không thể có ACID transaction across multiple databases.
Nếu Payment Service thành công nhưng Course Service fail:
- User đã bị trừ tiền ✅
- Nhưng không được cấp quyền truy cập course ❌
- Data inconsistency!
Ví dụ thực tế: Mua khóa học trên HPN
Giả sử user mua khóa học "System Design Mastery" trên HPN:
| Step | Service | Action | Database |
|---|---|---|---|
| 1 | Order Service | Tạo order | PostgreSQL |
| 2 | Payment Service | Trừ tiền user | MySQL |
| 3 | Inventory Service | Giảm slot (nếu limited) | Redis |
| 4 | Course Service | Cấp quyền truy cập | MongoDB |
| 5 | Notification Service | Gửi email xác nhận | - |
Câu hỏi: Nếu Step 3 fail (hết slot), làm sao rollback Step 1 và 2?
🔐 Two-Phase Commit (2PC)
Cách hoạt động
Two-Phase Commit là protocol cổ điển để đảm bảo atomicity across multiple databases:
┌─────────────────────────────────────────────────────────────────┐
│ TWO-PHASE COMMIT (2PC) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ COORDINATOR │ │
│ │ (Transaction │ │
│ │ Manager) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Participant A│ │Participant B│ │Participant C│ │
│ │ (Order DB) │ │ (Payment DB)│ │ (Course DB) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ PHASE 1: PREPARE (Voting) │
│ ───────────────────────── │
│ Coordinator → All: "Can you commit?" │
│ All → Coordinator: "Yes, I'm ready" or "No, I can't" │
│ │
│ PHASE 2: COMMIT/ABORT │
│ ──────────────────── │
│ If ALL voted YES → Coordinator: "COMMIT" │
│ If ANY voted NO → Coordinator: "ABORT" │
│ │
└─────────────────────────────────────────────────────────────────┘Pseudocode
typescript
// Two-Phase Commit Coordinator
class TwoPhaseCommitCoordinator {
async executeTransaction(participants: Participant[], operations: Operation[]) {
// PHASE 1: PREPARE
const votes: boolean[] = [];
for (let i = 0; i < participants.length; i++) {
try {
// Ask each participant to prepare (lock resources, validate)
const canCommit = await participants[i].prepare(operations[i]);
votes.push(canCommit);
} catch (error) {
votes.push(false);
}
}
// PHASE 2: COMMIT or ABORT
const allVotedYes = votes.every(vote => vote === true);
if (allVotedYes) {
// All participants agreed - COMMIT
for (const participant of participants) {
await participant.commit();
}
return { success: true };
} else {
// At least one participant disagreed - ABORT
for (const participant of participants) {
await participant.abort();
}
return { success: false };
}
}
}Limitations của 2PC
⚠️ Tại sao 2PC không phù hợp cho Microservices
| Limitation | Giải thích |
|---|---|
| Blocking Protocol | Participants phải giữ locks trong suốt quá trình, blocking other transactions |
| Single Point of Failure | Nếu Coordinator chết giữa chừng → participants bị stuck |
| Latency | Cần nhiều round-trips giữa coordinator và participants |
| Tight Coupling | Tất cả participants phải available cùng lúc |
| Not Scalable | Không phù hợp với high-throughput systems |
2PC Failure Scenario:
─────────────────────
Timeline:
T1: Coordinator sends PREPARE to all
T2: All participants vote YES
T3: Coordinator decides COMMIT
T4: Coordinator sends COMMIT to Participant A ✅
T5: Coordinator CRASHES! 💥
Result:
- Participant A: COMMITTED
- Participant B: WAITING (holding locks)
- Participant C: WAITING (holding locks)
→ B và C không biết nên COMMIT hay ABORT
→ Resources bị locked vô thời hạn
→ DEADLOCK!🔧 Raizo's Take on 2PC
"2PC là giải pháp từ thời databases còn chạy trên mainframe. Trong thế giới Microservices với hàng trăm services, 2PC là công thức cho disaster. Tôi đã thấy production systems bị stuck hàng giờ vì coordinator crash."
🎭 Saga Pattern: The Modern Solution
Saga là gì?
Saga Pattern là cách xử lý distributed transactions bằng chuỗi local transactions, mỗi transaction có một compensation transaction tương ứng để rollback nếu cần.
💡 Ý tưởng cốt lõi
Thay vì một big transaction, chia thành nhiều small transactions. Nếu một step fail, chạy compensation transactions để "undo" các steps trước đó.
SAGA = T1 → T2 → T3 → T4
↓ ↓ ↓ ↓
C1 C2 C3 C4 (Compensation transactions)
Nếu T3 fail:
Execute: C2 → C1 (reverse order)Hai Approaches của Saga
1. Choreography (Event-based)
Mỗi service tự quyết định next step dựa trên events:
┌─────────────────────────────────────────────────────────────────┐
│ SAGA: CHOREOGRAPHY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │Order Service│ │Payment Svc │ │Course Svc │ │
│ └──────┬──────┘ └──────┬──────┘ └─────┬─────┘ │
│ │ │ │ │
│ │ OrderCreated │ │ │
│ │──────────────────────▶│ │ │
│ │ │ │ │
│ │ │ PaymentCompleted │ │
│ │ │─────────────────────▶│ │
│ │ │ │ │
│ │ │ │ Grant │
│ │ │ │ Access │
│ │ │ │ │
│ │ CourseAccessGranted │ │ │
│ │◀─────────────────────────────────────────────│ │
│ │ │ │ │
│ │ Complete │ │ │
│ │ Order │ │ │
│ │
│ 📬 Event Bus (Kafka/RabbitMQ) │
│ ═══════════════════════════════════════════════════════════ │
│ │
└─────────────────────────────────────────────────────────────────┘Đặc điểm:
- Không có central coordinator
- Services communicate qua events
- Mỗi service subscribe events và react accordingly
- Decentralized decision making
2. Orchestration (Central Coordinator)
Một Saga Orchestrator điều phối toàn bộ flow:
┌─────────────────────────────────────────────────────────────────┐
│ SAGA: ORCHESTRATION │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ SAGA │ │
│ │ ORCHESTRATOR │ │
│ │ (Order Saga) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Order Service│ │Payment Svc │ │Course Svc │ │
│ │ │ │ │ │ │ │
│ │ 1. Create │ │ 2. Charge │ │ 3. Grant │ │
│ │ Order │ │ User │ │ Access │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Orchestrator controls the flow: │
│ Step 1 → Step 2 → Step 3 │
│ If Step 2 fails → Compensate Step 1 │
│ │
└─────────────────────────────────────────────────────────────────┘Đặc điểm:
- Central orchestrator biết toàn bộ flow
- Orchestrator gọi từng service theo thứ tự
- Dễ track state và handle failures
- Centralized decision making
So sánh Choreography vs Orchestration
| Tiêu chí | Choreography | Orchestration |
|---|---|---|
| Coupling | Loose (services độc lập) | Tighter (phụ thuộc orchestrator) |
| Complexity | Distributed (khó debug) | Centralized (dễ debug) |
| Single Point of Failure | Không có | Orchestrator là SPOF |
| Visibility | Khó track flow | Dễ track flow |
| Scalability | Tốt hơn | Orchestrator có thể bottleneck |
| Use Case | Simple flows, few steps | Complex flows, many steps |
📊 Saga Pattern: Mua khóa học trên HPN
Happy Path Flow
Compensation Flow (Khi có lỗi)
State Machine của Order Saga
📖 Key Terminology
Idempotency
💡 Định nghĩa: Idempotency
Idempotent operation là operation cho kết quả giống nhau dù thực hiện một hay nhiều lần.
f(x) = f(f(x)) = f(f(f(x))) = ...Tại sao Idempotency quan trọng trong Distributed Systems?
Trong distributed systems, network failures có thể gây ra:
- Duplicate messages: Message được gửi 2 lần
- Retry logic: Client retry khi không nhận được response
- At-least-once delivery: Message queue đảm bảo delivery bằng cách retry
typescript
// ❌ NON-IDEMPOTENT: Mỗi lần gọi tăng balance
async function addBalance(userId: string, amount: number) {
await db.query(
'UPDATE wallets SET balance = balance + ? WHERE user_id = ?',
[amount, userId]
);
}
// Gọi 2 lần → balance tăng 2 lần! 💥
// ✅ IDEMPOTENT: Dùng idempotency key
async function addBalanceIdempotent(
userId: string,
amount: number,
idempotencyKey: string
) {
// Check if already processed
const existing = await db.query(
'SELECT * FROM processed_transactions WHERE idempotency_key = ?',
[idempotencyKey]
);
if (existing) {
return existing.result; // Return cached result
}
// Process and store idempotency key
await db.transaction(async (tx) => {
await tx.query(
'UPDATE wallets SET balance = balance + ? WHERE user_id = ?',
[amount, userId]
);
await tx.query(
'INSERT INTO processed_transactions (idempotency_key, result) VALUES (?, ?)',
[idempotencyKey, 'success']
);
});
return 'success';
}
// Gọi 2 lần với cùng key → balance chỉ tăng 1 lần ✅Common Idempotency Patterns:
| Pattern | Mô tả | Ví dụ |
|---|---|---|
| Idempotency Key | Client gửi unique key với mỗi request | X-Idempotency-Key: uuid-123 |
| Conditional Update | Chỉ update nếu state match | UPDATE ... WHERE version = ? |
| Upsert | Insert or Update based on key | INSERT ... ON CONFLICT DO UPDATE |
| Deduplication | Track processed message IDs | Store message_id in Redis |
Compensation Transaction
💡 Định nghĩa: Compensation Transaction
Compensation Transaction là transaction được thiết kế để "undo" effect của một transaction trước đó.
Lưu ý quan trọng: Compensation KHÔNG phải là rollback. Nó là một forward transaction tạo ra effect ngược lại.
typescript
// Original Transaction
async function chargeUser(userId: string, amount: number, orderId: string) {
await db.query(
'UPDATE wallets SET balance = balance - ? WHERE user_id = ?',
[amount, userId]
);
await db.query(
'INSERT INTO transactions (order_id, type, amount) VALUES (?, ?, ?)',
[orderId, 'CHARGE', amount]
);
}
// Compensation Transaction (NOT a rollback!)
async function refundUser(userId: string, amount: number, orderId: string) {
// Tạo một transaction MỚI để hoàn tiền
// Không phải xóa transaction cũ!
await db.query(
'UPDATE wallets SET balance = balance + ? WHERE user_id = ?',
[amount, userId]
);
await db.query(
'INSERT INTO transactions (order_id, type, amount) VALUES (?, ?, ?)',
[orderId, 'REFUND', amount] // New transaction record
);
}Compensation vs Rollback:
| Aspect | Database Rollback | Compensation Transaction |
|---|---|---|
| Timing | Trước khi commit | Sau khi commit |
| Mechanism | Undo log | New forward transaction |
| Audit Trail | Không có | Có (cả original và compensation) |
| Scope | Single database | Cross-service |
💻 Code Examples: Saga Implementation
Choreography Implementation (TypeScript)
typescript
// ============================================
// CHOREOGRAPHY SAGA: Event-based approach
// ============================================
// Event Types
interface OrderCreatedEvent {
type: 'ORDER_CREATED';
orderId: string;
userId: string;
courseId: string;
amount: number;
}
interface PaymentCompletedEvent {
type: 'PAYMENT_COMPLETED';
orderId: string;
transactionId: string;
}
interface PaymentFailedEvent {
type: 'PAYMENT_FAILED';
orderId: string;
reason: string;
}
interface SlotReservedEvent {
type: 'SLOT_RESERVED';
orderId: string;
courseId: string;
}
interface SlotReservationFailedEvent {
type: 'SLOT_RESERVATION_FAILED';
orderId: string;
reason: string;
}
// Order Service - Initiates Saga
class OrderService {
constructor(
private eventBus: EventBus,
private orderRepository: OrderRepository
) {
// Listen for completion/failure events
this.eventBus.subscribe('SLOT_RESERVED', this.onSlotReserved.bind(this));
this.eventBus.subscribe('PAYMENT_FAILED', this.onPaymentFailed.bind(this));
this.eventBus.subscribe('SLOT_RESERVATION_FAILED', this.onSlotFailed.bind(this));
}
async createOrder(userId: string, courseId: string, amount: number): Promise<string> {
const orderId = generateUUID();
// T1: Create order in PENDING state
await this.orderRepository.create({
id: orderId,
userId,
courseId,
amount,
status: 'PENDING'
});
// Publish event to trigger next step
await this.eventBus.publish<OrderCreatedEvent>({
type: 'ORDER_CREATED',
orderId,
userId,
courseId,
amount
});
return orderId;
}
private async onSlotReserved(event: SlotReservedEvent): Promise<void> {
// Saga completed successfully
await this.orderRepository.updateStatus(event.orderId, 'COMPLETED');
}
private async onPaymentFailed(event: PaymentFailedEvent): Promise<void> {
// C1: Cancel order (no payment to refund)
await this.orderRepository.updateStatus(event.orderId, 'CANCELLED');
}
private async onSlotFailed(event: SlotReservationFailedEvent): Promise<void> {
// Order will be cancelled after refund completes
await this.orderRepository.updateStatus(event.orderId, 'REFUNDING');
}
}
// Payment Service - Step 2
class PaymentService {
constructor(
private eventBus: EventBus,
private paymentRepository: PaymentRepository
) {
this.eventBus.subscribe('ORDER_CREATED', this.onOrderCreated.bind(this));
this.eventBus.subscribe('SLOT_RESERVATION_FAILED', this.onSlotFailed.bind(this));
}
private async onOrderCreated(event: OrderCreatedEvent): Promise<void> {
try {
// T2: Charge user
const transactionId = await this.chargeUser(
event.userId,
event.amount,
event.orderId
);
await this.eventBus.publish<PaymentCompletedEvent>({
type: 'PAYMENT_COMPLETED',
orderId: event.orderId,
transactionId
});
} catch (error) {
await this.eventBus.publish<PaymentFailedEvent>({
type: 'PAYMENT_FAILED',
orderId: event.orderId,
reason: error.message
});
}
}
private async onSlotFailed(event: SlotReservationFailedEvent): Promise<void> {
// C2: Refund user (compensation)
await this.refundUser(event.orderId);
await this.eventBus.publish({
type: 'REFUND_COMPLETED',
orderId: event.orderId
});
}
private async chargeUser(
userId: string,
amount: number,
orderId: string
): Promise<string> {
// Idempotent charge using orderId as idempotency key
return await this.paymentRepository.charge(userId, amount, orderId);
}
private async refundUser(orderId: string): Promise<void> {
await this.paymentRepository.refund(orderId);
}
}
// Inventory Service - Step 3
class InventoryService {
constructor(
private eventBus: EventBus,
private inventoryRepository: InventoryRepository
) {
this.eventBus.subscribe('PAYMENT_COMPLETED', this.onPaymentCompleted.bind(this));
}
private async onPaymentCompleted(event: PaymentCompletedEvent): Promise<void> {
const order = await this.getOrderDetails(event.orderId);
try {
// T3: Reserve slot
await this.inventoryRepository.reserveSlot(order.courseId, event.orderId);
await this.eventBus.publish<SlotReservedEvent>({
type: 'SLOT_RESERVED',
orderId: event.orderId,
courseId: order.courseId
});
} catch (error) {
await this.eventBus.publish<SlotReservationFailedEvent>({
type: 'SLOT_RESERVATION_FAILED',
orderId: event.orderId,
reason: error.message
});
}
}
}Orchestration Implementation (TypeScript)
typescript
// ============================================
// ORCHESTRATION SAGA: Central coordinator
// ============================================
// Saga State
interface OrderSagaState {
orderId: string;
userId: string;
courseId: string;
amount: number;
currentStep: 'INIT' | 'ORDER_CREATED' | 'PAYMENT_COMPLETED' | 'SLOT_RESERVED' | 'COMPLETED' | 'COMPENSATING' | 'CANCELLED';
paymentTransactionId?: string;
error?: string;
}
// Saga Orchestrator
class OrderSagaOrchestrator {
constructor(
private orderService: OrderServiceClient,
private paymentService: PaymentServiceClient,
private inventoryService: InventoryServiceClient,
private notificationService: NotificationServiceClient,
private sagaRepository: SagaRepository
) {}
async execute(userId: string, courseId: string, amount: number): Promise<string> {
const sagaId = generateUUID();
// Initialize saga state
const state: OrderSagaState = {
orderId: sagaId,
userId,
courseId,
amount,
currentStep: 'INIT'
};
await this.sagaRepository.save(sagaId, state);
try {
// Step 1: Create Order
await this.orderService.createOrder(state.orderId, userId, courseId, amount);
state.currentStep = 'ORDER_CREATED';
await this.sagaRepository.save(sagaId, state);
// Step 2: Process Payment
const transactionId = await this.paymentService.charge(userId, amount, state.orderId);
state.paymentTransactionId = transactionId;
state.currentStep = 'PAYMENT_COMPLETED';
await this.sagaRepository.save(sagaId, state);
// Step 3: Reserve Slot
await this.inventoryService.reserveSlot(courseId, state.orderId);
state.currentStep = 'SLOT_RESERVED';
await this.sagaRepository.save(sagaId, state);
// Step 4: Send Notification
await this.notificationService.sendConfirmation(userId, courseId, state.orderId);
// Complete saga
state.currentStep = 'COMPLETED';
await this.sagaRepository.save(sagaId, state);
await this.orderService.completeOrder(state.orderId);
return state.orderId;
} catch (error) {
// Compensation flow
state.error = error.message;
state.currentStep = 'COMPENSATING';
await this.sagaRepository.save(sagaId, state);
await this.compensate(state);
state.currentStep = 'CANCELLED';
await this.sagaRepository.save(sagaId, state);
throw new SagaFailedError(state.orderId, error.message);
}
}
private async compensate(state: OrderSagaState): Promise<void> {
// Compensate in REVERSE order
const compensationSteps: Array<() => Promise<void>> = [];
// Only compensate steps that were completed
switch (state.currentStep) {
case 'SLOT_RESERVED':
compensationSteps.push(() =>
this.inventoryService.releaseSlot(state.courseId, state.orderId)
);
// Fall through to compensate previous steps
case 'PAYMENT_COMPLETED':
compensationSteps.push(() =>
this.paymentService.refund(state.orderId)
);
case 'ORDER_CREATED':
compensationSteps.push(() =>
this.orderService.cancelOrder(state.orderId)
);
break;
}
// Execute compensations in order (which is reverse of original)
for (const compensate of compensationSteps) {
try {
await compensate();
} catch (compensationError) {
// Log and continue - compensation must be best-effort
console.error('Compensation failed:', compensationError);
// In production: alert, manual intervention queue
}
}
}
}
// Usage
const orchestrator = new OrderSagaOrchestrator(
orderService,
paymentService,
inventoryService,
notificationService,
sagaRepository
);
try {
const orderId = await orchestrator.execute('user-123', 'course-456', 100);
console.log('Order completed:', orderId);
} catch (error) {
console.error('Order failed:', error.message);
}🎓 Giáo sư Tom's Insight
🎓 Góc nhìn Lý thuyết
BASE vs ACID
Trong distributed systems, chúng ta trade ACID cho BASE:
| ACID | BASE |
|---|---|
| Atomicity | Basically Available |
| Consistency | Soft state |
| Isolation | Eventual consistency |
| Durability |
Saga Pattern là implementation của BASE model:
- Basically Available: System vẫn hoạt động dù có failures
- Soft State: State có thể thay đổi over time (PENDING → COMPLETED)
- Eventual Consistency: Data sẽ consistent sau khi saga hoàn thành
CAP Theorem Connection
Saga Pattern chấp nhận Partition Tolerance và Availability, sacrifice strong Consistency cho eventual consistency.
CAP Theorem:
┌─────────────────────────────────────┐
│ Consistency │
│ /\ │
│ / \ │
│ / \ │
│ / SAGA \ │
│ / ⭐ \ │
│ / \ │
│ /____________\ │
│ Availability Partition │
│ Tolerance │
└─────────────────────────────────────┘Semantic Locks
Saga sử dụng semantic locks thay vì database locks:
- Order status = PENDING là một semantic lock
- Các services khác biết order đang được xử lý
- Không block database resources
🔧 Kỹ sư Raizo's Reality Check
🔧 Góc nhìn Thực chiến
"Saga không phải silver bullet"
"Saga giải quyết được distributed transactions, nhưng đừng nghĩ nó miễn phí. Đây là những gì tôi học được sau nhiều năm production:"
Pitfalls thực tế
| Pitfall | Mô tả | Giải pháp |
|---|---|---|
| Compensation Failures | Compensation cũng có thể fail! | Retry với exponential backoff, dead letter queue |
| Partial Failures | Một số compensations thành công, một số không | Saga state machine, manual intervention queue |
| Ordering Issues | Events đến không đúng thứ tự | Sequence numbers, event sourcing |
| Duplicate Events | Network retry gây duplicate | Idempotency keys everywhere |
| Long-running Sagas | Saga chạy quá lâu | Timeouts, saga expiration |
Production Checklist
markdown
✅ Saga Checklist:
- [ ] Mọi operation đều idempotent
- [ ] Mọi step đều có compensation
- [ ] Compensation có retry logic
- [ ] Có timeout cho toàn bộ saga
- [ ] Có monitoring cho saga state
- [ ] Có alerting khi saga stuck
- [ ] Có manual intervention UI
- [ ] Có dead letter queue cho failed compensationsKhi nào KHÔNG dùng Saga
"Đừng dùng Saga khi bạn có thể tránh được distributed transactions:"
- Có thể dùng single database? → Dùng ACID transaction
- Có thể merge services? → Giảm số services
- Có thể dùng eventual consistency đơn giản? → Không cần compensation
💡 Think About It
🤔 Câu hỏi suy ngẫm
Idempotency quan trọng như thế nào? Nếu payment service không idempotent, điều gì xảy ra khi network timeout và client retry?
Tại sao Compensation Transaction không phải là rollback? Sự khác biệt này quan trọng như thế nào cho audit trail?
Choreography vs Orchestration: Nếu bạn có 10 services trong một saga, approach nào dễ debug hơn? Approach nào scale tốt hơn?
Trong ví dụ "Mua khóa học", nếu Notification Service fail, có cần rollback payment không? Tại sao?
🎯 Interactive Scenario
🎯 Tình huống: E-commerce Order Processing
Bối cảnh
Bạn đang thiết kế hệ thống order processing cho một e-commerce platform:
| Thông tin | Chi tiết |
|---|---|
| Services | Order, Payment, Inventory, Shipping, Notification |
| Daily Orders | 100,000+ orders/day |
| SLA | 99.9% availability |
| Requirement | Không được mất tiền của customer |
Flow hiện tại (có vấn đề)
typescript
async function processOrder(order: Order) {
// Synchronous chain - BAD!
await orderService.create(order);
await paymentService.charge(order.userId, order.total);
await inventoryService.reserve(order.items);
await shippingService.createShipment(order);
await notificationService.sendConfirmation(order);
}Vấn đề: Nếu inventoryService.reserve() fail sau khi payment đã charge, customer mất tiền!
Câu hỏi
Bạn sẽ thiết kế lại như thế nào?
A) Two-Phase Commit - Dùng distributed transaction coordinator
B) Saga với Choreography - Mỗi service publish events, services khác react
C) Saga với Orchestration - Central OrderSaga coordinator điều phối flow
D) Giữ nguyên - Thêm try-catch và manual refund
🔧 Raizo's Verdict
Nếu chọn A (2PC):"2PC với 5 services và 100k orders/day? Bạn sẽ có latency khủng khiếp và coordinator sẽ là bottleneck. Đừng làm vậy."
Nếu chọn D (Giữ nguyên):"Manual refund cho 100k orders/day? Team support của bạn sẽ nghỉ việc trong tuần đầu tiên."
✅ Đáp án đúng: B hoặc C (Saga Pattern)
Phân tích
Cả Choreography (B) và Orchestration (C) đều là valid choices, tùy thuộc vào context:
Chọn Choreography khi:
- Team có experience với event-driven architecture
- Muốn loose coupling tối đa
- Có sẵn robust event infrastructure (Kafka)
Chọn Orchestration khi:
- Flow phức tạp, cần visibility
- Team mới với Saga pattern
- Cần dễ debug và monitor
Recommended Design (Orchestration)
typescript
class OrderSaga {
async execute(order: Order): Promise<void> {
const saga = await this.initSaga(order);
try {
// Step 1: Create Order (PENDING)
await this.orderService.create(order);
saga.step = 'ORDER_CREATED';
// Step 2: Reserve Inventory FIRST (before payment!)
await this.inventoryService.reserve(order.items);
saga.step = 'INVENTORY_RESERVED';
// Step 3: Charge Payment (after inventory confirmed)
await this.paymentService.charge(order);
saga.step = 'PAYMENT_COMPLETED';
// Step 4: Create Shipment
await this.shippingService.createShipment(order);
saga.step = 'SHIPMENT_CREATED';
// Step 5: Notification (non-critical, no compensation needed)
await this.notificationService.sendConfirmation(order);
saga.step = 'COMPLETED';
} catch (error) {
await this.compensate(saga);
}
}
}Key insight: Reserve inventory TRƯỚC payment để tránh charge customer khi hết hàng!
:::
📝 Summary
| Concept | Key Takeaway |
|---|---|
| Distributed Transactions | ACID không khả thi across multiple databases |
| Two-Phase Commit | Blocking, không scalable, avoid trong Microservices |
| Saga Pattern | Chuỗi local transactions + compensation transactions |
| Choreography | Event-based, loose coupling, khó debug |
| Orchestration | Central coordinator, dễ debug, có SPOF |
| Idempotency | BẮT BUỘC cho mọi operation trong distributed systems |
| Compensation | Forward transaction để "undo", không phải rollback |
🚀 Next Steps
Tiếp theo trong Phase 3
👉 Module 3.3: Advanced Patterns →
Học về CQRS, Event Sourcing, và Service Mesh.
Related Topics
| Topic | Liên quan |
|---|---|
| 📊 Consistency Models | Strong vs Eventual Consistency |
| 📬 Async Messaging | Event-driven architecture foundation |
| 🏛️ Monolith vs Microservices | Database per Service principle |
Case Studies áp dụng
| Case Study | Áp dụng |
|---|---|
| 🚗 Design Uber | Saga trong ride booking flow |
| 🐦 Design Twitter | Event-driven architecture |
📖 Further Reading
- Saga Pattern - Chris Richardson
- Compensating Transaction - Microsoft Azure
- Idempotency Patterns - Stripe API Docs