Architecture: consistency and evolution · 3 MIN READ
Sagas: compensate business steps without pretending to roll back time
Model a distributed workflow as durable states with explicit compensation and recovery.
A reservation, payment authorization and shipment may belong to different services. They cannot usually share a database transaction. A saga records progress and issues commands whose effects can be retried or compensated. Compensation is another business operation, with its own failures, rather than a database rollback that erases everything that already happened.
system-design · A MAP OF THE IDEA
- Persist step
- Issue stable command
- Reconcile outcome
- Confirm compensation
Useful when
- Model a distributed workflow as durable states with explicit compensation and recovery.
- Makes the owner and the failure boundary explicit.
Watch out
- A timeout is not proof that charging failed.
- Run native service and failure tests in your own development environment.
Implementation & operation cost: Additional state and tests must be justified by the demonstrated failure; this lesson is not a requirement for every Go program.
The contract and the failure
A reservation, payment authorization and shipment may belong to different services. They cannot usually share a database transaction. A saga records progress and issues commands whose effects can be retried or compensated. Compensation is another business operation, with its own failures, rather than a database rollback that erases everything that already happened.
Work through the example
Define a workflow ID, durable step state, command idempotency key, timeout policy and reconciliation owner. Separate orchestration from event choreography: a central coordinator makes recovery visible, while decentralized reactions need an equally explicit ownership story. Keep irreversible steps late where practical and plan manual resolution for states no automatic compensation can safely resolve.
package main
import "fmt"
type State string
const (
Reserved State = "reserved"
Charged State = "charged"
Refunding State = "refunding"
Done State = "done"
)
func next(s State, event string) (State, error) {
switch {
case s == Reserved && event == "charged":
return Charged, nil
case s == Charged && event == "shipping-failed":
return Refunding, nil
case s == Refunding && event == "refund-confirmed":
return Done, nil
}
return s, fmt.Errorf("unexpected %s in %s", event, s)
}
func main() {
s, _ := next(Charged, "shipping-failed")
fmt.Println(s) // refunding, not done
s, err := next(s, "refund-confirmed")
fmt.Println(s, err)
}Shipping failure moves the workflow into refunding. It does not declare success because a refund command was merely sent. The second transition waits for confirmation. The pure transition function is easy to test, but a durable coordinator must persist the transition and the next command intent atomically.
Production decisions
A timeout is not proof that charging failed. Retrying with a new payment identity can double-charge. Compensation can be refused or delayed; do not mark the workflow completed before it is confirmed. Test duplicate commands, late success after timeout, coordinator restart and failure of the compensation itself. Use the smallest number of remote boundaries the business actually requires.
Explain it in an interview
A payment request timed out. Can the workflow safely create a fresh payment? The outcome is unknown. Reconcile the stable payment identity before retrying or compensating, and keep the workflow state durable. Describe the state before the operation, its owner, the failure between steps and the evidence you would test. Distinguish what this example demonstrates from the infrastructure a production service still needs.
TAKE THIS WITH YOU
Model a distributed workflow as durable states with explicit compensation and recovery. The outcome is unknown. Reconcile the stable payment identity before retrying or compensating, and keep the workflow state durable.
Check your understanding
A payment request timed out. Can the workflow safely create a fresh payment?
Go to the source
Progress stays in this browser. No account needed.