Menjaga Konsistensi Data di Dunia Mikro: Memahami Saga Pattern untuk Transaksi Terdistribusi
1. Pendahuluan
Di dunia monolit, menjaga konsistensi data dalam transaksi yang melibatkan beberapa tabel atau bagian sistem adalah hal yang relatif mudah. Kita punya ACID transactions (Atomicity, Consistency, Isolation, Durability) yang bisa langsung diandalkan. Cukup bungkus operasi dalam satu transaksi database, dan jika ada yang gagal, semua akan di-rollback. Sederhana!
Namun, begitu kita masuk ke arsitektur microservices, cerita berubah drastis. Setiap layanan memiliki database-nya sendiri yang terisolasi. Bagaimana jika satu operasi bisnis memerlukan pembaruan data di tiga layanan yang berbeda? Misalnya, “Pemesanan Produk” yang melibatkan layanan Order, Payment, dan Inventory. Kita tidak bisa lagi melakukan transaksi ACID lintas database yang terpisah ini. Jika pembayaran berhasil tapi stok tidak tersedia, apa yang terjadi? Data menjadi tidak konsisten! 🤯
Di sinilah tantangan besar muncul: bagaimana menjaga konsistensi data di sistem terdistribusi? Kita tidak bisa lagi mengandalkan transaksi database tradisional.
Artikel ini akan membawa Anda menyelami Saga Pattern, sebuah pola desain arsitektur yang kuat untuk mengatasi masalah transaksi terdistribusi ini. Saga memungkinkan kita membangun alur kerja yang melibatkan banyak layanan, memastikan bahwa meskipun ada kegagalan di tengah jalan, sistem dapat kembali ke keadaan yang konsisten melalui serangkaian transaksi kompensasi.
Siap untuk menaklukkan konsistensi data di dunia mikro? Mari kita mulai!
2. Apa Itu Saga Pattern?
📌 Saga Pattern adalah pola manajemen transaksi terdistribusi yang mengelola urutan transaksi lokal. Setiap transaksi lokal memperbarui database-nya sendiri dan mempublikasikan event atau memicu transaksi lokal berikutnya. Jika transaksi lokal gagal, Saga akan mengeksekusi serangkaian transaksi kompensasi untuk membatalkan perubahan yang telah dilakukan oleh transaksi lokal sebelumnya.
Analogi paling mudah untuk memahami Saga adalah proses pemesanan perjalanan. Bayangkan Anda memesan paket liburan yang mencakup:
- Pemesanan tiket pesawat ✈️
- Pemesanan kamar hotel 🏨
- Penyewaan mobil 🚗
Setiap langkah ini adalah “transaksi lokal” yang independen dan dikelola oleh penyedia layanan yang berbeda (maskapai, hotel, rental mobil).
- Jika pemesanan pesawat berhasil, kita lanjut ke hotel.
- Jika pemesanan hotel berhasil, kita lanjut ke rental mobil.
- ✅ Jika semua berhasil, selamat! Liburan Anda terkonfirmasi.
- ❌ Tapi, bagaimana jika pemesanan hotel gagal (misal, kamar penuh)? Kita tidak bisa membiarkan tiket pesawat sudah terpesan tanpa hotel. Di sinilah “transaksi kompensasi” masuk: kita harus membatalkan pemesanan tiket pesawat.
Saga Pattern bekerja persis seperti ini:
- Transaksi Lokal: Setiap langkah dalam alur kerja yang memodifikasi data di satu layanan.
- Transaksi Kompensasi: Operasi yang membatalkan efek dari transaksi lokal sebelumnya jika ada kegagalan di langkah selanjutnya.
Tujuan utama Saga adalah mencapai konsistensi data eventual di seluruh sistem terdistribusi. Artinya, mungkin ada periode singkat di mana sistem tidak sepenuhnya konsisten, tetapi pada akhirnya, semua data akan konsisten (baik itu berhasil diselesaikan atau dikompensasi sepenuhnya).
3. Dua Pendekatan Implementasi Saga
Ada dua cara utama untuk mengimplementasikan Saga Pattern: Choreography dan Orchestration. Keduanya memiliki kelebihan dan kekurangan masing-masing.
A. Choreography-based Saga (Saga Berbasis Koreografi)
Dalam pendekatan Choreography, setiap layanan berpartisipasi dalam Saga secara mandiri. Tidak ada “pusat kendali” atau orkestrator. Sebaliknya, setiap layanan mempublikasikan event setelah menyelesaikan transaksi lokalnya, dan layanan lain yang tertarik akan mendengarkan event tersebut untuk memicu transaksi lokal berikutnya.
💡 Prinsip: Layanan bereaksi terhadap event yang dipublikasikan oleh layanan lain.
Mari kita ambil contoh proses “Pemesanan Produk” (Order -> Payment -> Inventory -> Shipping):
- Layanan Order menerima permintaan
CreateOrder.- Ia membuat entri
Orderdi database-nya (statusPENDING). - Memublikasikan event
OrderCreatedke message broker.
- Ia membuat entri
- Layanan Payment mendengarkan event
OrderCreated.- Ia mencoba memproses pembayaran untuk order tersebut.
- Jika berhasil, memublikasikan event
PaymentProcessed. - Jika gagal, memublikasikan event
PaymentFailed.
- Layanan Inventory mendengarkan event
PaymentProcessed.- Ia mencoba mengurangi stok produk.
- Jika berhasil, memublikasikan event
InventoryUpdated. - Jika gagal (misal, stok habis), memublikasikan event
InventoryUpdateFailed.
- Layanan Shipping mendengarkan event
InventoryUpdated.- Ia membuat jadwal pengiriman.
- Memublikasikan event
ShippingScheduled.
- Layanan Order mendengarkan
ShippingScheduleddan memperbarui status order menjadiCOMPLETED.
Bagaimana jika ada kegagalan?
- Jika Layanan Inventory gagal mengurangi stok (misal,
InventoryUpdateFailed):- Layanan Payment mendengarkan
InventoryUpdateFailed. - Ia mengeksekusi transaksi kompensasi: mengembalikan pembayaran ke pelanggan.
- Memublikasikan event
PaymentRefunded. - Layanan Order mendengarkan
PaymentRefundeddan memperbarui status order menjadiCANCELLED.
- Layanan Payment mendengarkan
sequenceDiagram
participant Client
participant OrderService
participant PaymentService
participant InventoryService
participant ShippingService
participant MessageBroker
Client->>OrderService: Create Order
OrderService->>OrderService: Create Local Order (PENDING)
OrderService->>MessageBroker: Publish OrderCreated Event
MessageBroker-->>PaymentService: OrderCreated Event
PaymentService->>PaymentService: Process Payment
alt Payment Succeeded
PaymentService->>MessageBroker: Publish PaymentProcessed Event
MessageBroker-->>InventoryService: PaymentProcessed Event
InventoryService->>InventoryService: Update Local Inventory
alt Inventory Succeeded
InventoryService->>MessageBroker: Publish InventoryUpdated Event
MessageBroker-->>ShippingService: InventoryUpdated Event
ShippingService->>ShippingService: Schedule Shipping
ShippingService->>MessageBroker: Publish ShippingScheduled Event
MessageBroker-->>OrderService: ShippingScheduled Event
OrderService->>OrderService: Update Order (COMPLETED)
else Inventory Failed
InventoryService->>MessageBroker: Publish InventoryUpdateFailed Event
MessageBroker-->>PaymentService: InventoryUpdateFailed Event
PaymentService->>PaymentService: Refund Payment (Compensation)
PaymentService->>MessageBroker: Publish PaymentRefunded Event
MessageBroker-->>OrderService: PaymentRefunded Event
OrderService->>OrderService: Update Order (CANCELLED)
end
else Payment Failed
PaymentService->>MessageBroker: Publish PaymentFailed Event
MessageBroker-->>OrderService: PaymentFailed Event
OrderService->>OrderService: Update Order (CANCELLED)
end
✅ Kelebihan Choreography:
- Decoupled: Layanan tidak perlu tahu tentang keberadaan layanan lain secara langsung, hanya perlu tahu tentang event.
- Sederhana untuk alur yang tidak terlalu kompleks: Cocok untuk Saga dengan sedikit langkah.
❌ Kekurangan Choreography:
- Sulit melacak alur: Ketika jumlah layanan dan event bertambah, sulit untuk memahami alur Saga secara keseluruhan. Debugging bisa menjadi mimpi buruk.
- Potensi circular dependencies: Jika layanan A merespons event dari B, dan B merespons event dari A.
- Kurang fleksibel: Sulit untuk mengubah alur Saga karena logika tersebar di banyak layanan.
B. Orchestration-based Saga (Saga Berbasis Orkestrasi)
Dalam pendekatan Orchestration, ada satu layanan khusus yang disebut Saga Orchestrator yang bertanggung jawab untuk mengelola dan mengarahkan seluruh alur Saga. Orchestrator ini tahu urutan transaksi lokal yang harus dipanggil dan transaksi kompensasi yang harus dijalankan jika terjadi kegagalan.
🎯 Prinsip: Orchestrator adalah “otak” yang memberi tahu setiap layanan apa yang harus dilakukan.
Kembali ke contoh “Pemesanan Produk”:
- Layanan Order menerima permintaan
CreateOrder.- Ia membuat entri
Orderdi database-nya (statusPENDING). - Mengirimkan perintah
CreateOrderSagake Saga Orchestrator.
- Ia membuat entri
- Saga Orchestrator menerima perintah
CreateOrderSaga.- Ia menyimpan status Saga.
- Mengirimkan perintah
ProcessPaymentke Layanan Payment.
- Layanan Payment memproses pembayaran.
- Mengirimkan respons
PaymentProcessedatauPaymentFailedkembali ke Orchestrator.
- Mengirimkan respons
- Jika
PaymentProcessed:- Orchestrator mengirimkan perintah
UpdateInventoryke Layanan Inventory.
- Orchestrator mengirimkan perintah
- Layanan Inventory memperbarui stok.
- Mengirimkan respons
InventoryUpdatedatauInventoryUpdateFailedkembali ke Orchestrator.
- Mengirimkan respons
- Jika
InventoryUpdated:- Orchestrator mengirimkan perintah
ScheduleShippingke Layanan Shipping.
- Orchestrator mengirimkan perintah
- Layanan Shipping membuat jadwal.
- Mengirimkan respons
ShippingScheduledkembali ke Orchestrator.
- Mengirimkan respons
- Jika
ShippingScheduled:- Orchestrator mengirimkan perintah
CompleteOrderke Layanan Order. - Layanan Order memperbarui status menjadi
COMPLETED. - Orchestrator menandai Saga selesai.
- Orchestrator mengirimkan perintah
Bagaimana jika ada kegagalan?
- Jika Layanan Inventory gagal mengurangi stok dan mengirim
InventoryUpdateFailedke Orchestrator:- Orchestrator mendeteksi kegagalan.
- Ia memulai transaksi kompensasi: Mengirim perintah
RefundPaymentke Layanan Payment. - Setelah
PaymentRefundeddikonfirmasi, mengirim perintahCancelOrderke Layanan Order. - Layanan Order memperbarui status menjadi
CANCELLED. - Orchestrator menandai Saga selesai (dengan status gagal).
sequenceDiagram
participant Client
participant OrderService
participant SagaOrchestrator
participant PaymentService
participant InventoryService
participant ShippingService
Client->>OrderService: Create Order
OrderService->>OrderService: Create Local Order (PENDING)
OrderService->>SagaOrchestrator: Start Saga: CreateOrderSaga(orderId, userId, items)
SagaOrchestrator->>PaymentService: Command: ProcessPayment(orderId, amount)
PaymentService-->>SagaOrchestrator: Reply: PaymentProcessed(orderId)
SagaOrchestrator->>InventoryService: Command: UpdateInventory(orderId, items)
alt Inventory Succeeded
InventoryService-->>SagaOrchestrator: Reply: InventoryUpdated(orderId)
SagaOrchestrator->>ShippingService: Command: ScheduleShipping(orderId, address)
ShippingService-->>SagaOrchestrator: Reply: ShippingScheduled(orderId)
SagaOrchestrator->>OrderService: Command: CompleteOrder(orderId)
OrderService-->>SagaOrchestrator: Reply: OrderCompleted(orderId)
SagaOrchestrator->>SagaOrchestrator: Mark Saga as COMPLETED
else Inventory Failed
InventoryService-->>SagaOrchestrator: Reply: InventoryUpdateFailed(orderId, reason)
SagaOrchestrator->>PaymentService: Command: RefundPayment(orderId) (Compensation)
PaymentService-->>SagaOrchestrator: Reply: PaymentRefunded(orderId)
SagaOrchestrator->>OrderService: Command: CancelOrder(orderId)
OrderService-->>SagaOrchestrator: Reply: OrderCancelled(orderId)
SagaOrchestrator->>SagaOrchestrator: Mark Saga as FAILED
end
✅ Kelebihan Orchestration:
- Kontrol terpusat: Alur Saga jelas dan mudah dilacak.
- Debugging lebih mudah: Semua logika ada di satu tempat.
- Lebih fleksibel: Mudah untuk mengubah alur Saga atau menambahkan langkah baru.
❌ Kekurangan Orchestration:
- Potensi single point of failure: Jika Orchestrator gagal, seluruh alur Saga bisa terhenti. Perlu penanganan high availability.
- Coupling ke Orchestrator: Layanan harus tahu bagaimana berkomunikasi dengan Orchestrator.
4. Memilih Antara Choreography dan Orchestration
Memilih pendekatan yang tepat sangat tergantung pada kompleksitas dan kebutuhan spesifik sistem Anda:
-
Choreography-based Saga:
- Cocok untuk Saga dengan jumlah langkah yang lebih sedikit (misal, 2-3 langkah).
- Ketika Anda ingin meminimalkan coupling antar layanan dan mendorong otonomi maksimal.
- Sistem lebih toleran terhadap konsistensi eventual yang lebih lama.
-
Orchestration-based Saga:
- Cocok untuk Saga dengan banyak langkah dan alur yang kompleks.
- Ketika Anda membutuhkan kontrol ketat atas alur transaksi dan penanganan kegagalan.
- Mempermudah observability dan debugging karena alur terpusat.
- Ketika Anda memiliki tim yang lebih nyaman dengan stateful logic di satu tempat.
Secara umum, untuk Saga yang lebih kompleks dan penting, Orchestration-based Saga seringkali menjadi pilihan yang lebih baik karena kemudahan pengelolaan dan pemeliharaan jangka panjang.
5. Tantangan dan Pertimbangan Praktis
Meskipun Saga Pattern adalah solusi ampuh, ada beberapa tantangan yang perlu Anda perhatikan:
-
Idempotency (Idempoten):
- Transaksi kompensasi dan bahkan transaksi lokal mungkin dipanggil lebih dari sekali (misal, karena retry atau duplikasi event). Pastikan setiap operasi bersifat idempoten, artinya menjalankannya berkali-kali memberikan hasil yang sama dengan menjalankannya sekali.
- Contoh: Refund pembayaran harus bisa dipanggil berkali-kali tanpa menggandakan pengembalian dana.
-
Observability (Keteramatan):
- Melacak perjalanan Saga yang melibatkan banyak layanan bisa sangat menantang. Anda memerlukan distributed tracing (misalnya dengan OpenTelemetry) untuk melihat seluruh alur Saga, dari awal hingga akhir, termasuk transaksi kompensasi.
- Logging yang efektif dan metrik yang jelas di setiap langkah Saga juga krusial.
-
Penanganan Kegagalan Kompensasi:
- Bagaimana jika transaksi kompensasi itu sendiri gagal? Ini adalah skenario langka tapi mungkin. Anda perlu strategi untuk menanganinya, seperti retry dengan backoff, alerting manual, atau bahkan intervensi operator.
- ⚠️ Never-ending Saga: Hindari kondisi di mana Saga tidak bisa selesai atau dikompensasi.
-
Versiing Saga:
- Seiring waktu, alur bisnis Anda mungkin berubah. Bagaimana Anda mengelola perubahan pada alur Saga yang sedang berjalan? Pertimbangkan strategi versiing untuk Saga Orchestrator atau event yang digunakan dalam Choreography.
-
Teknologi Pendukung:
- Message Queues (Kafka, RabbitMQ): Sangat penting untuk Choreography dan juga bisa digunakan Orchestrator untuk mengirim perintah/event.
- Distributed Tracing Tools (Jaeger, Zipkin): Penting untuk observability.
- Saga Frameworks (misal: Cadence, Temporal, Axon Framework): Untuk Orchestration yang lebih canggih, framework ini menyediakan fitur-fitur seperti workflow engine, state management, dan retry logic out-of-the-box.
6. Kapan Menggunakan Saga Pattern?
✅ Gunakan Saga Pattern ketika:
- Anda membangun arsitektur microservices di mana transaksi bisnis melintasi batas layanan.
- Anda tidak bisa (atau tidak ingin) menggunakan transaksi ACID tradisional karena melibatkan multiple independent databases.
- Anda membutuhkan konsistensi data eventual, di mana sistem dapat berada dalam keadaan tidak konsisten untuk sementara waktu sebelum mencapai konsistensi akhir.
- Anda ingin menjaga otonomi layanan dan menghindari coupling yang ketat antar layanan melalui transaksi terdistribusi.
❌ Hindari Saga Pattern ketika:
- Anda bisa menggunakan transaksi ACID tradisional (misal, semua operasi dalam satu database). Jangan mempersulit diri jika tidak perlu!
- Anda membutuhkan konsistensi data yang kuat (strong consistency) secara instan di seluruh sistem. Saga memberikan konsistensi eventual.
- Kompleksitas yang ditambahkan oleh Saga (terutama penanganan kompensasi) tidak sebanding dengan manfaatnya untuk kasus penggunaan Anda.
Kesimpulan
Saga Pattern adalah alat yang sangat berharga dalam kotak peralatan setiap developer yang bekerja dengan arsitektur microservices. Ini memungkinkan kita mengatasi tantangan konsistensi data yang muncul ketika kita memecah monolit dan mengadopsi database per layanan.
Baik Anda memilih Choreography atau Orchestration, memahami prinsip dasar transaksi lokal dan transaksi kompensasi adalah kuncinya. Ingatlah untuk selalu mempertimbangkan tantangan praktis seperti idempotency, observability, dan penanganan kegagalan kompensasi untuk membangun sistem yang tangguh dan dapat diandalkan.
Dengan Saga Pattern, Anda tidak perlu lagi mengorbankan otonomi layanan demi konsistensi data. Anda bisa memiliki keduanya, membangun aplikasi terdistribusi yang kuat dan skalabel!
🔗 Baca Juga
- Event-Driven Architecture (EDA): Membangun Aplikasi Responsif dan Skalabel
- Menggali Lebih Dalam Event Sourcing dan CQRS: Fondasi Sistem yang Auditabel dan Skalabel
- Idempotency dalam Sistem Terdistribusi: Membangun Aplikasi yang Aman dan Konsisten
- Membangun Sistem Tangguh: Mengimplementasikan Circuit Breaker Pattern dalam Aplikasi Anda