Documentation ¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrActionNotFound is returned when trying to run an Action which is not // configured. ErrActionNotFound = errors.New("action not found") // ErrEmptyName is returned when an Action is configured with an empty name. ErrEmptyName = errors.New("empty action name") // ErrCompensateTimeout is returned when the compensation of a SAGA fails // due to the CompensateTimeout being exceeded. ErrCompensateTimeout = errors.New("compensation timed out") )
var ( // DefaultCompensateTimeout is the default timeout for compensating Actions // of a failed SAGA. DefaultCompensateTimeout = 10 * time.Second )
Functions ¶
func Execute ¶
func Execute(ctx context.Context, s Setup, opts ...ExecutorOption) error
Execute executes the given Setup.
Execution error ¶
Execution can fail because of multiple reasons. Execute returns an error in the following cases:
- Setup validation fails (see Validate)
- An Action fails and compensation is disabled
- An Action and any of the subsequent compensations fail
Compensation is disabled when no compensators are configured.
Skip validation ¶
Validation of the Setup can be skipped by providing the SkipValidation ExecutorOption, but should only be used when the Setup s is ensured to be valid (e.g. by calling Validate manually beforehand).
Reporting ¶
When a Reporter r is provided using the Report ExecutorOption, Execute will call r.Report with a report.Report which contains detailed information about the execution of the SAGA (a *report.Report is also a Reporter):
s := saga.New(...) var r report.Report err := saga.Execute(context.TODO(), s, saga.Report(&r)) // err == r.Error()
func Validate ¶
Validate validates that a Setup is configured safely and returns an error in the following cases:
- `ErrEmptyName` if an Action has an empty name (or just whitespace)
- `ErrActionNotFound` if the sequence contains an unconfigured Action
- `ErrActionNotFound` if an unknown Action is configured as a compensating Action
Types ¶
type CompensateErr ¶
CompensateErr is returned when the compensation of a failed SAGA fails.
func CompensateError ¶
func CompensateError(err error) (*CompensateErr, bool)
CompensateError unwraps the *CompensateErr from the given error.
func (*CompensateErr) Error ¶
func (err *CompensateErr) Error() string
func (*CompensateErr) Unwrap ¶
func (err *CompensateErr) Unwrap() error
type Executor ¶
type Executor struct { Setup // contains filtered or unexported fields }
An Executor executes SAGAs. Use NewExector to create an Executor.
func NewExecutor ¶
func NewExecutor(opts ...ExecutorOption) *Executor
NewExecutor returns a SAGA executor.
type ExecutorOption ¶
type ExecutorOption func(*Executor)
ExecutorOption is an option for the Execute function.
func CommandBus ¶
func CommandBus(bus command.Bus) ExecutorOption
CommandBus returns an ExecutorOption that provides a SAGA with an command.Bus. Actions within that SAGA that receive an action.Context may dispatch Commands through that Context over the provided Bus. Dispatches over the Command Bus are automatically made synchronous.
Example:
s := saga.New(saga.Action("foo", func(ctx action.Context) { cmd := command.New("foo", fooPayload{}) err := ctx.Dispatch(ctx, cmd) // handle err })) var bus command.Bus err := saga.Execute(context.TODO(), s, saga.CommandBus(bus)) // handle err
func CompensateTimeout ¶
func CompensateTimeout(d time.Duration) ExecutorOption
CompensateTimeout returns an ExecutorOption that sets the timeout for compensating a failed SAGA.
func EventBus ¶
func EventBus(bus event.Bus) ExecutorOption
EventBus returns an ExecutorOption that provides a SAGA with an event.Bus. Actions within that SAGA that receive an action.Context may publish events through that Context over the provided Bus.
Example:
s := saga.New(saga.Action("foo", func(ctx action.Context) { evt := event.New("foo", fooData{}) err := ctx.Publish(ctx, evt) // handle err })) var bus event.Bus err := saga.Execute(context.TODO(), s, saga.EventBus(bus)) // handle err
func Report ¶
func Report(r Reporter) ExecutorOption
Report returns an ExecutorOption that configures the Reporter r to be used by the SAGA to report the execution result of the SAGA.
func Repository ¶
func Repository(r aggregate.Repository) ExecutorOption
Repository returns an ExecutorOption that provides a SAGA with an aggregate.Repository. Action within that SAGA that receive an action.Context may fetch aggregates through that Context from the provided Repository.
Example:
s := saga.New(saga.Action("foo", func(ctx action.Context) { foo := newFooAggregate() err := ctx.Fetch(ctx, foo) // handle err })) var repo aggregate.Repository err := saga.Execute(context.TODO(), s, saga.Repository(repo)) // handle err
func SkipValidation ¶
func SkipValidation() ExecutorOption
SkipValidation returns an ExecutorOption that disables validation of a Setup before it is executed.
type Option ¶
type Option func(*setup)
Option is a Setup option.
func Action ¶
Action returns an Option that adds an Action to a SAGA. The first configured Action of a SAGA is also its starting Action, unless the StartWith Option is used.
func Compensate ¶
Compensate returns an Option that configures the compensating Action for a failed Action.
func Sequence ¶
Sequence returns an Option that defines the sequence of Actions that should be run when the SAGA is executed.
When the Sequence Option is used, the StartWith Option has no effect and the first Action of the sequence is used as the starting Action.
Example:
s := saga.New( saga.Action("foo", func(action.Context) error { return nil }), saga.Action("bar", func(action.Context) error { return nil }), saga.Action("baz", func(action.Context) error { return nil }), saga.Sequence("bar", "foo", "baz"), ) err := saga.Execute(context.TODO(), s) // would run "bar", "foo" & "baz" sequentially
type Setup ¶
type Setup interface { // Sequence returns the names of the actions that should be run sequentially. Sequence() []string // Compensator finds and returns the name of the compensating action for the // Action with the given name. If Compensator returns an empty string, there // is no compensator for the given action configured. Compensator(string) string // Action returns the action with the given name. Action returns nil if no // Action with that name was configured. Action(string) action.Action }
Setup is the setup for a SAGA.
func New ¶
New returns a reusable Setup that can be safely executed concurrently.
Define Actions ¶
The core of a SAGA are Actions. Actions are basically just named functions that can be composed to orchestrate the execution flow of a SAGA. Action can be configured with the Action Option:
s := saga.New( saga.Action("foo", func(action.Context) error { return nil })), saga.Action("bar", func(action.Context) error { return nil })), )
By default, the first configured Action is the starting point for the SAGA when it's executed. The starting Action can be overriden with the StartWith Option:
s := saga.New( saga.Action("foo", func(action.Context) error { return nil })), saga.Action("bar", func(action.Context) error { return nil })), saga.StartWith("bar"), )
Alternatively define a sequence of Actions that should be run when the SAGA is executed. The first Action of the sequence will be used as the starting Action for the SAGA:
s := saga.New( saga.Action("foo", func(action.Context) error { return nil }), saga.Action("bar", func(action.Context) error { return nil }), saga.Action("baz", func(action.Context) error { return nil }), saga.Sequence("bar", "foo", "baz"), ) // would run "bar", "foo" & "baz" sequentially
Compensate Actions ¶
Every Action a can be assigned a compensating Action c that is called when the SAGA fails. Actions are compensated in reverse order and only failed Actions will be compensated.
Example:
s := saga.New( saga.Action("foo", func(action.Context) error { return nil }), saga.Action("bar", func(action.Context) error { return nil }), saga.Action("baz", func(action.Context) error { return errors.New("whoops") }), saga.Action("compensate-foo", func(action.Context) error { return nil }), saga.Action("compensate-bar", func(action.Context) error { return nil }), saga.Action("compensate-baz", func(action.Context) error { return nil }), saga.Compensate("foo", "compensate-foo"), saga.Compensate("bar", "compensate-bar"), saga.Compensate("baz", "compensate-baz"), saga.Sequence("foo", "bar", "baz"), )
The above Setup would run the following Actions in order:
`foo`, `bar`, `baz`, `compensate-bar`, `compensate-foo`
A SAGA that successfully compensated every Action still returns the error that triggered the compensation. In order to check if a SAGA was compensated, unwrap the error into a *CompensateErr:
var s saga.Setup if err := saga.Execute(context.TODO(), s); err != nil { if compError, ok := saga.CompensateError(err); ok { log.Println(fmt.Sprintf("Compensation failed: %s", compError)) } else { log.Println(fmt.Sprintf("SAGA failed: %s", err)) } }
Action Context ¶
An Action has access to an action.Context that allows the Action to run other Actions in the same SAGA and, depending on the passed ExecutorOptions, publish events and dispatch Commands:
s := saga.New( saga.Action("foo", func(ctx action.Context) error { if err := ctx.Run(ctx, "bar"); err != nil { return fmt.Errorf("run %q: %w", "bar", err) } if err := ctx.Publish(ctx, event.New(...)); err != nil { return fmt.Errorf("publish event: %w", err) } if err := ctx.Dispatch(ctx, command.New(...)); err != nil { return fmt.Errorf("publish command: %w", err) } return nil }), )