Skip to content

tmac33/go-saga

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-saga

CI Go Reference License: MIT

Tiny, generic, synchronous saga library for Go — for orchestrating multi-step workflows that need deterministic rollback when one step fails.

err := saga.New[*Order]().
    NamedStep("reserve-stock", reserveStock, releaseStock).
    NamedStep("charge-card",   chargeCard,   refundCard).
    NamedStep("ship-order",    shipOrder,    nil).
    Run(ctx, order)

If ship-order fails, the library calls refundCard then releaseStock (reverse order) before returning. If the original ctx is already cancelled, rollback still runs against a WithoutCancel context capped by a configurable timeout.

Why this exists

Saga is a well-known pattern, but most existing Go libraries are heavyweight workflow engines. This is the opposite: ~150 LOC, no dependencies, no DSL, no orchestration server. You write your steps as plain Go functions and the library guarantees:

  • Deterministic rollback order. Compensations run in strict reverse order of completed steps.
  • Rollback survives client disconnect. The forward ctx may be cancelled (HTTP client gone, deadline exceeded). Rollback runs against context.WithoutCancel(ctx) with its own timeout, so resources allocated mid-saga are still released.
  • Errors aggregate. If multiple compensations fail, you get a joined error — not just the first failure.
  • Compile-time safety. Steps share a typed state value via Go generics; no interface{} plumbing.

Install

go get github.com/tmac33/go-saga

Requires Go 1.23+ (uses context.WithoutCancel and generics).

API

s := saga.New[*MyState]()                         // empty saga
s.Step(do, undo)                                  // append step (auto-named)
s.NamedStep("activate-sim", doSim, undoSim)       // append named step
s.WithRollbackTimeout(30 * time.Second)           // cap rollback wall-time
err := s.Run(ctx, state)                          // execute

Errors:

  • *saga.StepError — wraps the original error from a failed forward step.
  • *saga.RollbackError — wraps an error from a failed compensation. Multiple are joined via errors.Join.
var stepErr *saga.StepError
if errors.As(err, &stepErr) {
    log.Printf("saga failed at %s: %v", stepErr.Step, stepErr.Cause)
}

Design notes

Synchronous, not async. Sagas here are request-scoped — the caller blocks until all steps complete or rollback finishes. This is the right model for provisioning workflows where the client wants a definitive answer. For long-running, durable sagas spanning hours or days, use a workflow engine (Temporal, Cadence) — that's a different problem.

Idempotency is your job. The library makes rollback deterministic. Making releaseMSISDN safe to invoke twice is on you. In practice this means storing an outcome marker keyed by some idempotency key, but that varies per backend.

No retries. A step that needs retries should retry inside its Action. Conflating step-level retry with saga-level rollback makes both harder to reason about.

No persistence. State lives in memory for the duration of Run. If the process dies mid-saga, you have orphaned resources — same as any synchronous workflow. If you need crash recovery, use a workflow engine.

What this isn't trying to be

  • A Temporal/Cadence replacement
  • A choreography-style event-driven saga (this library is orchestration-only)
  • A persistent workflow store

It's the smallest amount of code that turns "five RPC calls with manual defer rollback" into "a typed saga with predictable failure semantics."

Provenance

This library is the abstraction behind a pattern I've shipped to production several times — most recently in NaaS provisioning at One New Zealand, where saga-style rollback across 5+ gRPC services governs the UFB broadband activation lifecycle, and earlier in Vodafone NZ's MVNE platform for mobile / FWA provisioning across MSISDN, OCS, Cellular, IPv4 and SIM resources. Those production systems hand-rolled the pattern with defer chains; this library packages the recurring shape.

License

MIT

About

Tiny generic synchronous saga library for Go — deterministic rollback, survives client disconnect, ~150 LOC, no deps

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages