Documentation
¶
Overview ¶
Package messaging is the standard tier's broker operations: publishing an event and subscribing to events as a reactor source.
The package exports:
- Publisher, which publishes an event, and Broker, a Publisher that also subscribes reactors; Encode, the check and encoding every provider's Publish makes; and DefaultDuplicates, a broker's deduplication window when it sets none
- Subscription, a durable consumer, with Subscription.Validate, Subscription.Normalize, the form a provider configures its consumer from, and Subscription.Matches, its type filter; Start, where a new consumer begins, StartAll or StartNew; and DefaultAckWait
- CheckName, the rule for a consumer's or a stream's name, and CheckType, the rule for a routable event type
- Report, which receives the errors a source survives
- WithAttempt, which a provider calls to put a delivery's attempt on its handler's context, and Attempt, which reads it
- Config, the service's messaging block, with Config.Merge, Config.Finalize, and Config.Validate; DefaultRelayPoll and DefaultRelayTimeout
- Runtime, one service's messaging, with New, which builds one; Runtime.Relay, the reactor that publishes the outbox; and Runtime.Consume, which builds a consuming reactor, handing its handler a Claim over the inbox
- Failure, a failure the runtime survives, and FailureKind, what failed: FailureRelay, FailureQuarantine, FailureSource, or FailureExhausted
- Hook, which receives each Failure; Option, which configures a Runtime; OnFailure, the Option that sets the hook; and Gated, the Option that sets the Gate the runtime's reactors hold shared
Brokers and subscriptions ¶
A Broker is what a composition root builds reactors from. A Subscription names a durable consumer: every source subscribed under one Name shares that consumer's position and splits its work, so replicas of a service form a delivery group, and each distinct Name receives every event. The handler's return is the delivery's outcome, so a reactor acknowledges, redelivers, or terminates without a broker type in sight, and Attempt reads which delivery of the event the handler is on. A source passes the errors it survives to the Report given to Subscribe.
The runtime ¶
A Runtime is one service's messaging: the broker, the outbox and inbox over its database engine's statements, and the recorder its domains emit through. Its Runtime.Relay publishes the outbox, and its Runtime.Consume builds a consuming reactor that decodes each event into the consumer's own type and binds its inbox claim. The failures both survive reach the hook OnFailure sets, each as a Failure, and are logged at warn without one. Under Gated, the relay holds a quiesce gate for each outbox row and a consumer for each delivery, so an operation that holds the gate exclusively runs with both paused. Config is the service's messaging configuration block, beside its provider's. The runtime registers nothing: the composition root makes the broker and each reactor a node of its graph, and the coordinator infers from their methods how to start, drain, and watch them.
Providers and engines ¶
The package imports no broker and no engine: the composition root hands them to New. A provider, such as the memory package, implements Broker, and the messagingtest package holds the conformance suite every provider passes. The outbox and inbox packages run their tables on any engine that supplies the statements; the postgres sub-module is that engine for PostgreSQL, and the nats sub-module is the NATS JetStream provider.
The base module depends on the standard library, go-core, and sqlate and its query package, the adjacent library the outbox's and inbox's engine contract builds on.
Index ¶
- Constants
- func Attempt(ctx context.Context) int
- func CheckName(s string) error
- func CheckType(t string) error
- func Encode(e event.Event) (event.Header, []byte, error)
- func WithAttempt(ctx context.Context, attempt int) context.Context
- type Broker
- type Claim
- type Config
- type Failure
- type FailureKind
- type Gate
- type Hook
- type Option
- type Publisher
- type Report
- type Runtime
- type Start
- type Subscription
Constants ¶
const ( // DefaultAckWait is the AckWait of a subscription that sets none. DefaultAckWait = 30 * time.Second // DefaultDuplicates is a broker's deduplication window when it sets // none: JetStream's own default. DefaultDuplicates = 2 * time.Minute )
const ( // DefaultRelayPoll is how long the relay waits between passes once the // outbox is empty, when a configuration sets none. DefaultRelayPoll = 250 * time.Millisecond // DefaultRelayTimeout bounds the relay's publish of one event, when a // configuration sets none. DefaultRelayTimeout = 10 * time.Second )
Variables ¶
This section is empty.
Functions ¶
func Attempt ¶
Attempt returns the attempt of the delivery whose handler context is ctx: 1 on the first delivery of an event, and one more on each redelivery, so a handler on attempt MaxDeliver is the event's last. A context no provider gave has no attempt, and Attempt returns 0.
func CheckName ¶
CheckName reports whether s can name a durable consumer or a stream: it is non-empty, with no whitespace, control character, '.', '*', '>', '/', or '\', the characters a JetStream name may not hold.
func CheckType ¶
CheckType reports whether t is an event type a broker can route: one or more tokens separated by '.', none of them empty, with no whitespace, control character, '*', or '>'. It is event.CheckType, the rule a domain's event kinds are declared under, so a type a domain can define is a type a broker routes.
func Encode ¶
Encode is the check and encoding every provider's Publish makes: e must pass event.Event.Validate and its type CheckType, and it is returned in binary content mode, as event.Encode gives it.
Types ¶
type Broker ¶
type Broker interface {
Publisher
// Subscribe validates sub and returns a source of its events: a member
// of the durable consumer sub.Name names.
//
// The first subscription under a Name creates its consumer, which starts
// where sub.Start says: under [StartAll], at the beginning of the stream,
// so it receives every event, including those published before it
// subscribed; under [StartNew], at its own creation. A later subscription
// under the same Name binds that consumer and must match its
// configuration; a mismatch fails Subscribe, or the source's Receive when
// the provider binds there.
//
// A member claims one delivery at a time, and the next only once the
// handler's outcome is settled, so members of one Name share the work
// rather than one member taking a batch. The outcome is the handler's
// return: nil acknowledges the delivery; an error redelivers it after
// sub.RetryDelay, until sub.MaxDeliver deliveries have been made; an
// error marked by [event.Permanent] terminates it, so it is never
// delivered again. A handler error never ends the source.
//
// Each delivery's handler context carries its attempt, which [Attempt]
// reads: 1 on the first delivery of an event, and one more on each
// redelivery, whether the previous delivery's handler returned an error
// or overran AckWait.
//
// The source passes report each error it survives, one that does not
// end its Receive, such as a message it cannot decode, with the context
// it was receiving under. A nil report discards them.
Subscribe(sub Subscription, report Report) (reactor.Source[event.Event], error)
}
Broker publishes events and subscribes reactors to them.
type Claim ¶
Claim records, in a command's transaction, that the consumer is handling the event it was bound to, and reports whether this is the first time. A command runs it first and, on false, commits nothing more. It is an alias, so a domain's own claim type of the same shape accepts it.
type Config ¶
type Config struct {
Source string `json:"source"`
RelayPoll libconfig.Duration `json:"relay_poll"`
RelayTimeout libconfig.Duration `json:"relay_timeout"`
}
Config is a service's messaging block, the part of its messaging that names no broker: the provider's own block sits beside it. Source is the service's CloudEvents source, one value for every replica. RelayPoll is how long the relay waits between passes once the outbox is empty. RelayTimeout bounds the relay's publish of one event, and so how long a hung publish holds the event's outbox row; it is the relay's outbox.Timeout.
func (*Config) Finalize ¶
Finalize defaults RelayPoll to DefaultRelayPoll and RelayTimeout to DefaultRelayTimeout, reads the block's environment overrides when a prefix is given (<PREFIX>_MESSAGING_SOURCE, …_RELAY_POLL, and …_RELAY_TIMEOUT), and validates the result.
type Failure ¶
type Failure struct {
// Kind is what failed.
Kind FailureKind
// Consumer is the subscription's Name, for a source or exhausted
// failure; it is empty for the relay's.
Consumer string
// EventSource, EventID, and EventType identify the event, when it is
// known: always for an exhausted failure, and for a relay or
// quarantine failure whose row decoded. They are empty otherwise.
EventSource string
EventID string
EventType string
// Attempt is the delivery the handler failed on, for an exhausted
// failure; it is 0 for the others, which no delivery attempt names.
Attempt int
// Err is the failure itself: the handler's error, the error the source
// reported, or the relay's report, an [*outbox.QuarantineError], an
// [*outbox.PublishError], a database error, or a gate's error.
Err error
}
Failure is a messaging failure the runtime reports to its Hook: one that no caller sees as an error, because the relay or the consumer survives it.
type FailureKind ¶
type FailureKind string
FailureKind names what failed in a Failure.
const ( // FailureRelay is a relay pass that ended on an error: a transient // publish failure, after which the next pass retries the event, a // database error, or, under [Gated], the gate's failure other than its // wait ending. FailureRelay FailureKind = "relay" // FailureQuarantine is an outbox row the relay quarantined, committed // by the time it is reported: the row does not decode, or its publish // failed with an error marked [event.Permanent]. Its event is never // published. FailureQuarantine FailureKind = "quarantine" // FailureSource is an error a consumer's source survived, such as a // message it could not decode: the [Report] it was subscribed with. FailureSource FailureKind = "source" // FailureExhausted is an event whose handler failed, with an error not // marked [event.Permanent], on the subscription's last delivery, its // MaxDeliver: the broker gives the event up, and nothing redelivers it. FailureExhausted FailureKind = "exhausted" )
type Gate ¶ added in v0.2.0
type Gate interface {
}
Gate is a quiesce gate the runtime's reactors hold shared while they work, so an operation that holds it exclusively runs with them paused: go-core's reactor.Gate is one. Shared waits until the gate admits a shared holder and returns its release, or fails, holding nothing; a wait ctx ends returns ctx's error.
type Hook ¶
Hook receives each Failure the runtime reports, with the context it was reported under. It must not block for long: the relay and the consumer wait on it before they go on.
type Option ¶
type Option func(*Runtime)
Option configures a Runtime.
func Gated ¶ added in v0.2.0
Gated sets the gate the runtime's reactors hold shared. Without it, or with a nil gate, they hold none.
The relay holds g for each outbox row, from before the row's transaction begins until the transaction commits or rolls back, so a relay waiting on g opens no transaction and claims no row. Its wait ends with the relay's stop, and its last pass's with that pass's bound, either of which claims nothing and reports nothing. Any other failure of g ends the pass and is reported as a FailureRelay, leaving the relay ready, since it is not the database's.
Each delivery a Runtime.Consume reactor handles holds g for the handler's whole call, and the handler is not called until it does. A wait that the drain ends hands the handler nothing and leaves the event unacknowledged, for the broker to redeliver. The wait is bounded by the delivery's AckWait like the handler is: a delivery that waits on g past AckWait is redelivered, and counts against MaxDeliver, so an event whose last delivery waits that long is given up unreported. Any other failure of g is the delivery's outcome, as a transient error of the handler's would be: the event is redelivered, and on its last delivery reported as a FailureExhausted.
func OnFailure ¶
OnFailure sets the hook the runtime reports each Failure to. Without it, or with a nil hook, the runtime logs each failure at warn on its logger. The default logs every failure it is given, so during an outage it logs each failed relay pass, about four a second at the default poll, and each failed pull of the nats provider's sources, one per consumer member about every 1.1 seconds, a pull's one-second bound plus its 100ms wait before the next; a service that wants failures throttled, counted, or alerted on sets a hook that does so.
type Publisher ¶
Publisher publishes an event. Publish returns once the broker holds the event, so a delivery can outlive the publisher.
Publish rejects an event that Encode rejects. The broker deduplicates on the event's source and id, which together identify a CloudEvents event: an event whose source and id the broker has seen within its deduplication window is accepted and dropped, so a publisher that retries, such as the outbox's relay, delivers the event once, while another source's event that reuses the id still delivers.
type Report ¶
Report receives an error a source survives: one it handled itself and kept receiving after, which would otherwise go unseen. It must not block for long, since the source waits on it before its next delivery.
type Runtime ¶
type Runtime struct {
Recorder *event.Recorder[*sqlate.Tx]
// contains filtered or unexported fields
}
Runtime is one service's messaging, built from its Config, the broker its provider supplies, and its database engine's outbox and inbox statements. The broker carries events to and from the service. The outbox holds the events a command emits until the relay publishes them, and the inbox records the events a consumer has handled. A domain takes Recorder, its one value for emitting events, and records each event in its command's transaction.
The failures the relay and the consumers survive, which no caller sees as an error, go to the runtime's Hook, set with OnFailure: each as a Failure naming what failed.
A Runtime registers nothing and knows nothing of the coordinator: the composition root makes the broker and each reactor the runtime builds a node of its graph, which the coordinator starts, drains, and watches.
func New ¶
func New(cfg Config, broker Broker, outboxEngine outbox.Engine, inboxEngine inbox.Engine, shutdown time.Duration, logger *slog.Logger, opts ...Option) (*Runtime, error)
New builds a service's messaging from cfg, over broker and the engine's statements, logging to logger. shutdown is the process's drain timeout, which every reactor the runtime builds drains within. New checks cfg, which is finalized, and shutdown, which must be positive. It does no I/O.
func (*Runtime) Consume ¶
func (r *Runtime) Consume[T any](sub Subscription, fn func(ctx context.Context, data T, claim Claim) error) (*reactor.Reactor[event.Event], error)
Consume returns the reactor that consumes sub's events, with the bound reactor.CancelWithin gives the drain timeout. The reactor decodes each event's data into T, the consumer's own type for the payload, and hands it to fn with a Claim over the inbox under sub.Name, bound to the event, so fn's domain never sees the event and a redelivery changes nothing. Data that does not decode is refused with event.Permanent, because no redelivery can fix it.
The reactor logs each delivery with the consumer, the event's type, id, and subject, and its outcome, so the service's log shows the traffic it receives. The outcome is "event consumed" at info when handled; repeat, when the claim found the event handled already; or retried, with the error, when the broker will redeliver it. A permanent refusal, from the decode or from fn, is "event refused" at warn with the error, because the broker terminates the delivery and nothing else reports it.
The reactor reports to the hook each error the source survives, as a FailureSource, and each event fn fails, with an error not marked permanent, on the delivery whose Attempt is sub.MaxDeliver, as a FailureExhausted: the broker gives that event up. An unlimited subscription, a MaxDeliver of 0, exhausts nothing. Exhaustion is seen only from fn's error, so an event whose last delivery overruns AckWait, with fn returning nil or late, is given up unreported.
Under Gated, each delivery holds the gate shared for fn's whole call, and fn is not called until it does. A wait that the drain or the delivery's deadline ends hands fn nothing, logs and reports nothing, and leaves the event to be redelivered.
func (*Runtime) Relay ¶
Relay returns the reactor that publishes the outbox's committed events on db to the broker, with the bound reactor.CancelWithin gives the drain timeout. Each publish is bounded by the configuration's RelayTimeout. The relay makes a last pass, bounded at a quarter of the drain timeout and so below the reactor's bound, that publishes what the producers committed while they drained. The root therefore makes every node that commits events use the relay's node, so they drain before it. The relay logs each event it publishes at info with its type, id, and subject, so the service's log shows the traffic it sends.
The relay reports each row it quarantines to the hook as a FailureQuarantine, and each pass a publish failure or a database error ends as a FailureRelay, with the event's identity when the relay knew it.
Under Gated, the relay holds the gate shared for each row, from before the row's transaction begins until it settles.
type Start ¶
type Start int
Start is where a new durable consumer begins in the stream.
const ( // StartAll begins at the stream's beginning, so the consumer receives // every event the stream retains, including those published before it // was created. StartAll Start = iota // StartNew begins at the consumer's creation, so the consumer receives // only the events published after it. A provider may create the consumer // as late as the source's first Receive, so an event published between // Subscribe and that Receive may be skipped; a caller that must not miss // one uses StartAll. StartNew )
type Subscription ¶
type Subscription struct {
// Name is the durable consumer, and so its delivery group: sources
// subscribed under the same Name share one position and split the work.
// It is a token: no whitespace, '.', '*', '>', '/', or '\'.
Name string
// Types filters on the event's type; empty matches every type. Each
// entry must pass [CheckType].
Types []string
// MaxDeliver bounds the deliveries of one event; 0 is unlimited.
MaxDeliver int
// AckWait is the handler's deadline for each delivery, and so the
// deadline on its context. When it passes, the event is redelivered and
// the late outcome is ignored. 0 is [DefaultAckWait].
AckWait time.Duration
// RetryDelay is how long an event whose handler returned an error waits
// before it is redelivered; 0 redelivers it at once.
RetryDelay time.Duration
// Start is where the Name's consumer begins when this subscription
// creates it; the zero value is [StartAll]. It is part of the consumer's
// configuration, so a later subscription under the Name must match it,
// even though it moves nothing once the consumer exists.
Start Start
}
Subscription describes a durable consumer of events.
func (Subscription) Matches ¶
func (sub Subscription) Matches(t string) bool
Matches reports whether sub's type filter admits an event of type t.
func (Subscription) Normalize ¶
func (sub Subscription) Normalize() Subscription
Normalize returns sub in the form a provider configures its consumer from: AckWait defaulted to DefaultAckWait, and Types copied, sorted, and without repeats, nil when empty. Subscriptions that mean the same thing normalize equal, so they bind the same consumer, and the caller cannot change the filter afterward.
func (Subscription) Validate ¶
func (sub Subscription) Validate() error
Validate reports every way sub is unusable.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package inbox is a consumer's guard against handling a redelivered event twice, on any SQL engine that supplies its statement.
|
Package inbox is a consumer's guard against handling a redelivered event twice, on any SQL engine that supplies its statement. |
|
internal
|
|
|
engine
Package engine checks the statements an engine's module supplies to the outbox and the inbox, which hold no SQL of their own.
|
Package engine checks the statements an engine's module supplies to the outbox and the inbox, which hold no SQL of their own. |
|
Package memory is the in-memory messaging provider: the conformance double for the standard tier and a test double for a service.
|
Package memory is the in-memory messaging provider: the conformance double for the standard tier and a test double for a service. |
|
Package messagingtest is the conformance suite every messaging provider passes.
|
Package messagingtest is the conformance suite every messaging provider passes. |
|
nats
module
|
|
|
Package outbox writes events through a transactional outbox and publishes them with a relay, on any SQL engine that supplies the statements.
|
Package outbox writes events through a transactional outbox and publishes them with a relay, on any SQL engine that supplies the statements. |
|
postgres
module
|