Kompensasi Saga Bukan Transaction Rollback
Operasi yang melibatkan banyak service dapat melewati inventory, payment, shipping, dan sistem lain yang melakukan commit secara independen. Setelah satu service melakukan commit, kegagalan pada step berikutnya tidak dapat membuat commit sebelumnya hilang melalui database rollback biasa.
Saga menangani boundary ini dengan memasangkan forward action dengan recovery action yang eksplisit. Jika step berikutnya gagal, coordinator menjalankan kompensasi untuk step sebelumnya yang sudah selesai ketika business process mengizinkannya.
Kompensasi adalah operasi baru. Mekanisme ini bukan perjalanan kembali ke masa lalu.
Local commit tetap benar-benar terjadi
Bayangkan checkout flow berikut:
1. reserve inventory
2. charge payment
3. create shipmentSetiap service memiliki durable state sendiri. Misalkan inventory reservation sudah commit, lalu payment service menolak charge. Inventory transaction sudah berakhir. Transaction coordinator tidak dapat menjalankan ROLLBACK terhadap local transaction yang sudah selesai kecuali arsitektur sejak awal memakai distributed transaction protocol terpisah.
Saga justru mengirim command lain:
reserve inventory -> committed
charge payment -> declined
release inventory -> compensationFinal business state mungkin menyerupai state sebelum reservation, tetapi riwayatnya berbeda. Reservation sempat ada. Log, metric, audit record, notification, cache, atau observer lain mungkin sudah melihatnya.
Perbedaan ini penting ketika forward action memiliki effect di luar satu mutable row.
Kompensasi memerlukan domain semantics
Inverse generik seperti “kurangi nilai yang sebelumnya ditambahkan” sering tidak cukup. Kompensasi yang benar bergantung pada business state saat recovery berjalan.
Hotel booking mungkin dapat dibatalkan sebelum check-in tetapi dikenai biaya setelah batas tertentu. Shipment mungkin dapat dibatalkan sebelum carrier pickup tetapi memerlukan return process setelahnya. Payment authorization dapat di-void sebelum capture, sedangkan payment yang sudah captured mungkin memerlukan refund.
Semua itu merupakan domain operation yang berbeda, bukan inverse mekanis.
Definisi saga karena itu perlu menyatakan forward step mana yang dapat dikompensasi, kompensasi apa yang berlaku pada setiap state, dan step mana yang menjadi irreversible setelah boundary tertentu.
authorize payment -> void authorization
capture payment -> refund payment
ship parcel -> return workflow, bukan "unship"Memberi nama kompensasi berdasarkan business action yang nyata menjaga perbedaan ini tetap terlihat di code dan operasi.
Kompensasi juga dapat gagal
Recovery code berjalan melewati network dan service boundary yang sama tidak andalnya dengan forward code. Timeout, service outage, concurrency conflict, atau policy rejection dapat membuat kompensasi gagal.
Saga coordinator harus menyimpan progress secara durable agar recovery dapat berlanjut setelah process restart. Menyimpan seluruh saga state hanya di memory membuat kegagalan coordinator berubah menjadi recovery work yang hilang.
Minimal state model dapat mencatat:
saga_id: checkout-918
step: payment
status: compensating
completed:
- reserve_inventory
pending_compensation:
- release_inventoryRepresentasi persisnya dapat berbeda, tetapi coordinator memerlukan durable information yang cukup untuk menentukan action berikutnya tanpa menyusun kembali intent dari log.
Retry juga memerlukan command identity yang stabil. Jika timeout membuat coordinator tidak yakin apakah release_inventory berhasil, pengiriman logical compensation yang sama tidak boleh melepas inventory dua kali. Idempotent handler atau deduplication berdasarkan stable command identifier merupakan safeguard yang umum.
Timeout menciptakan ketidakpastian, bukan bukti kegagalan
Network timeout tidak membuktikan bahwa remote operation gagal. Remote service mungkin sudah commit tetapi response hilang, atau request mungkin sama sekali belum sampai.
Ambiguitas ini memengaruhi forward step maupun kompensasi.
coordinator -> charge payment
X response timeout
kemungkinan remote state:
- charge belum dimulai
- charge gagal
- charge committedLangsung menjalankan kompensasi dengan asumsi charge gagal dapat beradu dengan successful late result. Protocol memerlukan cara untuk menyelesaikan uncertain outcome, misalnya idempotent retry, status query berdasarkan operation ID, atau service contract yang mengembalikan hasil sebelumnya untuk command yang diulang.
Timeout policy karena itu merupakan bagian dari correctness saga, bukan sekadar latency setting.
Concurrent change dapat merusak inverse sederhana
State dapat terus berubah ketika saga masih aktif. Kompensasi berupa blind update dapat menghapus legitimate work yang terjadi setelah step awal.
Misalkan saga melakukan reserve lima unit, process lain menyesuaikan inventory, lalu kompensasi menulis kembali absolute quantity lama. Replacement tersebut dapat menimpa adjustment yang lebih baru.
Operation yang menyatakan intent lebih aman:
forward: create reservation R for 5 units
compensate: cancel reservation RKompensasi merujuk pada artifact yang dibuat forward step, bukan menyusun kembali global value sebelumnya.
Conditional write, version check, dan domain invariant juga dapat mencegah kompensasi diterapkan pada state yang tidak lagi mengizinkannya.
Urutan kompensasi terbalik umum dipakai, tetapi tidak universal
Untuk saga linear, mengompensasi completed step dalam urutan terbalik merupakan default yang berguna:
A -> B -> C -> D gagal
|
kompensasi C -> B -> ADependency sering membuat reverse order terasa alami. Resource yang dibuat A mungkin masih diperlukan ketika B sedang dikompensasi.
Workflow nyata dapat berbentuk graph, bukan chain sederhana. Branch independen dapat dikompensasi secara paralel, sementara action tertentu memiliki dependency constraint yang memerlukan urutan khusus. Saga model sebaiknya menyimpan dependency tersebut alih-alih menganggap stack order selalu sesuai dengan business requirement.
Sebuah step juga dapat menjadi pivot: setelah berhasil, workflow masuk ke fase yang tidak dapat dikompensasi dalam arti semula. Kegagalan berikutnya kemudian memerlukan forward recovery, manual intervention, atau business process lain.
Orchestration dan choreography menempatkan state di lokasi berbeda
Orchestrated saga memakai coordinator yang mengirim command dan mencatat progress. Bentuk ini membuat control flow dan recovery state eksplisit di satu component.
Choreographed saga membuat service bereaksi terhadap event lalu menerbitkan event berikutnya. Pendekatan ini dapat mengurangi central coordination, tetapi keseluruhan workflow tersebar di banyak event handler. Recovery tetap memerlukan semantics eksplisit; menghilangkan central coordinator tidak menghilangkan persoalan kompensasi, deduplication, ordering, atau observability.
Pilihan tersebut mengubah lokasi control state, bukan fundamental commit boundary. Setiap participating service tetap melakukan commit secara lokal.
Untuk workflow yang panjang atau bernilai tinggi, saga identifier eksplisit yang dibawa melalui command, event, log, dan trace membantu operator menyusun progress lintas service.
Side effect perlu diklasifikasikan sebelum implementasi
Sebagian effect dapat dibalik, sebagian hanya dapat dikompensasi melalui action berbeda, dan sebagian tidak dapat ditarik kembali.
Internal reservation biasanya dapat dibatalkan. Email yang sudah terkirim tidak dapat ditarik dari mailbox penerima. Physical package yang sudah diserahkan kepada carrier tidak dapat dibuat menjadi “belum dikirim.” Third-party API mungkin menyediakan cancellation hanya dalam window terbatas.
Design review yang berguna mengklasifikasikan setiap step sebelum workflow diimplementasikan:
- local atomic change;
- retryable remote action;
- compensatable action;
- irreversible atau externally visible action;
- action yang memerlukan human resolution setelah boundary tertentu.
Irreversible action sering ditempatkan menjelang akhir saga, setelah reversible work yang rawan gagal sudah selesai. Pendekatan ini mengurangi jumlah state yang memerlukan exceptional recovery, walau tidak dapat menghapus semua failure mode.
Observability perlu menunjukkan progress forward dan recovery
Saga yang hanya diberi status failed menyembunyikan operational state yang penting. Kegagalan sebelum durable step apa pun sangat berbeda dari saga dengan tiga committed step dan satu kompensasi yang masih terus di-retry.
Signal yang berguna mencakup current saga phase, age, last successful step, pending compensation, retry count, uncertain remote outcome, dan terminal manual-review state. Metric sebaiknya memakai bounded label; individual saga identifier lebih sesuai berada di trace atau log daripada high-cardinality metric label.
Operator juga memerlukan cara aman untuk melanjutkan atau menyelesaikan stuck saga tanpa mengulang completed effect. Administrative action perlu memakai durable state dan idempotency rule yang sama dengan automatic recovery.
Guarantee-nya adalah recovery yang terkoordinasi
Saga tidak menyediakan isolation lintas service dan tidak membuat rangkaian local commit setara dengan satu ACID transaction. Actor lain dapat melihat intermediate state, dan kompensasi sendiri dapat memerlukan waktu.
Guarantee yang berguna lebih sempit: sistem mencatat workflow progress dan menjalankan recovery action yang sudah ditentukan ketika forward path tidak dapat dilanjutkan.
Kontrak tersebut baru dapat diandalkan ketika kompensasi memiliki domain semantics nyata, ambiguous outcome dapat direkonsiliasi, command tahan terhadap retry, concurrency dijaga, dan irreversible boundary dibuat eksplisit. Menganggap kompensasi sebagai rollback biasa justru menyembunyikan seluruh kewajiban engineering tersebut.