event

package
v0.38.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrBusNotStarted indicates Publish was called before the bus
	// completed its Start lifecycle hook. Publishing during fx Provide
	// is not supported; move the call to a fx.Invoke after app start.
	ErrBusNotStarted = errors.New("event: bus not started")
	// ErrBusAlreadyStarted indicates a second Start call on the bus.
	ErrBusAlreadyStarted = errors.New("event: bus already started")
	// ErrTxRequired indicates WithTx was used but the resolved route
	// has no TxTransport. Typically a routing misconfiguration.
	ErrTxRequired = errors.New("event: transactional publish requires a TxTransport on route")
	// ErrTransportNotFound indicates routing referenced a transport
	// name that was not registered or whose enabled flag is false.
	ErrTransportNotFound = errors.New("event: transport not found")
	// ErrAsyncQueueFull indicates the async fan-in queue is full and
	// the publish was dropped. Reported via ErrorSink, not returned.
	ErrAsyncQueueFull = errors.New("event: async queue full")
	// ErrQueueFull indicates a transport operating with a non-blocking
	// full policy rejected the publish.
	ErrQueueFull = errors.New("event: queue full")
	// ErrHandlerPanic wraps a recovered panic from a subscriber.
	ErrHandlerPanic = errors.New("event: handler panic")
	// ErrShutdownTimeout indicates the bus did not drain within the
	// graceful shutdown deadline.
	ErrShutdownTimeout = errors.New("event: shutdown timeout exceeded")
	// ErrNoRouteMatched indicates no routing rule (including the
	// fallback) matched the published event type.
	ErrNoRouteMatched = errors.New("event: no route matched")
	// ErrUnknownPayload indicates SubscribeTyped received an Envelope
	// whose Payload could neither be type-asserted nor JSON-decoded
	// into the declared T.
	ErrUnknownPayload = errors.New("event: unknown payload type")
	// ErrPayloadTooLarge indicates a publish was rejected because the
	// encoded payload or headers exceed the framework size limits.
	// Cross-process transports inherit the limit so a single hostile
	// publisher cannot push arbitrarily large frames through the bus.
	ErrPayloadTooLarge = errors.New("event: payload too large")
	// ErrInvalidEventType indicates an event type contains characters
	// outside the allowed alphabet (^[a-zA-Z0-9._-]+$). The framework
	// rejects such types at publish to keep transport keys safe.
	ErrInvalidEventType = errors.New("event: invalid event type")
	// ErrNilTypeParameter indicates SubscribeTyped was instantiated
	// with a type parameter that has no runtime representation (a
	// nil interface). Callers should pass a concrete *MyEvent or
	// MyEvent type, not the bare Event interface.
	ErrNilTypeParameter = errors.New("event: SubscribeTyped requires a concrete event type")
	// ErrGroupRequired indicates Subscribe was called on a route that
	// resolves to one or more at-least-once transports (outbox, Redis
	// Streams, …) without an explicit WithGroup option. A stable group
	// name is the dedupe scope for the Inbox middleware and the
	// consumer group name for Redis Streams; an auto-generated value
	// would change across restarts and either reset Redis Streams
	// position or invalidate dedupe records.
	ErrGroupRequired = errors.New("event: at-least-once subscription requires event.WithGroup(\"name\")")
	// ErrTxAsyncMutex indicates the caller combined WithTx and
	// WithAsync, which is contradictory: a transactional publish must
	// complete inside the caller's transaction window.
	ErrTxAsyncMutex = errors.New("event: WithTx and WithAsync are mutually exclusive")
)

Functions

This section is empty.

Types

type Bus

type Bus interface {
	// Publish sends a single event. The returned error reflects whether
	// the configured transport accepted the frame, not whether
	// downstream handlers succeeded.
	Publish(ctx context.Context, evt Event, opts ...PublishOption) error
	// PublishBatch submits multiple events through the same route
	// resolution pass. Atomicity is transport-specific: transactional
	// transports participate in the caller's transaction, while
	// non-transactional transports may partially accept a batch before
	// returning an error.
	PublishBatch(ctx context.Context, evts []Event, opts ...PublishOption) error
	// Subscribe registers a handler for the given event type. The
	// registration is buffered when the bus has not started and
	// flushed during Bus.Start, so framework modules can subscribe
	// during fx Provide regardless of startup order.
	Subscribe(eventType string, h Handler, opts ...SubscribeOption) (Unsubscribe, error)
}

Bus is the single entry point for publishing and subscribing. All publishing modes are expressed through PublishOption — the interface stays narrow on purpose so future modes (e.g. delayed delivery) extend the option set rather than the method set.

type Envelope added in v0.24.0

type Envelope struct {
	// ID is the unique message ID generated on publish. It is stable
	// across retries and is the dedupe key for the Inbox middleware.
	ID string
	// Type mirrors Event.EventType() and drives routing and dispatch.
	Type string
	// Source identifies the producing service; defaults to the
	// application name configured under vef.app.
	Source string
	// OccurredAt is the business time of the event; defaults to now.
	OccurredAt time.Time
	// PublishedAt is stamped by the bus at the publish call (and, on the
	// outbox path, equals the outbox row's insert time). It is not the
	// moment a transport accepted the frame — for relayed events the gap
	// to actual sink delivery can be large.
	PublishedAt time.Time
	// TraceID / SpanID propagate W3C tracing context across transports.
	TraceID string
	SpanID  string
	// CorrelationID groups related events; opaque to the framework.
	CorrelationID string
	// Headers carry arbitrary string metadata, forwarded verbatim.
	Headers map[string]string
	// Payload holds the original Event for in-process delivery, or a
	// RawPayload when the message has crossed a process boundary.
	// SubscribeTyped[T] transparently handles both shapes.
	Payload Event
}

Envelope wraps an Event with transport-level metadata. The framework populates default values (ID, Source, PublishedAt) at publish time; business code may override individual fields via PublishOption.

type ErrorSink added in v0.24.0

type ErrorSink func(err error, env Envelope)

ErrorSink receives errors from out-of-band publish paths (notably WithAsync). The default sink logs at error level; applications wanting alerting or metrics can supply their own via fx.Decorate.

type Event

type Event interface {
	EventType() string
}

Event is the minimum contract for any event flowing through the bus. EventType must be safe to call on a nil receiver — implementations should return a static string literal rather than dereference fields. This convention lets SubscribeTyped[T] derive the topic from a zero value of T without constructing an instance.

func AsEvents added in v0.24.0

func AsEvents[T Event](items []T) []Event

AsEvents converts a slice of any Event-implementing type into a []Event suitable for Bus.PublishBatch. Go's type system does not auto-convert []T to []Event even when T satisfies Event, so callers that maintain typed slices (e.g. domain-specific event lists) need this adapter at the publish boundary.

type Handler added in v0.24.0

type Handler func(ctx context.Context, env Envelope) error

Handler is the untyped consumer signature. A non-nil error signals failure; transports with retry semantics will Nack and back off.

type MetricsRecorder added in v0.24.0

type MetricsRecorder interface {
	// PublishObserved is called once per publish attempt with the
	// resolved event type and the publish outcome. err is non-nil when
	// the underlying transport rejected the frame.
	PublishObserved(eventType string, err error)
	// ConsumeObserved is called once per consume attempt with the
	// resolved event type, the handler's wall-clock elapsed time, and
	// the outcome.
	ConsumeObserved(eventType string, elapsed time.Duration, err error)
}

MetricsRecorder receives publish and consume observations from the framework's built-in Metrics middleware. The default implementation publishes lightweight expvar maps; applications that need richer instrumentation (Prometheus, OpenTelemetry, vendor SDKs) can supply their own via fx.Decorate.

Implementations must be safe for concurrent use. The framework invokes these hooks from the request-serving goroutine, so they should be O(1) and non-blocking.

type PublishConfig added in v0.24.0

type PublishConfig struct {
	// Tx, when non-nil, forces routing through a TxTransport using the
	// supplied transaction handle. Returns ErrTxRequired at publish
	// time when the resolved route contains no TxTransport.
	Tx orm.DB
	// Async, when true, enqueues the publish on the bus's fan-in queue
	// and returns immediately. Errors flow to the bus ErrorSink, not
	// the caller. Mutually exclusive with Tx (a transactional publish
	// must complete before the transaction commits).
	Async bool
	// Source overrides Envelope.Source.
	Source string
	// OccurredAt overrides Envelope.OccurredAt.
	OccurredAt time.Time
	// CorrelationID stamps the envelope with a caller-controlled key.
	CorrelationID string
	// Headers are merged into the envelope; later keys override earlier.
	Headers map[string]string
}

PublishConfig carries the resolved per-publish settings. It is exported for transport implementations and middleware; business code should compose options instead of building it directly.

func ApplyPublishOptions added in v0.24.0

func ApplyPublishOptions(opts []PublishOption) PublishConfig

ApplyPublishOptions returns a fully resolved PublishConfig from the supplied options. Useful for middleware that needs to inspect the effective configuration without re-implementing the fold.

type PublishOption added in v0.24.0

type PublishOption func(*PublishConfig)

PublishOption mutates publish-time configuration. Options compose left-to-right; later options override earlier ones for the same field.

func WithAsync added in v0.24.0

func WithAsync() PublishOption

WithAsync enqueues the publish on the bus's fan-in queue and returns immediately. Designed for hot-path events (audit, login) where back-pressure must not affect request latency.

func WithCorrelationID added in v0.24.0

func WithCorrelationID(id string) PublishOption

WithCorrelationID stamps the envelope with a caller-controlled correlation key, opaque to the framework.

func WithHeaders added in v0.24.0

func WithHeaders(h map[string]string) PublishOption

WithHeaders merges arbitrary headers into the envelope; later keys override earlier ones.

func WithOccurredAt added in v0.24.0

func WithOccurredAt(t time.Time) PublishOption

WithOccurredAt overrides Envelope.OccurredAt. Defaults to time.Now.

func WithSource

func WithSource(src string) PublishOption

WithSource overrides Envelope.Source. Defaults to the application name configured under vef.app.

func WithTx added in v0.24.0

func WithTx(tx orm.DB) PublishOption

WithTx routes the publish through a TxTransport using the supplied transaction handle. Events become visible iff the caller commits.

type RawPayload added in v0.24.0

type RawPayload struct {
	// Type mirrors Envelope.Type and is what EventType() returns.
	Type string
	// Body is the canonical JSON encoding of the original Event.
	Body []byte
}

RawPayload carries a JSON-encoded event body. Cross-process transports deliver Envelope.Payload as a RawPayload; SubscribeTyped[T] decodes it into the concrete T transparently.

func (RawPayload) EventType added in v0.24.0

func (r RawPayload) EventType() string

EventType implements Event so RawPayload can sit inside Envelope.Payload.

type RouteInspector added in v0.24.0

type RouteInspector interface {
	// HasTransactionalRoute reports whether the resolved route for
	// eventType contains at least one transport whose Capabilities
	// declare Transactional=true. Returns false when no transports
	// route to the type, or when the bus has not yet built its router
	// (i.e. before Start).
	//
	// Modules that publish with WithTx (transactional outbox pattern)
	// should call this once during fx lifecycle OnStart to assert that
	// their event types route to a Transactional transport — without
	// it, the first Publish under WithTx would fail with ErrTxRequired
	// at runtime.
	HasTransactionalRoute(eventType string) bool

	// HasSubscribableTransport reports whether the resolved route for
	// eventType contains at least one transport whose Capabilities do
	// not declare PublishOnly. Returns false when no transports route
	// to the type, or when the bus has not yet built its router.
	//
	// Modules whose framework-side code subscribes to an event
	// (binding listeners, projections, integration handlers) should
	// call this during fx lifecycle OnStart to assert that subscribers
	// can actually receive deliveries. Without it, a route that
	// resolves only to publish-only transports (e.g. the transactional
	// outbox alone) lets the application start, but every Subscribe
	// call against the route fails at runtime with ErrNoRouteMatched
	// — typically after the bus has already accepted publishes that
	// no consumer will ever see.
	HasSubscribableTransport(eventType string) bool
}

RouteInspector exposes read-only queries over the bus's configured routing rules so modules that depend on specific delivery guarantees can fail fast at start-up instead of at the first failing Publish or Subscribe.

The framework's bus implementation satisfies this interface; tests can stub it with a simple in-memory map.

type StreamGroupInfo added in v0.37.0

type StreamGroupInfo struct {
	// Name is the consumer group name (the subscription's WithGroup value
	// or its derived default).
	Name string `json:"name"`
	// Consumers is the number of consumer records registered in the group,
	// including historical consumers of restarted processes.
	Consumers int64 `json:"consumers"`
	// Pending is the number of delivered-but-unacknowledged entries.
	Pending int64 `json:"pending"`
	// Lag is the number of stream entries not yet delivered to this group,
	// as reported by Redis (approximate after trimming; zero on server
	// versions that do not report lag).
	Lag int64 `json:"lag"`
	// LastDeliveredID is the stream ID of the last entry delivered to the
	// group. Its timestamp part shows how long the group has been stalled.
	LastDeliveredID string `json:"lastDeliveredId"`
}

StreamGroupInfo describes one consumer group on a stream. A group whose Lag keeps growing while Consumers stay idle is an orphan candidate — a subscriber that was removed or renamed without decommissioning its group.

type StreamInfo added in v0.37.0

type StreamInfo struct {
	// Stream is the full transport-level stream key (prefix + event type).
	Stream string `json:"stream"`
	// Length is the current number of entries in the stream (post-trim).
	Length int64 `json:"length"`
	// Groups lists the consumer groups attached to the stream.
	Groups []StreamGroupInfo `json:"groups"`
}

StreamInfo describes one transport stream and its consumer groups.

type StreamInspector added in v0.37.0

type StreamInspector interface {
	// Streams lists every stream under the transport's prefix together
	// with its consumer groups.
	Streams(ctx context.Context) ([]StreamInfo, error)
}

StreamInspector reports consumer-group state for every stream a cross-process transport manages. Its purpose is operational visibility: a decommissioned subscriber leaves its consumer group behind (growing lag, stalled last-delivered position), and this is the surface that makes such orphans observable — the monitor module exposes it as an API endpoint.

The redis_stream transport provides an implementation when enabled; the dependency is optional, so consumers must tolerate a nil inspector.

type SubscribeConfig added in v0.24.0

type SubscribeConfig struct {
	// Group is the consumer group name. Two subscriptions sharing a
	// group on a SupportsGroups transport receive at most one delivery
	// per message between them (load balancing).
	Group string
	// Concurrency is the desired worker count for parallel handler
	// dispatch within this subscription. Defaults to 1.
	Concurrency int
}

SubscribeConfig carries the resolved per-subscription settings.

func ApplySubscribeOptions added in v0.24.0

func ApplySubscribeOptions(opts []SubscribeOption) SubscribeConfig

ApplySubscribeOptions returns a fully resolved SubscribeConfig.

type SubscribeOption added in v0.24.0

type SubscribeOption func(*SubscribeConfig)

SubscribeOption mutates subscription-time configuration. Options compose left-to-right.

func WithConcurrency added in v0.24.0

func WithConcurrency(n int) SubscribeOption

WithConcurrency sets the per-subscription worker count. Values above 1 trade ordering for throughput: the workers race on the subscription's single feed, so handler executions interleave even on transports that advertise Ordered delivery. Keep the default of 1 for order-sensitive subscribers.

func WithGroup added in v0.24.0

func WithGroup(name string) SubscribeOption

WithGroup sets the consumer group name. Required for any subscription whose resolved route touches an at-least-once transport (outbox, Redis Streams, …) — the group is the Inbox dedupe scope and the Redis Streams XGROUP identifier, both of which must remain stable across process restarts. Bus.Subscribe returns ErrGroupRequired if the group is missing on such a route.

type TypedHandler added in v0.24.0

type TypedHandler[T Event] func(ctx context.Context, evt T, env Envelope) error

TypedHandler is the strongly-typed handler signature accepted by SubscribeTyped. The framework decodes the wire payload into T before invoking the handler.

type Unsubscribe added in v0.24.0

type Unsubscribe func()

Unsubscribe detaches a previously registered handler. Safe for concurrent use; subsequent calls are no-ops.

func SubscribeTyped added in v0.24.0

func SubscribeTyped[T Event](
	b Bus,
	h TypedHandler[T],
	opts ...SubscribeOption,
) (Unsubscribe, error)

SubscribeTyped registers a typed handler for events of type T. The framework deduces the topic from T's EventType() method and decodes cross-process payloads via reflect + json.Unmarshal. No global registry is involved — the type information is captured at the call site, which keeps tests independent of init() side effects.

T is typically a pointer type (e.g. *FooEvent). The receiver of EventType must be safe on a nil value — implementations should return a string literal rather than dereference fields. Value types (e.g. SubscribeTyped[FooEvent]) are also supported provided EventType has a value receiver.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL