Documentation
¶
Overview ¶
PostgresStore implements durable outbox storage using pgx. The caller owns the pool and every transaction passed to PostgresStore.Bind; the adapter never closes the pool or commits or rolls back the caller's outer transaction.
RedisStore implements storage with bounded, atomic Redis Lua scripts. It treats Redis as authoritative storage, not as a cache. Production deployments must deliberately configure persistence, noeviction, backups, replication, and failover.
Example (PostgresDomainTransaction) ¶
package main
import (
"context"
"os"
"github.com/0x626f/react/outbox"
"github.com/jackc/pgx/v5/pgxpool"
)
func main() {
ctx := context.Background()
pool, err := pgxpool.New(ctx, os.Getenv("POSTGRES_URL"))
if err != nil {
return
}
defer pool.Close()
store, err := outbox.NewPostgresStore(pool, outbox.DefaultPostgresConfig())
if err != nil {
return
}
tx, err := pool.Begin(ctx)
if err != nil {
return
}
defer tx.Rollback(context.Background())
if _, err = tx.Exec(ctx, `UPDATE orders SET state='confirmed' WHERE id=$1`, "42"); err != nil {
return
}
if _, err = store.Bind(tx).Append(ctx, exampleRecord("atomic-postgres")); err != nil {
return
}
_ = tx.Commit(ctx)
}
func exampleRecord(id outbox.ID) outbox.NewRecord {
return outbox.NewRecord{ID: id, Destination: "orders", MessageType: "order.confirmed", Payload: []byte(`{"order_id":"42"}`), MaxAttempts: 8}
}
Output:
Example (PostgresStandalone) ¶
package main
import (
"context"
"os"
"github.com/0x626f/react/outbox"
"github.com/jackc/pgx/v5/pgxpool"
)
func main() {
ctx := context.Background()
pool, err := pgxpool.New(ctx, os.Getenv("POSTGRES_URL"))
if err != nil {
return
}
defer pool.Close()
config := outbox.DefaultPostgresConfig()
config.Namespace = "orders"
store, err := outbox.NewPostgresStore(pool, config)
if err != nil {
return
}
if err = store.Migrate(ctx); err != nil {
return
}
_, _ = store.Append(ctx, exampleRecord("standalone-postgres"))
}
func exampleRecord(id outbox.ID) outbox.NewRecord {
return outbox.NewRecord{ID: id, Destination: "orders", MessageType: "order.confirmed", Payload: []byte(`{"order_id":"42"}`), MaxAttempts: 8}
}
Output:
Example (RabbitMQPublisherConfirms) ¶
package main
import (
"context"
"errors"
"github.com/0x626f/react/outbox"
)
func main() {
// IConfirmedPublisher must enable publisher confirms, use a stable message
// ID, and resolve only after ack/nack plus mandatory-return handling.
var publisher IConfirmedPublisher
var sink outbox.ISink = rabbitConfirmedSink{publisher: publisher}
_ = sink
}
// IConfirmedPublisher publishes a message and waits for its broker outcome.
type IConfirmedPublisher interface {
PublishAndConfirm(ctx context.Context, destination string, messageID string, headers map[string]string, payload []byte) (routed bool, err error)
}
type rabbitConfirmedSink struct{ publisher IConfirmedPublisher }
func (sink rabbitConfirmedSink) Deliver(ctx context.Context, record outbox.Record) error {
if sink.publisher == nil {
return errors.New("publisher unavailable")
}
routed, err := sink.publisher.PublishAndConfirm(ctx, record.Destination, string(record.ID), record.Headers, record.Payload)
if err != nil {
return err
}
if !routed {
return &outbox.TerminalError{Err: errors.New("message was returned as unroutable")}
}
return nil
}
Output:
Example (RedisDomainComposition) ¶
package main
import (
"context"
"os"
"github.com/0x626f/react/outbox"
goredis "github.com/redis/go-redis/v9"
)
func main() {
options, err := goredis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil {
return
}
client := goredis.NewClient(options)
defer client.Close()
config := outbox.DefaultRedisConfig()
config.Namespace = "orders"
store, err := outbox.NewRedisStore(context.Background(), client, config)
if err != nil {
return
}
domainKey := "react:outbox:{orders}:domain:order-42"
_, _ = store.Compose(context.Background(), outbox.RedisCompositionRequest{
Append: outbox.AppendRequest{Records: []outbox.NewRecord{exampleRecord("composed-redis")}, DuplicateMode: outbox.RejectDuplicate},
DomainKeys: []string{domainKey}, DomainArguments: []any{"pending", "confirmed"},
ValidateLua: `
local value = redis.call('GET', KEYS[DOMAIN_KEY_OFFSET+1])
if value ~= ARGV[DOMAIN_ARG_OFFSET+1] then return {-3} end`,
ApplyLua: `redis.call('SET', KEYS[DOMAIN_KEY_OFFSET+1], ARGV[DOMAIN_ARG_OFFSET+2])`,
})
}
func exampleRecord(id outbox.ID) outbox.NewRecord {
return outbox.NewRecord{ID: id, Destination: "orders", MessageType: "order.confirmed", Payload: []byte(`{"order_id":"42"}`), MaxAttempts: 8}
}
Output:
Example (RedisStandalone) ¶
package main
import (
"context"
"os"
"github.com/0x626f/react/outbox"
goredis "github.com/redis/go-redis/v9"
)
func main() {
options, err := goredis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil {
return
}
client := goredis.NewClient(options)
defer client.Close()
config := outbox.DefaultRedisConfig()
config.Namespace = "orders"
config.DurabilityMode = outbox.RedisDurabilityRequireAOF
store, err := outbox.NewRedisStore(context.Background(), client, config)
if err != nil {
return
}
_, _ = store.Append(context.Background(), exampleRecord("standalone-redis"))
}
func exampleRecord(id outbox.ID) outbox.NewRecord {
return outbox.NewRecord{ID: id, Destination: "orders", MessageType: "order.confirmed", Payload: []byte(`{"order_id":"42"}`), MaxAttempts: 8}
}
Output:
Index ¶
- Constants
- Variables
- func CanonicalTime(value time.Time) time.Time
- func CursorForRecord(record Record, field SortField, direction SortDirection) (string, error)
- func EncodeCursor(cursor Cursor) (string, error)
- func ForFeature(storage StoreFeature, configs ...Config) *gioc.Module
- func ImmutableDigest(record NewRecord) string
- func NormalizeQuery(query Query, limits Limits) (Query, Cursor, error)
- func PostgresStoreProvider(token gioc.Token, config PostgresConfig) gioc.IProvider
- func PostgresStoreServiceProvider(token gioc.Token) gioc.IProvider
- func ProvidePostgresConfig(config PostgresConfig) gioc.IProvider
- func ProvideRedisConfig(config RedisConfig) gioc.IProvider
- func RecordAfterCursor(record Record, cursor Cursor) bool
- func RecordSortTime(record Record, field SortField) time.Time
- func RedisClusterSlot(key string) uint16
- func RedisStoreProvider(token gioc.Token, config RedisConfig) gioc.IProvider
- func RedisStoreServiceProvider(token gioc.Token) gioc.IProvider
- func ServiceCapabilityProviders(tokens Tokens) []gioc.IProvider
- func SortRecords(records []Record, field SortField, direction SortDirection)
- func ValidateDestination(destination string, limits Limits) error
- func ValidateID(id ID, limits Limits) error
- func ValidateLeaseDuration(field string, duration, maximum time.Duration) error
- func ValidateLeaseOwner(owner string, limits Limits) error
- func ValidateLeaseToken(token string, limits Limits) error
- func ValidateTimestamp(field string, value time.Time) error
- type AppendRequest
- type Backlog
- type ClaimRequest
- type Config
- type Cursor
- type DeliveryOutcome
- type DeliveryResult
- type DestinationsConfig
- type DuplicateMode
- type ErrorClassifierFunc
- type ExponentialBackoff
- type ExponentialBackoffConfig
- type Failure
- type FieldError
- type Health
- type IAppender
- type IBacklogReader
- type IBatchAppender
- type ID
- type IDeliveryStore
- type IErrorClassifier
- type IHealthChecker
- type IMaintenanceStore
- type IPostgresDB
- type IPostgresRowScanner
- type IReader
- type IRetryPolicy
- type ISink
- type IStore
- type LeaseRef
- type Limits
- type NewRecord
- type OperationError
- type Page
- type PostgresConfig
- type PostgresMigration
- type PostgresStore
- func (store *PostgresStore) Acknowledge(ctx context.Context, lease LeaseRef, _ DeliveryResult) error
- func (store *PostgresStore) Append(ctx context.Context, records ...NewRecord) ([]Record, error)
- func (store *PostgresStore) AppendBatch(ctx context.Context, request AppendRequest) ([]Record, error)
- func (store *PostgresStore) Backlog(ctx context.Context) (Backlog, error)
- func (store *PostgresStore) Bind(tx pgx.Tx) *PostgresTxAppender
- func (store *PostgresStore) Cancel(ctx context.Context, id ID, reason string) error
- func (store *PostgresStore) Claim(ctx context.Context, request ClaimRequest) ([]Record, error)
- func (store *PostgresStore) Close() error
- func (store *PostgresStore) DeadLetter(ctx context.Context, lease LeaseRef, failure Failure) error
- func (store *PostgresStore) Find(ctx context.Context, query Query) (Page, error)
- func (store *PostgresStore) Get(ctx context.Context, id ID) (Record, error)
- func (store *PostgresStore) Health(ctx context.Context) Health
- func (store *PostgresStore) Migrate(ctx context.Context) error
- func (store *PostgresStore) Migrations() []PostgresMigration
- func (store *PostgresStore) Purge(ctx context.Context, request PurgeRequest) (int, error)
- func (store *PostgresStore) Release(ctx context.Context, lease LeaseRef, availableAt time.Time) error
- func (store *PostgresStore) Renew(ctx context.Context, lease LeaseRef, until time.Time) error
- func (store *PostgresStore) Requeue(ctx context.Context, id ID, options RequeueOptions) error
- func (store *PostgresStore) Reschedule(ctx context.Context, id ID, availableAt time.Time) error
- func (store *PostgresStore) Retry(ctx context.Context, lease LeaseRef, retry RetryRequest) error
- type PostgresStoreService
- type PostgresTxAppender
- type PurgeRequest
- type Query
- type Record
- type RedisCompositionRequest
- type RedisConfig
- type RedisDurabilityMode
- type RedisDurabilityReport
- type RedisKeys
- func (keys RedisKeys) Cancelled() string
- func (keys RedisKeys) Dead() string
- func (keys RedisKeys) Delivered() string
- func (keys RedisKeys) Idempotency() string
- func (keys RedisKeys) Leased() string
- func (keys RedisKeys) Namespace() string
- func (keys RedisKeys) Pending() string
- func (keys RedisKeys) PendingDestinations() string
- func (keys RedisKeys) QueryAll() string
- func (keys RedisKeys) QueryCancelled() string
- func (keys RedisKeys) QueryDead() string
- func (keys RedisKeys) QueryDelivered() string
- func (keys RedisKeys) QueryDestinations() string
- func (keys RedisKeys) QueryLeased() string
- func (keys RedisKeys) QueryPending() string
- func (keys RedisKeys) QueryState(state State) (string, error)
- func (keys RedisKeys) RecordKey(id ID) string
- func (keys RedisKeys) Records() string
- func (keys RedisKeys) ScriptKeys() []string
- func (keys RedisKeys) State(state State) (string, error)
- type RedisStore
- func (store *RedisStore) Acknowledge(ctx context.Context, lease LeaseRef, _ DeliveryResult) error
- func (store *RedisStore) Append(ctx context.Context, records ...NewRecord) ([]Record, error)
- func (store *RedisStore) AppendBatch(ctx context.Context, request AppendRequest) ([]Record, error)
- func (store *RedisStore) Backlog(ctx context.Context) (Backlog, error)
- func (store *RedisStore) Cancel(ctx context.Context, id ID, reason string) error
- func (store *RedisStore) CheckDurability(ctx context.Context) (report RedisDurabilityReport, resultErr error)
- func (store *RedisStore) Claim(ctx context.Context, request ClaimRequest) ([]Record, error)
- func (store *RedisStore) Close() error
- func (store *RedisStore) Compose(ctx context.Context, request RedisCompositionRequest) ([]Record, error)
- func (store *RedisStore) DeadLetter(ctx context.Context, lease LeaseRef, failure Failure) error
- func (store *RedisStore) Find(ctx context.Context, query Query) (Page, error)
- func (store *RedisStore) Get(ctx context.Context, id ID) (Record, error)
- func (store *RedisStore) Health(ctx context.Context) Health
- func (store *RedisStore) Keys() RedisKeys
- func (store *RedisStore) LastDurabilityReport() RedisDurabilityReport
- func (store *RedisStore) Purge(ctx context.Context, request PurgeRequest) (int, error)
- func (store *RedisStore) Release(ctx context.Context, lease LeaseRef, availableAt time.Time) error
- func (store *RedisStore) Renew(ctx context.Context, lease LeaseRef, until time.Time) error
- func (store *RedisStore) Requeue(ctx context.Context, id ID, options RequeueOptions) error
- func (store *RedisStore) Reschedule(ctx context.Context, id ID, availableAt time.Time) error
- func (store *RedisStore) Retry(ctx context.Context, lease LeaseRef, retry RetryRequest) error
- type RedisStoreService
- type RequeueOptions
- type RetryRequest
- type Service
- func (service *Service) Append(ctx context.Context, records ...NewRecord) ([]Record, error)
- func (service *Service) AppendBatch(ctx context.Context, request AppendRequest) ([]Record, error)
- func (service *Service) Destinations() []string
- func (service *Service) Done() <-chan struct{}
- func (service *Service) Register(sink ISink, config DestinationsConfig) error
- func (service *Service) Shutdown(ctx context.Context) error
- func (service *Service) String() string
- type SinkFunc
- type SortDirection
- type SortField
- type State
- type StoreFeature
- type StoreProviderFactory
- type TerminalError
- type TimeRange
- type Tokens
Examples ¶
Constants ¶
const ( // OutboxModuleToken is the base token used by modules returned from // ForFeature. The selected storage feature is appended to this value. OutboxModuleToken gioc.Token = "OutboxModule" // OutboxServiceToken resolves the lifecycle-aware routing and worker hub. OutboxServiceToken gioc.Token = "OutboxService" // OutboxConfigToken resolves the service-level worker pool configuration. OutboxConfigToken gioc.Token = "OutboxConfig" // OutboxStoreToken resolves the selected adapter's internal aggregate store. OutboxStoreToken gioc.Token = "OutboxStore" // OutboxAppenderToken exposes only producer append capability. OutboxAppenderToken gioc.Token = "OutboxAppender" // OutboxDeliveryStoreToken exposes only fenced delivery state transitions. OutboxDeliveryStoreToken gioc.Token = "OutboxDeliveryStore" // OutboxReaderToken exposes only operational reads. OutboxReaderToken gioc.Token = "OutboxReader" // OutboxMaintenanceStoreToken exposes state-aware administrative operations. OutboxMaintenanceStoreToken gioc.Token = "OutboxMaintenanceStore" // OutboxPostgresConfigToken is consumed by PostgresStoreService. OutboxPostgresConfigToken gioc.Token = "OutboxPostgresConfig" // OutboxRedisConfigToken is consumed by RedisStoreService. OutboxRedisConfigToken gioc.Token = "OutboxRedisConfig" )
const MaxClaimDestinations = 16
MaxClaimDestinations is the portable bound for one storage claim. Service rotates larger routing tables through bounded claim windows.
const MaximumTimestampUnixMicro int64 = 1<<53 - 1
MaximumTimestampUnixMicro is the largest portable timestamp. Microseconds through this value are exactly representable by the numeric type used by Redis Lua and are also supported by the other first-party adapters.
Variables ¶
var ( // ErrNotFound means no record exists for the requested ID. ErrNotFound = errors.New("outbox: record not found") // ErrConflict means existing durable state contradicts an idempotent request. ErrConflict = errors.New("outbox: conflict") // ErrDuplicateID means an ID or idempotency key already exists in reject mode. ErrDuplicateID = errors.New("outbox: duplicate ID or idempotency key") // ErrLeaseLost means the supplied lease fence is absent, stale, or expired. ErrLeaseLost = errors.New("outbox: lease lost") // ErrInvalidTransition means the record state does not permit the operation. ErrInvalidTransition = errors.New("outbox: invalid state transition") // ErrUnsupportedCriteria means an adapter lacks indexes for a query shape. ErrUnsupportedCriteria = errors.New("outbox: unsupported query criteria") // ErrClosed means the adapter facade has been closed. ErrClosed = errors.New("outbox: store closed") // ErrInvalidArgument means validation rejected external input. ErrInvalidArgument = errors.New("outbox: invalid argument") )
var ( // Postgres selects PostgresStoreService. Provide PostgresConfig at // OutboxPostgresConfigToken and import React's PostgreSQL module. Postgres = DefineStoreFeature("postgres", PostgresStoreServiceProvider) // Redis selects RedisStoreService. Provide RedisConfig at // OutboxRedisConfigToken and import React's Redis module. Redis = DefineStoreFeature("redis", RedisStoreServiceProvider) )
var ErrRedisUnsafeDurability = errors.New("outbox redis: unsafe durability configuration")
ErrRedisUnsafeDurability marks a required persistence or eviction policy failure.
var PostgresStoreServiceInjections = []gioc.Token{ reactpostgres.DataSourceToken, OutboxPostgresConfigToken, react.LoggerToken, }
PostgresStoreServiceInjections lists the dependencies used by NewPostgresStoreService.
var RedisStoreServiceInjections = []gioc.Token{ reactredis.ServiceToken, react.ApplicationContextToken, OutboxRedisConfigToken, react.LoggerToken, }
RedisStoreServiceInjections lists the dependencies used by NewRedisStoreService.
var ServiceInjections = []gioc.Token{ OutboxStoreToken, OutboxConfigToken, react.ApplicationContextServiceToken, react.LoggerToken, }
ServiceInjections are the complete dependencies resolved by NewService.
Functions ¶
func CanonicalTime ¶
CanonicalTime is the timestamp representation shared by all adapters. PostgreSQL and Redis both preserve microseconds, and Unix microseconds remain exactly representable by Redis Lua numbers for the supported date range.
func CursorForRecord ¶
func CursorForRecord(record Record, field SortField, direction SortDirection) (string, error)
CursorForRecord encodes a record's complete stable sort tuple.
func EncodeCursor ¶
EncodeCursor creates the opaque base64url continuation value.
func ForFeature ¶
func ForFeature(storage StoreFeature, configs ...Config) *gioc.Module
ForFeature creates one outbox module backed by storage. The optional Config controls its single service-level worker pool; omitting it uses DefaultConfig. Sinks and routes are registered through Service.Register.
func ImmutableDigest ¶
ImmutableDigest returns the canonical digest used for duplicate comparison.
func NormalizeQuery ¶
NormalizeQuery applies defaults and validates portable query bounds.
func PostgresStoreProvider ¶
func PostgresStoreProvider(token gioc.Token, config PostgresConfig) gioc.IProvider
PostgresStoreProvider creates a singleton store at token from React's PostgreSQL DataSourceToken. Use distinct tokens for independently named outboxes. For standard module-managed wiring prefer ProvidePostgresConfig with ForFeature; that path provides PostgresStoreService and the service-owned worker pool.
func PostgresStoreServiceProvider ¶
PostgresStoreServiceProvider provides a singleton PostgresStoreService at token.
func ProvidePostgresConfig ¶
func ProvidePostgresConfig(config PostgresConfig) gioc.IProvider
ProvidePostgresConfig returns a provider for OutboxPostgresConfigToken.
func ProvideRedisConfig ¶
func ProvideRedisConfig(config RedisConfig) gioc.IProvider
ProvideRedisConfig returns a provider for OutboxRedisConfigToken.
func RecordAfterCursor ¶
RecordAfterCursor reports whether a record belongs after a continuation tuple.
func RecordSortTime ¶
RecordSortTime returns the selected stable sort component.
func RedisClusterSlot ¶
RedisClusterSlot returns the Redis Cluster slot for a key and is useful for deployment validation and third-party composition tests.
func RedisStoreProvider ¶
func RedisStoreProvider(token gioc.Token, config RedisConfig) gioc.IProvider
RedisStoreProvider creates a singleton store at token from React's Redis service and application context. Use distinct tokens and namespaces for named outboxes. For standard module-managed wiring prefer ProvideRedisConfig with ForFeature; that path provides RedisStoreService and the service-owned worker pool.
func RedisStoreServiceProvider ¶
RedisStoreServiceProvider provides a singleton RedisStoreService at token.
func ServiceCapabilityProviders ¶
ServiceCapabilityProviders exposes the service through narrow named capabilities. Ordinary producers should inject IAppender; administrative consumers can request only the stronger interface they need.
func SortRecords ¶
func SortRecords(records []Record, field SortField, direction SortDirection)
SortRecords orders records by the complete time-plus-ID tuple.
func ValidateDestination ¶
ValidateDestination checks a portable destination value.
func ValidateID ¶
ValidateID checks the portable record ID syntax.
func ValidateLeaseDuration ¶
ValidateLeaseDuration checks a lease duration against the portable microsecond precision and an adapter's configured upper bound.
func ValidateLeaseOwner ¶
ValidateLeaseOwner checks the bounded worker identity stored with a claim.
func ValidateLeaseToken ¶
ValidateLeaseToken checks the bounded shape of a generated lease token.
Types ¶
type AppendRequest ¶
type AppendRequest struct {
Records []NewRecord
DuplicateMode DuplicateMode
}
AppendRequest selects duplicate semantics for one atomic bounded batch.
type ClaimRequest ¶
type ClaimRequest struct {
Owner string
Limit int
LeaseDuration time.Duration
Destinations []string
// RecoveryLimit bounds expired-lease work performed by a claim. Zero uses
// the adapter's claim limit.
RecoveryLimit int
}
ClaimRequest describes one bounded due-record lease operation.
type Config ¶
type Config struct {
WorkerCount int
ClaimBatchSize int
LeaseDuration time.Duration
RenewalEnabled bool
LeaseRenewalThreshold time.Duration
PollMinimumInterval time.Duration
PollMaximumInterval time.Duration
DeliveryTimeout time.Duration
ShutdownTimeout time.Duration
MaximumAttempts int
Retry ExponentialBackoffConfig
Limits Limits
// These dependencies are optional application delivery policies. The service
// supplies production defaults when they are nil.
RetryPolicy IRetryPolicy
ErrorClassifier IErrorClassifier
Owner string
}
Config controls the service-level worker pool and delivery policy. Storage adapters keep their own namespace and durability configuration.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns conservative, internally valid service defaults.
type Cursor ¶
type Cursor struct {
Version int `json:"v"`
Sort SortField `json:"s"`
Direction SortDirection `json:"d"`
Micros int64 `json:"t"`
ID ID `json:"i"`
}
Cursor is exported for third-party adapter implementations; applications should treat encoded cursor strings as opaque.
func DecodeCursor ¶
DecodeCursor validates a continuation value for adapter implementations.
type DeliveryOutcome ¶
type DeliveryOutcome string
DeliveryOutcome classifies a transport attempt for settlement.
const ( OutcomeSuccess DeliveryOutcome = "success" OutcomeRetryable DeliveryOutcome = "retryable" OutcomeTerminal DeliveryOutcome = "terminal" OutcomeAmbiguous DeliveryOutcome = "ambiguous" )
func (DeliveryOutcome) Valid ¶
func (outcome DeliveryOutcome) Valid() bool
Valid reports whether outcome is one of the bounded built-in labels.
type DeliveryResult ¶
type DeliveryResult struct{}
DeliveryResult is intentionally transport-neutral. Delivery is considered successful only after the sink's own reliable-delivery condition is met.
type DestinationsConfig ¶
DestinationsConfig binds one sink to a unique set of destinations. Concurrency limits all deliveries through that sink registration; zero uses the service worker count.
type DuplicateMode ¶
type DuplicateMode int
DuplicateMode controls append behavior when an ID or idempotency key exists.
const ( RejectDuplicate DuplicateMode = iota AcceptIdentical )
func (DuplicateMode) Valid ¶
func (m DuplicateMode) Valid() bool
Valid reports whether m is a defined duplicate mode.
type ErrorClassifierFunc ¶
type ErrorClassifierFunc func(error) DeliveryOutcome
ErrorClassifierFunc adapts a function to IErrorClassifier.
func (ErrorClassifierFunc) Classify ¶
func (f ErrorClassifierFunc) Classify(err error) DeliveryOutcome
type ExponentialBackoff ¶
type ExponentialBackoff struct {
// contains filtered or unexported fields
}
ExponentialBackoff implements bounded exponential delay with optional jitter.
func NewExponentialBackoff ¶
func NewExponentialBackoff(config ExponentialBackoffConfig) (*ExponentialBackoff, error)
NewExponentialBackoff validates and constructs a retry policy.
type ExponentialBackoffConfig ¶
type ExponentialBackoffConfig struct {
Minimum time.Duration
Maximum time.Duration
Multiplier float64
Jitter float64
MaxAttempts int
}
ExponentialBackoffConfig configures bounded exponential retry delays.
type Failure ¶
Failure is bounded, non-sensitive error information persisted operationally.
func BoundFailure ¶
BoundFailure truncates persisted failure fields without producing invalid UTF-8.
type FieldError ¶
FieldError identifies an invalid public input while remaining inspectable as ErrInvalidArgument through errors.Is.
func (*FieldError) Error ¶
func (e *FieldError) Error() string
func (*FieldError) Unwrap ¶
func (e *FieldError) Unwrap() error
type Health ¶
type Health struct {
Ready bool
StorageAvailable bool
DurabilitySafe bool
Message string
Backlog Backlog
}
Health separates storage and durability readiness from backlog signals.
type IBacklogReader ¶
IBacklogReader supplies bounded-cardinality backlog observations.
type IBatchAppender ¶
type IBatchAppender interface {
AppendBatch(ctx context.Context, request AppendRequest) ([]Record, error)
}
IBatchAppender is implemented by first-party adapters in addition to the convenient IAppender API.
type IDeliveryStore ¶
type IDeliveryStore interface {
Claim(ctx context.Context, request ClaimRequest) ([]Record, error)
Renew(ctx context.Context, lease LeaseRef, until time.Time) error
Acknowledge(ctx context.Context, lease LeaseRef, result DeliveryResult) error
Retry(ctx context.Context, lease LeaseRef, retry RetryRequest) error
DeadLetter(ctx context.Context, lease LeaseRef, failure Failure) error
Release(ctx context.Context, lease LeaseRef, availableAt time.Time) error
}
IDeliveryStore exposes only fenced delivery state-machine operations.
type IErrorClassifier ¶
type IErrorClassifier interface {
Classify(err error) DeliveryOutcome
}
IErrorClassifier maps sink errors to transport-neutral outcomes.
func DefaultErrorClassifier ¶
func DefaultErrorClassifier() IErrorClassifier
DefaultErrorClassifier treats TerminalError as terminal and every other non-nil error as retryable.
type IHealthChecker ¶
IHealthChecker reports readiness separately from backlog pressure. A large backlog is observable but does not by itself make Ready false.
type IMaintenanceStore ¶
type IMaintenanceStore interface {
Cancel(ctx context.Context, id ID, reason string) error
Reschedule(ctx context.Context, id ID, availableAt time.Time) error
Requeue(ctx context.Context, id ID, options RequeueOptions) error
Purge(ctx context.Context, request PurgeRequest) (int, error)
}
IMaintenanceStore contains separately injectable administrative operations.
type IPostgresDB ¶
type IPostgresDB interface {
Begin(ctx context.Context) (pgx.Tx, error)
Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error)
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}
IPostgresDB is the narrow pgx pool surface owned by the application.
type IPostgresRowScanner ¶
IPostgresRowScanner is the shared row and rows scanning surface.
type IReader ¶
type IReader interface {
Get(ctx context.Context, id ID) (Record, error)
Find(ctx context.Context, query Query) (Page, error)
}
IReader retrieves records without granting mutation capability.
type IRetryPolicy ¶
IRetryPolicy decides whether and when a failed attempt becomes due again.
func DefaultRetryPolicy ¶
func DefaultRetryPolicy(maxAttempts int) IRetryPolicy
DefaultRetryPolicy returns the library's general-purpose bounded policy.
type ISink ¶
ISink delivers one copied record and returns nil only after its reliable transport acceptance condition has been met.
type IStore ¶
type IStore interface {
IAppender
IBatchAppender
IDeliveryStore
IReader
IMaintenanceStore
}
IStore is the aggregate convenience contract implemented by first-party adapters.
type Limits ¶
type Limits struct {
MaxIDBytes int
MaxDestinationBytes int
MaxMessageTypeBytes int
MaxAggregateTypeBytes int
MaxAggregateIDBytes int
MaxOrderingKeyBytes int
MaxIdempotencyKeyBytes int
MaxPayloadBytes int
MaxHeaders int
MaxHeaderKeyBytes int
MaxHeaderValueBytes int
MaxHeaderBytes int
MaxErrorCodeBytes int
MaxErrorMessageBytes int
MaxLeaseOwnerBytes int
MaxLeaseTokenBytes int
MaxBatchSize int
MaxClaimBatchSize int
MaxPageSize int
MaxQueryIDs int
MaxQueryValues int
MaxPurgeSize int
MaxAttempts int
MaxWorkerCount int
MaxDestinationWorkers int
}
Limits bounds data and work accepted at public API boundaries.
func DefaultLimits ¶
func DefaultLimits() Limits
DefaultLimits returns the portable first-party resource bounds.
func (Limits) Normalized ¶
Normalized returns a copy with safe defaults applied to zero-valued fields.
type NewRecord ¶
type NewRecord struct {
ID ID
Destination string
MessageType string
AggregateType string
AggregateID string
OrderingKey string
IdempotencyKey string
Headers map[string]string
Payload []byte
AvailableAt time.Time
MaxAttempts int
}
NewRecord contains immutable message content and its initial delivery policy.
type OperationError ¶
OperationError adds stable operation context without hiding a sentinel returned by an adapter.
func (*OperationError) Error ¶
func (e *OperationError) Error() string
func (*OperationError) Unwrap ¶
func (e *OperationError) Unwrap() error
type PostgresConfig ¶
type PostgresConfig struct {
Namespace string
Schema string
Table string
DuplicateMode DuplicateMode
DefaultMaxAttempts int
MaxLeaseDuration time.Duration
Limits Limits
}
PostgresConfig selects namespace, validated identifiers, and limits.
func DefaultPostgresConfig ¶
func DefaultPostgresConfig() PostgresConfig
DefaultPostgresConfig returns adapter defaults for one shared table.
type PostgresMigration ¶
PostgresMigration is one ordered adapter-owned schema change.
type PostgresStore ¶
type PostgresStore struct {
// contains filtered or unexported fields
}
PostgresStore implements the portable contracts with an application-owned pgx pool.
func NewPostgresStore ¶
func NewPostgresStore(db IPostgresDB, config PostgresConfig) (*PostgresStore, error)
NewPostgresStore validates and constructs a PostgreSQL adapter without running migrations.
func NewPostgresStoreFromDataSource ¶
func NewPostgresStoreFromDataSource(dataSource *pgxext.DataSource, config PostgresConfig) (*PostgresStore, error)
NewPostgresStoreFromDataSource constructs a store over React's application-owned PostgreSQL pool. Closing the returned facade never closes the data source.
func (*PostgresStore) Acknowledge ¶
func (store *PostgresStore) Acknowledge(ctx context.Context, lease LeaseRef, _ DeliveryResult) error
func (*PostgresStore) AppendBatch ¶
func (store *PostgresStore) AppendBatch(ctx context.Context, request AppendRequest) ([]Record, error)
func (*PostgresStore) Backlog ¶
func (store *PostgresStore) Backlog(ctx context.Context) (Backlog, error)
func (*PostgresStore) Bind ¶
func (store *PostgresStore) Bind(tx pgx.Tx) *PostgresTxAppender
Bind creates an appender on the caller's pgx transaction. Append uses a nested pgx transaction (a savepoint) so a rejected batch leaves no partial rows. It never commits or rolls back the caller's outer transaction.
func (*PostgresStore) Claim ¶
func (store *PostgresStore) Claim(ctx context.Context, request ClaimRequest) ([]Record, error)
func (*PostgresStore) Close ¶
func (store *PostgresStore) Close() error
Close only closes this adapter facade. The application-owned PostgreSQL pool remains open.
func (*PostgresStore) DeadLetter ¶
func (*PostgresStore) Migrate ¶
func (store *PostgresStore) Migrate(ctx context.Context) error
Migrate applies the adapter-owned, versioned migrations. Applications may instead consume Migrations from their normal migration system.
func (*PostgresStore) Migrations ¶
func (store *PostgresStore) Migrations() []PostgresMigration
Migrations returns rendered, safely quoted adapter migrations in version order.
func (*PostgresStore) Purge ¶
func (store *PostgresStore) Purge(ctx context.Context, request PurgeRequest) (int, error)
func (*PostgresStore) Requeue ¶
func (store *PostgresStore) Requeue(ctx context.Context, id ID, options RequeueOptions) error
func (*PostgresStore) Reschedule ¶
func (*PostgresStore) Retry ¶
func (store *PostgresStore) Retry(ctx context.Context, lease LeaseRef, retry RetryRequest) error
type PostgresStoreService ¶
type PostgresStoreService struct {
*PostgresStore
Logger react.ILogger
}
PostgresStoreService is the module-managed PostgreSQL store facade. The application retains ownership of the injected data source and its pool.
func NewPostgresStoreService ¶
func NewPostgresStoreService(injections gioc.Injections) (*PostgresStoreService, error)
NewPostgresStoreService resolves React's PostgreSQL data source and PostgresConfig from OutboxPostgresConfigToken.
func (*PostgresStoreService) Close ¶
func (service *PostgresStoreService) Close() error
Close closes the adapter facade and logs an unexpected failure.
type PostgresTxAppender ¶
type PostgresTxAppender struct {
// contains filtered or unexported fields
}
PostgresTxAppender appends through a caller-owned pgx transaction.
func (*PostgresTxAppender) AppendBatch ¶
func (appender *PostgresTxAppender) AppendBatch(ctx context.Context, request AppendRequest) ([]Record, error)
type PurgeRequest ¶
PurgeRequest deletes a bounded set of terminal records before a cutoff.
func NormalizePurgeRequest ¶
func NormalizePurgeRequest(request PurgeRequest, limits Limits) (PurgeRequest, error)
NormalizePurgeRequest validates and canonicalizes bounded terminal cleanup work for adapter implementations.
type Query ¶
type Query struct {
IDs []ID
States []State
Destinations []string
MessageTypes []string
AggregateType string
AggregateID string
OrderingKey string
IdempotencyKey string
CreatedAt TimeRange
AvailableAt TimeRange
Sort SortField
Direction SortDirection
Limit int
Cursor string
}
Query is the deliberately limited, storage-independent operational query model.
type Record ¶
type Record struct {
ID ID
Destination string
MessageType string
AggregateType string
AggregateID string
OrderingKey string
IdempotencyKey string
Headers map[string]string
Payload []byte
// ContentDigest is a lowercase SHA-256 digest of immutable message content.
// It deliberately excludes ID and mutable delivery scheduling fields so an
// idempotency key can identify the same message submitted with a newly
// generated ID or after the existing record has been rescheduled.
ContentDigest string
State State
Attempts int
MaxAttempts int
AvailableAt time.Time
LeaseOwner string
LeaseToken string
LeaseUntil *time.Time
LastErrorCode string
LastErrorMessage string
CreatedAt time.Time
UpdatedAt time.Time
DeliveredAt *time.Time
DeadAt *time.Time
CancelledAt *time.Time
Version uint64
}
Record is the copied public snapshot of one persisted outbox record.
func PrepareRecord ¶
func PrepareRecord(input NewRecord, now time.Time, defaultMaxAttempts int, limits Limits) (Record, error)
PrepareRecord validates, defaults, copies, and digests an append request. Adapters call it before making any storage mutation.
type RedisCompositionRequest ¶
type RedisCompositionRequest struct {
Append AppendRequest
DomainKeys []string
DomainArguments []any
ValidateLua string
ApplyLua string
}
RedisCompositionRequest atomically combines a Redis-backed domain mutation with an outbox append in one server-side script. DomainKeys are appended after the 15 outbox keys and DomainArguments after the two append arguments. Trusted Lua snippets address them with DOMAIN_KEY_OFFSET and DOMAIN_ARG_OFFSET.
ValidateLua must perform every domain precondition and type check without writing. ApplyLua runs only after both domain and outbox duplicate validation have succeeded. It must use only commands whose runtime types were validated; Redis does not roll back writes when a later command raises a runtime error. Both snippets are deployment code, never untrusted request input.
type RedisConfig ¶
type RedisConfig struct {
Namespace string
DuplicateMode DuplicateMode
DefaultMaxAttempts int
MaxLeaseDuration time.Duration
// MaxAppendEncodedBytes bounds the complete JSON batch evaluated by Lua.
MaxAppendEncodedBytes int
// MaxClaimResponseBytes bounds record data returned by one claim script.
MaxClaimResponseBytes int
Limits Limits
DurabilityMode RedisDurabilityMode
RequireNoEviction bool
// AllowUnsafeEviction must be set deliberately to permit construction when
// maxmemory-policy is not required to be noeviction.
AllowUnsafeEviction bool
}
RedisConfig selects the same-slot namespace, resource limits, and required durability and eviction posture.
func DefaultRedisConfig ¶
func DefaultRedisConfig() RedisConfig
DefaultRedisConfig requires noeviction and warns when AOF is unavailable.
type RedisDurabilityMode ¶
type RedisDurabilityMode string
RedisDurabilityMode controls startup behavior when persistence cannot meet policy.
const ( RedisDurabilityUnchecked RedisDurabilityMode = "unchecked" RedisDurabilityWarn RedisDurabilityMode = "warn" RedisDurabilityRequireAOF RedisDurabilityMode = "require_aof" )
type RedisDurabilityReport ¶
type RedisDurabilityReport struct {
Checked bool
AOFEnabled bool
AOFFsync string
AOFLastWriteOK bool
EvictionPolicy string
Role string
Warnings []string
}
RedisDurabilityReport is a copied snapshot of inspected Redis settings and status.
type RedisKeys ¶
type RedisKeys struct {
// contains filtered or unexported fields
}
RedisKeys centralizes every Redis key. A namespace is also the Redis Cluster hash tag, so every atomic transition remains in exactly one hash slot.
func NewRedisKeys ¶
func (RedisKeys) Idempotency ¶
func (RedisKeys) PendingDestinations ¶
func (RedisKeys) QueryCancelled ¶
func (RedisKeys) QueryDelivered ¶
func (RedisKeys) QueryDestinations ¶
func (RedisKeys) QueryLeased ¶
func (RedisKeys) QueryPending ¶
func (RedisKeys) RecordKey ¶
RecordKey exposes the collision-safe conventional per-record key for domain composition tooling. The first-party representation keeps record blobs in a shared hash so Lua scripts access only explicitly declared keys.
func (RedisKeys) ScriptKeys ¶
type RedisStore ¶
type RedisStore struct {
// contains filtered or unexported fields
}
RedisStore implements the portable contracts with an application-owned client.
func NewRedisStore ¶
func NewRedisStore(ctx context.Context, client goredis.UniversalClient, config RedisConfig) (*RedisStore, error)
NewRedisStore validates dependencies, pings Redis, and performs startup durability checks.
func NewRedisStoreFromService ¶
func NewRedisStoreFromService(ctx context.Context, service *reactredis.Service, config RedisConfig) (*RedisStore, error)
NewRedisStoreFromService constructs a store over React's application-owned Redis client. Closing the returned facade never closes the service client.
func (*RedisStore) Acknowledge ¶
func (store *RedisStore) Acknowledge(ctx context.Context, lease LeaseRef, _ DeliveryResult) error
func (*RedisStore) AppendBatch ¶
func (store *RedisStore) AppendBatch(ctx context.Context, request AppendRequest) ([]Record, error)
func (*RedisStore) CheckDurability ¶
func (store *RedisStore) CheckDurability(ctx context.Context) (report RedisDurabilityReport, resultErr error)
CheckDurability refreshes persistence, eviction, and replication observations.
func (*RedisStore) Claim ¶
func (store *RedisStore) Claim(ctx context.Context, request ClaimRequest) ([]Record, error)
func (*RedisStore) Close ¶
func (store *RedisStore) Close() error
Close marks the facade closed without closing the application-owned client.
func (*RedisStore) Compose ¶
func (store *RedisStore) Compose(ctx context.Context, request RedisCompositionRequest) ([]Record, error)
Compose performs one atomic Redis-backed domain mutation and append. It does not make mutations in PostgreSQL or another Redis hash slot atomic.
func (*RedisStore) DeadLetter ¶
func (*RedisStore) Keys ¶
func (store *RedisStore) Keys() RedisKeys
func (*RedisStore) LastDurabilityReport ¶
func (store *RedisStore) LastDurabilityReport() RedisDurabilityReport
LastDurabilityReport returns a copy of the most recent startup or health durability inspection.
func (*RedisStore) Purge ¶
func (store *RedisStore) Purge(ctx context.Context, request PurgeRequest) (int, error)
func (*RedisStore) Requeue ¶
func (store *RedisStore) Requeue(ctx context.Context, id ID, options RequeueOptions) error
func (*RedisStore) Reschedule ¶
func (*RedisStore) Retry ¶
func (store *RedisStore) Retry(ctx context.Context, lease LeaseRef, retry RetryRequest) error
type RedisStoreService ¶
type RedisStoreService struct {
*RedisStore
Logger react.ILogger
}
RedisStoreService is the module-managed Redis store facade. The application retains ownership of the injected Redis service and client.
func NewRedisStoreService ¶
func NewRedisStoreService(injections gioc.Injections) (*RedisStoreService, error)
NewRedisStoreService resolves React's Redis service, the application context, and RedisConfig from OutboxRedisConfigToken.
func (*RedisStoreService) Close ¶
func (service *RedisStoreService) Close() error
Close closes the adapter facade and logs an unexpected failure.
type RequeueOptions ¶
type RequeueOptions struct {
AvailableAt time.Time
ResetAttempts bool
// MaxAttempts optionally replaces the existing limit. Zero preserves it.
// When preserved attempts have reached that limit, requeue requires either
// ResetAttempts or a larger valid MaxAttempts value.
MaxAttempts int
}
RequeueOptions controls replay of a dead record.
type RetryRequest ¶
RetryRequest persists one computed retry schedule and failure.
type Service ¶
type Service struct {
IStore
ApplicationService *react.ApplicationService
Logger react.ILogger
// contains filtered or unexported fields
}
Service is the outbox boundary exposed to applications. It provides the storage capabilities, owns all destination routing, and runs one bounded worker pool for every registered sink.
func NewService ¶
func NewService(injections gioc.Injections) (*Service, error)
NewService resolves the store, delivery capability, worker configuration, application lifecycle, and logger. It starts the service-owned worker pool; consumers then declare routes with Register.
func (*Service) Append ¶
Append delegates to the selected store and wakes the worker pool after a successful append. Transaction-bound adapter appends remain visible through the bounded polling interval after their outer transaction commits.
func (*Service) AppendBatch ¶
AppendBatch delegates the atomic batch and wakes the worker pool after a successful append.
func (*Service) Destinations ¶
Destinations returns a stable copy of the registered routing keys.
func (*Service) Done ¶
func (service *Service) Done() <-chan struct{}
Done closes after the worker pool and bounded lease cleanup stop.
func (*Service) Register ¶
func (service *Service) Register(sink ISink, config DestinationsConfig) error
Register atomically routes every configured destination to sink. A destination can belong to only one sink, which makes routing deterministic. Registration is safe while the worker pool is running.
type SortDirection ¶
type SortDirection string
SortDirection selects ascending or descending keyset traversal.
const ( SortAscending SortDirection = "asc" SortDescending SortDirection = "desc" )
type State ¶
type State string
State is a durable delivery state.
type StoreFeature ¶
type StoreFeature struct {
// contains filtered or unexported fields
}
StoreFeature is an immutable storage adapter description selected by ForFeature.
func DefineStoreFeature ¶
func DefineStoreFeature(name string, provider StoreProviderFactory) StoreFeature
DefineStoreFeature creates a feature descriptor without global mutable registration or init side effects.
func (StoreFeature) Name ¶
func (feature StoreFeature) Name() string
Name returns the adapter's stable feature name.
type StoreProviderFactory ¶
StoreProviderFactory creates a fresh provider for an adapter store service at the requested aggregate-store token. Applications normally select one of the first-party feature values.
type TerminalError ¶
type TerminalError struct{ Err error }
TerminalError lets a sink explicitly identify an error that retrying cannot fix. All unclassified and ambiguous errors are retried for at-least-once delivery.
func (*TerminalError) Error ¶
func (e *TerminalError) Error() string
func (*TerminalError) Unwrap ¶
func (e *TerminalError) Unwrap() error
type Tokens ¶
type Tokens struct {
Service gioc.Token
Store gioc.Token
Appender gioc.Token
DeliveryStore gioc.Token
Reader gioc.Token
MaintenanceStore gioc.Token
}
Tokens gives every named outbox distinct dependency-injection identities. No package-level registry is used.
func ModuleTokens ¶
func ModuleTokens() Tokens
ModuleTokens returns the fixed service and capability tokens exposed by ForFeature.
Source Files
¶
- outbox.capability.provider.go
- outbox.config.go
- outbox.delivery.policy.go
- outbox.delivery.types.go
- outbox.errors.go
- outbox.health.go
- outbox.interfaces.go
- outbox.module.go
- outbox.postgres.config.go
- outbox.postgres.interfaces.go
- outbox.postgres.migration.go
- outbox.postgres.store.appender.go
- outbox.postgres.store.delivery.go
- outbox.postgres.store.go
- outbox.postgres.store.provider.go
- outbox.postgres.store.query.go
- outbox.postgres.store.service.go
- outbox.query.types.go
- outbox.redis.config.go
- outbox.redis.store.appender.go
- outbox.redis.store.composition.go
- outbox.redis.store.delivery.go
- outbox.redis.store.durability.go
- outbox.redis.store.go
- outbox.redis.store.keys.go
- outbox.redis.store.provider.go
- outbox.redis.store.query.go
- outbox.redis.store.scripts.go
- outbox.redis.store.service.go
- outbox.retry.go
- outbox.service.go
- outbox.token.go
- outbox.types.go