Documentation
¶
Overview ¶
Package kata provides an embedded saga orchestrator for Go.
kata executes a sequence of named steps against shared state, and automatically compensates (rolls back) completed steps in reverse order when any step fails. No external services or databases required.
Basic usage ¶
type OrderState struct {
CardToken string
ChargeID string
}
runner := kata.New(
kata.Step("charge", chargeCard).
Compensate(refundCard).
Retry(3, kata.Exponential(100*time.Millisecond)),
kata.Step("reserve", reserveStock).
Compensate(releaseStock),
kata.Step("ship", createShipment),
)
if err := runner.Run(ctx, &OrderState{CardToken: "tok_123"}); err != nil {
var stepErr *kata.StepError
var compErr *kata.CompensationError
switch {
case errors.As(err, &compErr):
// step failed AND some compensations failed - needs manual fix
case errors.As(err, &stepErr):
// step failed, all compensations ran cleanly
}
}
Parallel steps ¶
Use Parallel to run independent steps concurrently:
kata.Parallel("notify",
kata.Step("email", sendEmail),
kata.Step("sms", sendSMS).Compensate(cancelSMS),
)
Observability ¶
Attach hooks for logging and metrics without changing step code:
runner.WithOptions(kata.WithHooks(kata.Hooks{
OnStepStart: func(ctx context.Context, name string) { ... },
OnStepFailed: func(ctx context.Context, name string, err error) { ... },
}))
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CompensationError ¶
type CompensationError struct {
// StepName is the step that originally failed and triggered compensation.
StepName string
// StepCause is the original error that triggered compensation.
StepCause error
// Failed contains the names and errors of compensations that failed.
Failed []CompensationFailure
}
CompensationError is returned when a step fails AND one or more compensations also fail. The saga is in a partially inconsistent state - requires manual intervention.
func (*CompensationError) Error ¶
func (e *CompensationError) Error() string
func (*CompensationError) Unwrap ¶
func (e *CompensationError) Unwrap() error
type CompensationFailure ¶
CompensationFailure holds the name and error for a single failed compensation.
type Hooks ¶
type Hooks struct {
// OnStepStart is called before each step begins executing.
OnStepStart func(ctx context.Context, name string)
// OnStepDone is called after a step completes successfully.
OnStepDone func(ctx context.Context, name string, duration time.Duration)
// OnStepFailed is called when a step fails (after all retries are exhausted).
OnStepFailed func(ctx context.Context, name string, err error)
// OnCompensationStart is called before a compensation function begins.
OnCompensationStart func(ctx context.Context, name string)
// OnCompensationDone is called after a compensation completes successfully.
OnCompensationDone func(ctx context.Context, name string)
// OnCompensationFailed is called when a compensation function fails.
OnCompensationFailed func(ctx context.Context, name string, err error)
}
Hooks provides lifecycle callbacks for observability (logging, metrics, tracing).
type ParallelDef ¶
type ParallelDef[T any] struct { // contains filtered or unexported fields }
ParallelDef runs a group of steps concurrently.
Behaviour:
- All steps start at the same time and share the same state.
- If any step fails, the group cancels all remaining steps and compensates the ones that already succeeded (in reverse order).
- If the whole group succeeds and a later sequential step fails, all steps in the group are compensated in reverse order.
Use flow.Parallel() to create one.
type RetryPolicy ¶
RetryPolicy determines the wait duration between retry attempts.
var NoDelay RetryPolicy = func(_ int) time.Duration {
return 0
}
NoDelay retries immediately with no wait between attempts.
func Exponential ¶
func Exponential(base time.Duration) RetryPolicy
Exponential doubles the wait time on each attempt, starting from base. e.g. base=100ms -> 100ms, 200ms, 400ms, 800ms...
func Fixed ¶
func Fixed(d time.Duration) RetryPolicy
Fixed waits the same duration between every attempt.
type Runner ¶
type Runner[T any] struct { // contains filtered or unexported fields }
Runner orchestrates a sequence of steps with automatic compensation on failure. It is safe to reuse a Runner across multiple Run calls (e.g. per-request).
func New ¶
New creates a reusable Runner from a sequence of steps and parallel groups.
Steps execute in order. On failure, completed steps are compensated in reverse.
runner := flow.New(
flow.Step("charge", chargeCard).Compensate(refundCard).Retry(3, flow.Exponential(100*time.Millisecond)),
flow.Step("reserve", reserveStock).Compensate(releaseStock),
flow.Parallel("notify",
flow.Step("email", sendEmail),
flow.Step("sms", sendSMS),
),
)
func (*Runner[T]) Run ¶
Run executes all steps in order against the given state.
Returns:
- nil on success
- *StepError if a step failed and all compensations ran successfully
- *CompensationError if a step failed AND some compensations also failed
func (*Runner[T]) WithOptions ¶
func (r *Runner[T]) WithOptions(opts ...RunnerOption) *Runner[T]
WithOptions returns a new Runner with the given options applied. Useful when you want to add hooks without changing the step definitions.
runner := flow.New(step1, step2).WithOptions(flow.WithHooks(myHooks))
type RunnerOption ¶
type RunnerOption func(*runnerConfig)
RunnerOption is a functional option for configuring a Runner.
func WithHooks ¶
func WithHooks(h Hooks) RunnerOption
WithHooks is an option to set observability hooks on a Runner.
type StepDef ¶
type StepDef[T any] struct { // contains filtered or unexported fields }
StepDef defines a single sequential step with its configuration. Create one with flow.Step(), then chain builder methods to configure it.
func Step ¶
Step creates a new step definition with the given name and function.
flow.Step("charge-card", chargeCard).
Compensate(refundCard).
Retry(3, flow.Exponential(100*time.Millisecond)).
Timeout(10*time.Second)
func (*StepDef[T]) Compensate ¶
Compensate sets the rollback function for this step. It is called automatically (in reverse order) if a later step fails.