Documentation
¶
Overview ¶
Package events is an event system over asynq: application code dispatches events, listeners subscribe by event type, and each listener runs as an independent asynq task with its own retry budget. One event fans out to N listener tasks, so a failing listener retries without re-running the others.
Delivery is at-least-once end to end (outbox → relay → asynq → listener), so LISTENERS MUST BE IDEMPOTENT. Use Meta(ctx).ID as an idempotency key when a side effect must happen at most once.
Dispatch paths:
- NewDispatcher enqueues directly to Valkey. Simple, but an event dispatched after a DB commit is lost if the process dies before the enqueue.
- events/outbox.NewDispatcher writes to a Postgres outbox table in the caller's transaction; a relay forwards committed rows to Valkey. Use this whenever events accompany database writes.
- NewSyncDispatcher runs listeners inline — for tests and local tooling.
Index ¶
- func ContextWithMeta(ctx context.Context, m Meta) context.Context
- func Listen[T Event](r *Registry, name string, fn func(ctx context.Context, ev T) error, ...)
- func NewEnvelope(ctx context.Context, ev Event) (queue.Envelope, error)
- type DirectDispatcher
- type Dispatcher
- type Event
- type FanoutTask
- type Meta
- type Registry
- type SyncDispatcher
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ContextWithMeta ¶
ContextWithMeta returns a context carrying m. Used by the worker and the sync dispatcher; listeners read it with MetaFromContext.
func Listen ¶
func Listen[T Event](r *Registry, name string, fn func(ctx context.Context, ev T) error, opts ...queue.Option)
Listen registers a typed listener for an event under a name unique per event type (it forms the task type "listener:<event>:<name>" seen in Asynqmon). Options set per-listener enqueue defaults (queue, retry, timeout).
The event name is derived from the zero value of T, so T must be a value type whose EventName() works on the zero value. Registering the same (event, name) twice, or a listener whose EventName panics, fails fast at startup.
func NewEnvelope ¶
NewEnvelope builds an envelope for ev: a fresh id, the JSON-encoded payload, the trace context carried by ctx, and the current time. Both the direct and the outbox dispatcher use it, so a dispatched event looks identical however it is delivered.
Types ¶
type DirectDispatcher ¶
type DirectDispatcher struct {
// contains filtered or unexported fields
}
DirectDispatcher is a Dispatcher that enqueues events directly to Valkey. See NewDispatcher.
func NewDispatcher ¶
func NewDispatcher(client *queue.Client, opts ...queue.Option) *DirectDispatcher
NewDispatcher returns a DirectDispatcher that enqueues events directly to Valkey. There is no delivery guarantee if the process dies between a database commit and the enqueue — use events/outbox for state-changing flows. opts apply to every dispatched event's fan-out task.
type Dispatcher ¶
Dispatcher publishes events. See the package doc for the implementations.
type Event ¶
type Event interface {
EventName() string
}
Event is implemented by application event structs. EventName must be stable across deploys (it is persisted in the outbox and in Valkey) and must work on the zero value of the type — register a value type, not a pointer. Use a versioned, dotted name, e.g. "news.article_published".
type FanoutTask ¶
FanoutTask is one listener's task, produced when an event fans out.
type Meta ¶
type Meta struct {
// ID is the envelope id — a stable idempotency key across retries and
// across all listeners of the same event.
ID string
// Name is the event name.
Name string
// OccurredAt is when the event was dispatched.
OccurredAt time.Time
// Attempt is the current retry count: 0 on first delivery.
Attempt int
}
Meta carries per-delivery metadata into a listener. The worker injects it before invoking the listener; the sync dispatcher injects it too.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps event names to their listeners. Build it in the worker binary and pass it to worker.Run; NewSyncDispatcher also takes one for tests.
func (*Registry) EventNames ¶
EventNames returns the registered event names, sorted.
func (*Registry) FanoutTasks ¶
func (r *Registry) FanoutTasks(env queue.Envelope) ([]FanoutTask, error)
FanoutTasks expands an event envelope into one task per registered listener. Each task carries a deterministic TaskID (envelope id + listener name) so a re-run of the fan-out after a partial failure is idempotent.
type SyncDispatcher ¶
type SyncDispatcher struct {
// contains filtered or unexported fields
}
SyncDispatcher is a Dispatcher that runs every registered listener inline. See NewSyncDispatcher.
func NewSyncDispatcher ¶
func NewSyncDispatcher(r *Registry) *SyncDispatcher
NewSyncDispatcher returns a SyncDispatcher that runs every registered listener inline, in the caller's goroutine and transaction — Laravel's "sync" driver. Listener errors are joined and returned. Intended for unit tests and local tooling, not production (a slow or failing listener blocks the caller).
Directories
¶
| Path | Synopsis |
|---|---|
|
Package outbox implements the transactional-outbox delivery guarantee for events.
|
Package outbox implements the transactional-outbox delivery guarantee for events. |
|
bunx
Package bunx is the bun adapter for the event outbox store, mirroring the dbx/bunx split: projects that wire the bun transactor use it so outbox inserts join the bun transaction opened by WithinTransaction.
|
Package bunx is the bun adapter for the event outbox store, mirroring the dbx/bunx split: projects that wire the bun transactor use it so outbox inserts join the bun transaction opened by WithinTransaction. |
|
Package scheduler adds recurring work over asynq: cron- or interval-scheduled events (fanned out to their listeners) and scheduled jobs (a single handler).
|
Package scheduler adds recurring work over asynq: cron- or interval-scheduled events (fanned out to their listeners) and scheduled jobs (a single handler). |