Saga Pattern: Mengelola Distributed Transactions
Daftar Isi
- Pendahuluan
- Kenapa Distributed Transaction Sulit?
- Apa yang Dilakukan Saga Pattern?
- Choreography vs Orchestration
- Contoh: Saga Checkout Order
- Diagram Saga Orchestration
- Contoh Kode Saga Orchestrator
- Compensating Action
- Library dan Tool yang Bisa Dipakai
- Hal Operasional yang Perlu Disiapkan
- Kesalahan Umum
- Checklist
- FAQ
- Kesimpulan
Pendahuluan
Dalam monolith, checkout sering bisa berjalan dalam satu database transaction. Buat order, reserve inventory, charge payment, lalu commit semuanya bersama.
Dalam distributed system, langkah-langkah itu bisa berada di service berbeda. Order service, inventory service, payment service, dan shipping service masing-masing punya data sendiri. Satu database transaction tidak lagi mencakup seluruh workflow.
Saga pattern hadir sebagai solusi untuk mengoordinasikan workflow bisnis berdurasi panjang antar-service, tanpa memaksakan semua proses berjalan secara atomic.
Kenapa Distributed Transaction Sulit?
Distributed transaction sulit karena setiap service bisa gagal sendiri-sendiri.
| Langkah | Kemungkinan gagal |
|---|---|
| Buat order | database timeout |
| Reserve inventory | barang habis |
| Charge payment | payment provider error |
| Buat shipment | shipping service unavailable |
Jika payment berhasil tapi shipment gagal, sistem perlu keputusan bisnis. Apakah shipment dicoba lagi? Order dibatalkan? Payment di-refund? Perlu manual review?
Saga membuat keputusan seperti itu menjadi lebih eksplisit.
Apa yang Dilakukan Saga Pattern?
Saga memecah satu business transaction besar menjadi rangkaian local transaction yang lebih kecil.
Setiap langkah commit secara lokal. Jika langkah berikutnya gagal, saga menjalankan compensating action untuk membatalkan atau menyeimbangkan efek langkah sebelumnya.
Buat order -> Reserve stock -> Charge payment -> Buat shipment
Jika charge payment gagal:
Release stock -> Tandai order gagal
Compensation tidak selalu rollback sempurna. Ia adalah aksi bisnis untuk membuat sistem konsisten kembali.
Choreography vs Orchestration
Ada dua cara umum menerapkan saga.
| Style | Cara kerja | Cocok untuk |
|---|---|---|
| Choreography | service bereaksi terhadap event dari service lain | workflow kecil dengan dependency sederhana |
| Orchestration | satu coordinator memberi instruksi ke service lain | workflow kompleks yang butuh visibility dan kontrol |
Choreography terasa ringan, tapi bisa sulit dilacak. Orchestration lebih mudah diobservasi, tapi menambah komponen workflow pusat.
Contoh: Saga Checkout Order
Checkout saga dengan orchestration bisa terlihat seperti ini:
| Langkah | Command | Success event | Compensation saat gagal |
|---|---|---|---|
| 1 | Buat order | OrderCreated | tandai order gagal |
| 2 | Reserve inventory | InventoryReserved | release inventory |
| 3 | Charge payment | PaymentCaptured | refund payment |
| 4 | Buat shipment | ShipmentCreated | cancel shipment |
Saga coordinator menyimpan progress:
| order_id | state | last_step |
|---|---|---|
| O1001 | payment_captured | charge payment |
| O1002 | failed | reserve inventory |
| O1003 | completed | create shipment |
State ini penting untuk retry, debugging, dan support.
Diagram Saga Orchestration
Berikut contoh alur checkout saga dengan model orchestration. CheckoutSaga menjadi coordinator yang menentukan langkah berikutnya, menyimpan state, dan menjalankan compensation saat ada step yang gagal.
sequenceDiagram
autonumber
participant User
participant Saga as CheckoutSaga
participant Order as Order Service
participant Inventory as Inventory Service
participant Payment as Payment Service
participant Shipping as Shipping Service
User->>Saga: Place order
Saga->>Order: Create order
Order-->>Saga: OrderCreated
Saga->>Inventory: Reserve inventory
Inventory-->>Saga: InventoryReserved
Saga->>Payment: Capture payment
alt payment succeeds
Payment-->>Saga: PaymentCaptured
Saga->>Shipping: Create shipment
Shipping-->>Saga: ShipmentCreated
Saga-->>User: Checkout completed
else payment fails
Payment-->>Saga: PaymentFailed
Saga->>Inventory: Release inventory
Saga->>Order: Mark order failed
Saga-->>User: Checkout failed
end
Dalam choreography, diagramnya akan berbeda. Tidak ada coordinator pusat. Setiap service publish event, lalu service lain bereaksi terhadap event tersebut. Itu bisa lebih simpel untuk workflow kecil, tapi biasanya lebih sulit dilacak saat flow mulai bercabang.
Contoh Kode Saga Orchestrator
Contoh berikut sengaja dibuat sederhana. Tujuannya bukan menjadi production-ready framework, tapi menunjukkan bagaimana orchestrator menjalankan step berurutan dan memanggil compensation secara terbalik jika ada kegagalan.
type SagaContext = {
orderId: string;
userId: string;
items: Array<{ sku: string; quantity: number }>;
paymentId?: string;
shipmentId?: string;
};
type SagaStep = {
name: string;
execute: (context: SagaContext) => Promise<void>;
compensate: (context: SagaContext) => Promise<void>;
};
async function runSaga(context: SagaContext, steps: SagaStep[]) {
const completedSteps: SagaStep[] = [];
try {
for (const step of steps) {
await step.execute(context);
completedSteps.push(step);
await saveSagaState(context.orderId, step.name, "completed");
}
await saveSagaState(context.orderId, "checkout", "completed");
} catch (error) {
await saveSagaState(context.orderId, "checkout", "compensating");
for (const step of completedSteps.reverse()) {
await step.compensate(context);
await saveSagaState(context.orderId, step.name, "compensated");
}
await saveSagaState(context.orderId, "checkout", "failed");
throw error;
}
}
Pemakaiannya:
const checkoutSteps: SagaStep[] = [
{
name: "create_order",
execute: async (context) => {
await orderService.createOrder(context.orderId, context.userId, context.items);
},
compensate: async (context) => {
await orderService.markOrderFailed(context.orderId);
},
},
{
name: "reserve_inventory",
execute: async (context) => {
await inventoryService.reserve(context.orderId, context.items);
},
compensate: async (context) => {
await inventoryService.release(context.orderId);
},
},
{
name: "capture_payment",
execute: async (context) => {
context.paymentId = await paymentService.capture(context.orderId);
},
compensate: async (context) => {
if (context.paymentId) {
await paymentService.refund(context.paymentId);
}
},
},
{
name: "create_shipment",
execute: async (context) => {
context.shipmentId = await shippingService.createShipment(context.orderId);
},
compensate: async (context) => {
if (context.shipmentId) {
await shippingService.cancelShipment(context.shipmentId);
}
},
},
];
await runSaga(
{
orderId: "O1004",
userId: "U2001",
items: [{ sku: "SKU-1", quantity: 2 }],
},
checkoutSteps,
);
Ada beberapa hal penting yang belum ditangani contoh kecil ini: retry dengan backoff, timeout per step, idempotency key, concurrency control, dead-letter queue, dan recovery kalau proses mati di tengah jalan. Di production, bagian-bagian itu biasanya ditangani oleh workflow engine atau message framework.
Compensating Action
Compensating action adalah inti desain saga.
Contohnya:
- release reserved inventory
- refund payment
- cancel shipment
- tandai order sebagai failed
- buat support ticket untuk manual handling
Compensation yang baik harus idempotent. Jika sistem mengulang compensation, tidak boleh terjadi refund dua kali atau inventory dilepas dua kali.
Library dan Tool yang Bisa Dipakai
Kamu bisa menerapkan saga sendiri, tapi untuk workflow penting biasanya lebih aman memakai library atau workflow engine yang sudah menangani state, retry, timeout, observability, dan recovery.
Beberapa opsi yang umum:
| Tool | Cocok untuk |
|---|---|
| Temporal | workflow durable berbasis code, cocok untuk orchestration jangka panjang |
| Camunda / Zeebe | proses bisnis berbasis BPMN, cocok saat workflow perlu terlihat jelas oleh tim bisnis dan engineering |
| AWS Step Functions | orchestration serverless di ekosistem AWS |
| Azure Durable Functions | workflow durable di ekosistem Azure |
| MassTransit | saga state machine dan routing slip di ekosistem .NET/message bus |
| Netflix Conductor | workflow orchestration untuk microservices |
Kalau sistem masih kecil, implementasi sederhana dengan database state table dan message queue bisa cukup. Begitu workflow makin panjang, punya banyak cabang, atau butuh audit trail, workflow engine biasanya lebih masuk akal.
Hal Operasional yang Perlu Disiapkan
Saga bukan cuma design pattern. Ia butuh dukungan operasional.
Yang perlu disiapkan:
- saga state yang durable
- aturan retry
- timeout handling
- idempotency key
- dead-letter queue atau jalur manual review
- visibility untuk workflow yang stuck
- ownership aturan compensation dari sisi bisnis
Tanpa observability, saga bisa gagal diam-diam dan membuat user bingung.
Kesalahan Umum
Menganggap compensation sebagai rollback teknis
Compensation adalah keputusan bisnis. Refund, cancellation, atau manual review bisa sama-sama valid tergantung domain.
Lupa idempotency
Message bisa terkirim lebih dari sekali. Setiap command dan compensation harus aman untuk diulang.
Menyembunyikan state saga
Jika support dan engineering tidak bisa melihat workflow berhenti di mana, incident akan lebih sulit diselesaikan.
Memakai saga untuk workflow sederhana
Tidak semua proses multi-step butuh kompleksitas saga. Gunakan saat workflow melewati batas service dan failure handling memang penting.
Checklist
- Definisikan setiap local transaction dengan jelas.
- Definisikan compensation untuk setiap langkah.
- Buat command dan compensation idempotent.
- Simpan saga state secara durable.
- Tambahkan timeout dan retry policy.
- Track correlation ID antar service.
- Tampilkan workflow yang stuck untuk support atau operasi.
- Tentukan kapan perlu manual intervention.
- Test failure di setiap langkah.
FAQ
Apakah saga sama dengan two-phase commit?
Tidak. Two-phase commit mencoba membuat pekerjaan distributed menjadi atomic. Saga menerima local commit dan memakai compensation saat langkah berikutnya gagal.
Pilih choreography atau orchestration?
Gunakan choreography untuk event chain sederhana. Gunakan orchestration saat workflow punya banyak cabang, urutan kuat, atau butuh visibility pusat.
Apakah saga menjamin strong consistency?
Tidak. Saga biasanya memberi eventual consistency. Sistem bisa berada di state sementara sebelum semua langkah selesai. Untuk audit trail dan pencatatan state perubahan saga yang andal, pattern ini sering dipadukan dengan Event Sourcing dan CQRS Pattern. Selain itu, tempatkan saga handler atau orchestrator di dalam Application Layer sesuai prinsip Hexagonal Architecture agar business logic terpisah dari message broker.
Kesimpulan
Saga pattern membantu tim mengelola workflow bisnis lintas service. Pattern ini tidak menghapus failure, tapi memberi sistem cara yang jelas untuk merespons saat failure terjadi.
Desain workflow, compensation, retry, dan observability sebagai satu paket. Di situlah saga berubah dari diagram menjadi pattern yang siap production.
Artikel Terkait
Lanjutkan membaca topik yang masih satu konteks.
Circuit Breaker Pattern: Membangun Sistem Resilient
Pelajari circuit breaker pattern lewat state closed, open, half-open, retry, timeout, fallback, observability, dan trade-off production.
Event Sourcing: Membangun Sistem yang Dapat Diaudit
Pahami event sourcing lewat contoh praktis, event stream, projection, audit trail, trade-off, dan kapan pattern ini layak dipakai.
Hexagonal Architecture: Memahami Ports dan Adapters
Pahami hexagonal architecture lewat ports, adapters, arah dependency, manfaat testing, contoh use case, trade-off, dan panduan implementasi praktis.
NoSQL vs Relational: Memilih Database yang Tepat untuk Proyek Kamu
Kerangka praktis untuk memilih database relational atau NoSQL berdasarkan bentuk data, query, konsistensi, dan kebutuhan operasional.