Documentation
¶
Overview ¶
Package outbox implements the transactional outbox pattern on PostgreSQL.
Messages are enqueued in the same transaction as the business writes that produce them, and later dispatched by workers that call Dequeue.
Enqueue joins the caller's transaction when ctx carries one created by a dbtx.DB with the same ID as the repository (dbtx.ID by default). Otherwise the message is committed on its own.
Dequeue leases messages to the worker instead of holding row locks, so no transaction stays open while messages wait to be handled. A message whose lease expires, for example because the worker crashed, is delivered again. Delivery is at least once, so handlers must be idempotent.
Index ¶
- Constants
- Variables
- func DefaultBackoff(msg *Message) time.Duration
- type EnqueueParams
- type HandlerFunc
- type Message
- type Outbox
- func (o *Outbox) Count(ctx context.Context) (int64, error)
- func (o *Outbox) DeadLetters(ctx context.Context, limit int32) ([]*Message, error)
- func (o *Outbox) Dequeue(ctx context.Context, limit int32, fn HandlerFunc) (err error)
- func (o *Outbox) Enqueue(ctx context.Context, params EnqueueParams) (uuid.UUID, error)
- func (o *Outbox) Purge(ctx context.Context, status string, before time.Time) (int64, error)
- func (o *Outbox) Requeue(ctx context.Context, id uuid.UUID) error
- type Repository
Constants ¶
const ( StatusPending = "pending" StatusProcessing = "processing" StatusDone = "done" StatusDead = "dead" )
Statuses of a message.
const DefaultLease = 30 * time.Second
DefaultLease is how long Dequeue leases messages when Outbox.Lease is zero.
Variables ¶
var ( ErrDeadLetter = errors.New("outbox: dead letter") ErrEOQ = errors.New("outbox: end of queue") ErrInvalidMaxAttempts = errors.New("outbox: max attempts must not be negative") ErrLeaseExpired = errors.New("outbox: lease expired") ErrNotFound = errors.New("outbox: not found") ErrTxInContext = errors.New("outbox: ctx must not carry a transaction") )
Functions ¶
func DefaultBackoff ¶
DefaultBackoff doubles the delay on every attempt, starting from 1s and capped at 1h, with jitter.
Types ¶
type EnqueueParams ¶
type EnqueueParams struct {
AggregateID string
AggregateType string
EventType string
Payload jsontext.Value
// MaxAttempts is the number of deliveries before the message is dead.
// Zero means the default of 10.
MaxAttempts int32
// AvailableAt delays the first delivery. Zero means now.
AvailableAt time.Time
}
type HandlerFunc ¶
HandlerFunc processes a message. The ctx carries the transaction that acknowledges the message.
type Outbox ¶
type Outbox struct {
// Backoff returns how long a failed message stays hidden before it is
// retried. Defaults to DefaultBackoff.
Backoff func(msg *Message) time.Duration
// Lease is how long dequeued messages stay hidden from other workers.
// Each message's lease is renewed when its handler starts, and the
// message cannot be reclaimed while the handler runs. A message whose
// lease expires while it waits for its turn in the batch may be
// reclaimed by another worker, and is then skipped. Defaults to
// DefaultLease.
Lease time.Duration
// contains filtered or unexported fields
}
func New ¶
func New(repo Repository) *Outbox
func (*Outbox) DeadLetters ¶
DeadLetters returns up to limit dead messages, oldest first.
func (*Outbox) Dequeue ¶
Dequeue leases up to limit visible messages and passes each to fn, in its own transaction.
If fn returns nil, the message is marked as done in the same transaction as the writes made by fn. If fn returns an error, the writes made by fn are rolled back, and the message is hidden until its backoff expires. The message is dead once it runs out of attempts, or when the error wraps ErrDeadLetter.
Errors from fn are recorded on the message, and not returned. A message that another worker reclaimed before its turn is skipped with ErrLeaseExpired. If ctx is cancelled, or fn panics, the messages not handled yet are released without using up an attempt. It returns ErrEOQ if there are no visible messages, and ErrTxInContext if ctx already carries a transaction of the repository.
type Repository ¶
type Repository interface {
Ack(ctx context.Context, id uuid.UUID, lockedBy string) error
Count(ctx context.Context) (int64, error)
Create(ctx context.Context, params EnqueueParams) (uuid.UUID, error)
Dead(ctx context.Context) (int64, error)
IsTx(ctx context.Context) bool
ListDead(ctx context.Context, limit int32) ([]*Message, error)
Lock(ctx context.Context, id uuid.UUID, lockedBy string, lease time.Duration) error
Nack(ctx context.Context, id uuid.UUID, lockedBy string, dead bool, lastError string, delay time.Duration) error
Poll(ctx context.Context, lockedBy string, limit int32, lease time.Duration) ([]*Message, error)
Purge(ctx context.Context, status string, before time.Time) (int64, error)
Release(ctx context.Context, id uuid.UUID, lockedBy string) error
Requeue(ctx context.Context, id uuid.UUID) error
RunInSubTx(ctx context.Context, fn func(txCtx context.Context) error) error
RunInTx(ctx context.Context, fn func(txCtx context.Context) error) error
}
Repository stores outbox messages.