loafer-awsx

module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2026 License: MIT

README

loafer-awsx

Go Reference CI Go Version License

A modern, idiomatic Go library for AWS SQS/SNS message processing, built on aws-sdk-go-v2 with generic type-safe handlers, a composable middleware pipeline, first-class log/slog logging, and built-in Prometheus and OpenTelemetry observability.

loafer-awsx organizes message processing into small, single-responsibility packages you can compose: build an AWS connection, declare routes, wrap them in a broker, and publish events with a producer. Everything is configured through functional options, and every component accepts the standard library *slog.Logger directly, no custom logger interface.

  • Module: github.com/silviolleite/loafer-awsx
  • Minimum Go version: Go 1.26 or later
  • AWS SDK: aws-sdk-go-v2 (SQS + SNS + EventBridge Scheduler)

Table of Contents

  1. Architecture
  2. Installation
  3. Quickstart
  4. Examples
  5. Configuration Reference
  6. Client constructors
  7. IAM permissions
  8. Scheduled Retry (FIFO)
  9. Benchmarks
  10. Acknowledgements
  11. License

Architecture

loafer-awsx is a library, not a service. Your application imports it, wires routes and a broker, and processes messages from AWS SQS while optionally publishing to AWS SNS. The library also exposes Prometheus metrics and OpenTelemetry spans for observability.

Context Diagram
flowchart LR
    dev([Developer])
    loafer[loafer-awsx]
    sqs[(AWS SQS)]
    sns[(AWS SNS)]
    ebs((AWS EventBridge<br/>Scheduler))
    prom[Prometheus]
    otel[OpenTelemetry]

    dev --> loafer
    loafer --> sqs
    loafer --> sns
    loafer -->|FIFO scheduled retry:<br/>create schedule| ebs
    ebs -.->|fire: re-publish retry| sqs
    loafer --> prom
    loafer --> otel
Container Diagram

The broker orchestrates one consumer per route: each Route binds a queue to a handler, and its Consumer runs a worker pool that polls the matching SQS queue. Publishing runs alongside through the producer.

flowchart TB
    app([Application])
    broker[Broker]
    producer[Producer]

    subgraph routeStd[Route - Visibility retry]
        consumerStd[Consumer / Workers]
    end
    subgraph routeFifo[Route - FIFO Scheduled retry]
        consumerFifo[Consumer / Workers]
    end

    sqsStd[(SQS Queue)]
    sqsEntry[(SQS FIFO Entry_Queue)]
    dlq[(SQS FIFO DLQ)]
    sns[(AWS SNS)]
    ebs((AWS EventBridge<br/>Scheduler))

    app --> broker
    app --> producer
    broker --> routeStd
    consumerStd --> sqsStd

    app -->|scheduled route via consumer.New| routeFifo
    consumerFifo -->|poll / delete| sqsEntry
    consumerFifo -->|create one-time schedule| ebs
    ebs -.->|fire: re-publish with retry_count+1| sqsEntry
    consumerFifo -->|exhausted: publish| dlq

    producer --> sns

Cross-cutting packages support this pipeline: conn builds the shared aws.Config, middleware wraps each route handler (global middleware outermost, route middleware innermost), typed adds generic type-safe handlers and producers, idgen generates FIFO IDs, and logger supplies the *slog.Logger used throughout.

Package responsibilities at a glance:

Package Responsibility
conn Factory for an aws.Config (region, credentials, endpoint, profile, retry).
client Constructors that turn an aws.Config into SQS/SNS/Scheduler clients with construction-time connectivity validation.
logger Constructors for the standard library *slog.Logger (stdout + no-op).
middleware Handler, Middleware, Chain, and built-in Recovery, Logging, Metrics, OTel.
router Immutable Route value object binding a queue to a handler and options.
consumer SQS polling loop, worker-pool dispatch, visibility management, DLQ observability.
broker Lifecycle orchestrator that runs one consumer per route with graceful shutdown.
producer SNS single and batch publish for standard and FIFO topics.
typed Generic, type-safe handlers and producers via Codec[T].
idgen MessageGroupId / MessageDeduplicationId generation strategies.
errors Sentinel errors matchable with errors.Is.

Installation

Requires Go 1.26 or later.

go get github.com/silviolleite/loafer-awsx

Then import the packages you need, for example:

import (
    "github.com/silviolleite/loafer-awsx/broker"
    "github.com/silviolleite/loafer-awsx/conn"
    "github.com/silviolleite/loafer-awsx/logger"
    "github.com/silviolleite/loafer-awsx/router"
    "github.com/silviolleite/loafer-awsx/producer"
)

Quickstart

A typical setup builds a shared AWS config with conn.New, binds queue names to handlers with router.New, hands the routes to a broker, and calls broker.Run (which blocks until the context is canceled, then drains in-flight messages). Publishing works the same way: create a producer and call Publish or PublishBatch.

For complete, runnable programs covering the consumer, producer, FIFO, typed, and middleware setups, see the examples/ directory and its README.


Examples

Runnable, self-contained programs live in the examples/ directory, wired to run locally against LocalStack with infrastructure provisioned by Terraform. See examples/README.md for setup and run instructions (make up, make provision, make run-basic, and friends).

Example Directory What it shows
Basic examples/basic/ Standard SQS queue consumption with a simple handler.
FIFO examples/fifo/ Ordered consumption in PerGroupID mode with custom group fields.
Typed examples/typed/ Generic type-safe handling via typed.WrapHandler + typed.JSONCodec.
Middleware examples/middleware/ Recovery, logging, Prometheus metrics, and OpenTelemetry tracing.
Producer examples/producer/ Single and batch publishing to standard and FIFO SNS topics.

Configuration Reference

Every component is configured through functional options following the same pattern: pass With* options to each package's New constructor, which validates them and rejects invalid values at construction time with a descriptive error rather than accepting them silently. Option failures wrap errors.ErrInvalidOption, and each package exposes its own sentinel errors (matchable with errors.Is) for missing required inputs.

For the full, always-current list of options, signatures, defaults, and sentinel errors for every package, see the Go Reference linked by the badge at the top of this README. The notes below cover the conceptual behaviors that are easy to miss from signatures alone.

  • conn builds the shared aws.Config (region, credentials, endpoint, profile, retry). Region is required.
  • router declares an immutable Route binding a queue to a handler, with options for worker-pool size, receive batching, long-poll wait, visibility timeout and extension limit, run mode, route middleware, and DLQ observability.
  • consumer and broker run the polling loop and orchestrate one consumer per route; both accept a *slog.Logger, a retry timeout, and middleware, and the broker adds a shutdown timeout.
  • producer publishes to SNS (single and batch), with optional auto-generation of FIFO IDs.
  • typed, idgen, middleware, logger, and errors provide generic type-safe handlers/producers, FIFO ID generation strategies, the middleware primitives and built-ins, *slog.Logger constructors, and the sentinel error set respectively.

Run modes (router.Mode): Parallel assigns messages to workers randomly; PerGroupID hashes the MessageGroupId plus any custom group fields so a group's messages are handled in order.

Middleware ordering: broker-level (global) middleware is applied outermost and route-level middleware innermost (closest to the handler).

DLQ observability (router.WithDLQ) is observe-only. It does not take a target ARN, and the library never moves, publishes, or deletes messages for DLQ purposes. AWS SQS performs the actual redrive natively via the source queue's redrive policy. The maxReceiveCount you pass must mirror that policy; it is used only to detect when a message is exhausted so the consumer can emit an Error log, the loafer_messages_dlq_total metric, and the optional OnDLQ callback, while leaving the message in the queue.

Logging: the library uses the concrete *slog.Logger type everywhere and defines no custom logger interface. This works without any adapter because the extension point in slog is not the *slog.Logger type but the slog.Handler interface it wraps. A *slog.Logger is just a thin struct that delegates every record to its slog.Handler, and you build one with slog.New(handler). Any handler that implements slog.Handler therefore plugs in directly:

import (
    "log/slog"

    "go.uber.org/zap"
    "go.uber.org/zap/exp/zapslog" // zap's official slog bridge
)

zapLogger, _ := zap.NewProduction()
handler := zapslog.NewHandler(zapLogger.Core()) // implements slog.Handler
log := slog.New(handler)                        // -> *slog.Logger

broker.WithLogger(log) // accepted directly, no adapter

For backends without an official slog bridge (for example zerolog), any third-party or hand-written slog.Handler works the same way. The translation to the underlying backend happens inside the handler, so the library never needs a logger adapter of its own.


Client constructors

The client package turns an aws.Config (produced by conn.New) into the service clients the rest of the library consumes, so your application never has to import the AWS SDK for Go v2 service packages (sqs, sns, scheduler) directly:

  • client.NewSQS(ctx, cfg, opts...) returns a client for the broker and consumer.
  • client.NewSNS(ctx, cfg, opts...) returns a client for the producer.
  • client.NewScheduler(ctx, cfg, opts...) returns a client for the Scheduled Retry path (wired through consumer.WithSchedulerClient).

Each constructor validates connectivity during construction: before returning, it issues a lightweight, read-only request (the "Ping") to confirm the client can reach its AWS service with valid credentials, failing fast if it cannot. The validation uses a dedicated timeout and retry budget that are independent of the request retry policy carried by the aws.Config (defaults: 3s timeout, 2 retries).

Three functional options tune this behavior:

  • WithPingTimeout(d) overrides the total time budget for connectivity validation (including retries). The duration must be positive.
  • WithPingRetryLimit(n) overrides the number of retries performed beyond the initial attempt.
  • WithoutConnectivityCheck() disables the connectivity validation entirely. Use it when the credentials lack the read-only permission the Ping requires, or to construct a client offline.

The existing Go Reference badge at the top of this README covers the full signatures, defaults, and sentinel errors.


IAM permissions

Each constructor's connectivity validation (Ping) issues an additional read-only request beyond the operations the client uses at runtime, so the caller's credentials need the Ping permission too — unless the check is disabled with WithoutConnectivityCheck(). The tables below list the complete set of permissions each client requires.

SQS client (client.NewSQS, used by broker and consumer)
Action Required by Notes
sqs:ReceiveMessage Runtime (consumer poll) On the Entry_Queue.
sqs:DeleteMessage Runtime On the Entry_Queue.
sqs:ChangeMessageVisibility Runtime Visibility extension during processing.
sqs:GetQueueUrl Runtime Resolve the queue URL from its name.
sqs:SendMessage Runtime (Scheduled Retry only) On the DLQ, to publish exhausted messages.
sqs:ListQueues Construction (Ping) Account-level; omit only if the check is disabled.
SNS client (client.NewSNS, used by producer)
Action Required by Notes
sns:Publish Runtime Covers both Publish and PublishBatch, on the target topic(s).
sns:ListTopics Construction (Ping) Account-level; omit only if the check is disabled.
EventBridge Scheduler client (client.NewScheduler, used by consumer Scheduled Retry)
Action Required by Notes
scheduler:CreateSchedule Runtime Create the one-time retry schedule.
iam:PassRole Runtime On the execution role passed via WithSchedulerIdentity.
scheduler:ListSchedules Construction (Ping) Omit only if the check is disabled.

The execution role assumed by EventBridge Scheduler (the second argument to WithSchedulerIdentity) is separate from the caller's credentials and needs sqs:SendMessage on the Entry_Queue plus a trust policy allowing scheduler.amazonaws.com to assume it.

Because the Ping uses account-level List* permissions that scoped credentials may not grant, callers with least-privilege policies can either add the List* action or construct with WithoutConnectivityCheck().


Scheduled Retry (FIFO)

The FIFO consumption path supports two per-route retry models, selected with router.WithRetryModel (or the router.WithScheduledRetry shortcut):

Model Constant Behavior
Visibility (default) router.VisibilityRetryModel A failed message stays in the queue and its visibility timeout is extended until it succeeds or AWS SQS redrives it natively. This blocks the MessageGroupId until the message resolves.
Scheduled router.ScheduledRetryModel The consumer owns the whole retry lifecycle: on failure it schedules a delayed re-publish through AWS EventBridge Scheduler and deletes the original message so the MessageGroupId is unblocked immediately.

When no retry model is configured a route uses VisibilityRetryModel, so existing routes are unchanged. Selecting the Scheduled model on one route never affects routes that use the Visibility model, and no scheduler client is constructed or required unless a route opts in.

Under the Scheduled model, when a handler fails (returns an error or requests backoff) the consumer reads a retry_count message attribute (default 0), computes next = current + 1, and either:

  • Schedules a retry when next <= MaxRetryCount: it creates a one-time EventBridge Scheduler schedule that re-publishes the message to the queue after the computed backoff, then deletes the original.
  • Publishes to the DLQ when next > MaxRetryCount: it sends the message to the configured DLQ, then deletes the original.

On success the message is simply deleted. The library performs no success-side publishing; whether success means publishing to a topic, calling an API, or doing nothing is the handler's responsibility.

Architecture
graph TD
    classDef aws fill:#FF9900,stroke:#232F3E,stroke-width:2px,color:#232F3E;
    classDef compute fill:#232F3E,stroke:#FF9900,stroke-width:2px,color:#FFFFFF;
    classDef queue fill:#E2E3E5,stroke:#6C757D,stroke-width:2px,color:#232F3E;
    classDef dlq fill:#F8D7DA,stroke:#DC3545,stroke-width:2px,color:#721C24;
    classDef action fill:#D1E7DD,stroke:#0F5132,stroke-width:2px,color:#0F5132;

    PROD[Producer service<br/>e.g. Checkout]:::compute
    SNS{{SNS FIFO topic<br/>order_created.fifo}}:::aws
    SQS[(Entry_Queue &mdash; SQS FIFO<br/>inventory_order_created.fifo)]:::queue
    DLQ[(DLQ &mdash; SQS FIFO<br/>inventory_order_created_dlq.fifo)]:::dlq
    WORKER[Consumer service<br/>loafer-awsx worker]:::compute
    EBS((Amazon EventBridge<br/>Scheduler)):::aws
    DEL{{Delete from Entry_Queue<br/>frees the MessageGroupId}}:::action

    PROD -->|1. Publish event| SNS
    SNS -->|2. Route, raw delivery| SQS
    SQS -->|3. Poll / read batch| WORKER

    WORKER -->|4a. Success| DEL

    WORKER -->|4b. Transient error:<br/>compute backoff, create schedule,<br/>retry_count + 1| EBS
    EBS -.->|5. Fire time reached:<br/>re-publish to the queue| SQS
    WORKER -.->|Delete original now<br/>to free the MessageGroupId| DEL

    WORKER -->|4c. retry_count &gt; MaxRetryCount:<br/>publish directly to the DLQ| DLQ

Why this architecture. A FIFO queue guarantees ordering within a MessageGroupId by delivering the group's messages one at a time. That guarantee turns a single poison or transiently failing message into a head-of-line block: under the default Visibility model the failed message stays in the queue and its visibility timeout is extended, so every later message sharing its group waits behind it until it finally succeeds or SQS redrives it. For a busy group, one bad message can stall a whole stream of otherwise healthy work.

The Scheduled Retry model breaks that coupling by moving the wait out of the queue. On failure the consumer hands the retry to EventBridge Scheduler (step 4b) and immediately deletes the original message (step 5, the dashed delete-to-free edge). The MessageGroupId is unblocked right away, so the next message in the group is processed while the failed one waits — off-queue — for its backoff to elapse. When the schedule fires, EventBridge Scheduler re-publishes the message to the same Entry_Queue with an incremented retry_count, and the cycle repeats until the message either succeeds or exceeds MaxRetryCount and is routed straight to the DLQ (step 4c).

Why it is efficient.

  • Group liveness: a failing message no longer blocks its group. Throughput of a group is bounded by its healthy messages, not by its slowest failure.
  • No worker is held during backoff: the delay lives in EventBridge Scheduler, not in a sleeping goroutine or an extended visibility timeout, so worker slots and in-flight-message limits are not consumed while waiting to retry.
  • Backoff without polling churn: exponential backoff is expressed as a one-time schedule fire time, so the queue is not repeatedly re-reading and re-hiding the same message across attempts.
  • Deterministic, consumer-owned dead-lettering: the DLQ decision is driven by the retry_count carried on the message and the configured MaxRetryCount, rather than SQS maxReceiveCount redrive, giving you explicit control over when a message is dead-lettered and what metadata it carries.
  • Self-cleaning schedules: each retry schedule is created with ActionAfterCompletion = DELETE, so it removes itself after its single invocation and no schedule resources accumulate.

Accepted tradeoffs. Because the original is deleted before the retry is delivered, the model provides at-least-once delivery (a delete failure after a successful schedule/DLQ publish leaves the original for redelivery), and strict ordering within a MessageGroupId is not preserved for messages that are retried — the retried message rejoins the queue later, after messages that were behind it. Design handlers to be idempotent. These tradeoffs are the deliberate price paid for group liveness.

Router configuration

router.WithRetryModel(m router.RetryModel) sets the model explicitly and rejects any value other than VisibilityRetryModel or ScheduledRetryModel. router.WithScheduledRetry(opts ...router.ScheduledRetryOption) is the usual entry point: it sets the model to Scheduled and attaches a validated configuration assembled from its sub-options.

Sub-option Signature Description
WithSchedulerIdentity WithSchedulerIdentity(targetQueueARN, executionRoleARN string) Required. The EventBridge Scheduler target (Entry_Queue) ARN and the execution role ARN the scheduler assumes. A missing item is named individually in the error.
WithScheduledDLQ WithScheduledDLQ(dlqQueueURL string) Required. The DLQ destination queue URL for exhausted messages.
WithMaxRetryCount WithMaxRetryCount(n int) Inclusive threshold before DLQ routing. Must be within [0, 2147483647].
WithBackoff WithBackoff(base, max time.Duration) Base and maximum backoff delay. Each must be within [1ms, 24h] and max >= base. Base defaults to 1000ms when unset.

All Scheduled-model configuration is validated at router.New time. An invalid or incomplete configuration returns an error wrapping errors.ErrScheduledRetryConfig that identifies the offending value, so a misconfigured route is never built and consumption never starts for it. Configuring both WithScheduledRetry and the observe-only WithDLQ on the same route is a configuration error, regardless of option order.

Consumer wiring

The broker does not forward the scheduler client or the metrics recorder to the consumers it creates. Wire a Scheduled-model route through consumer.New directly and run it yourself.

consumer.WithSchedulerClient(consumer.SchedulerClient) supplies the EventBridge Scheduler client. A concrete *scheduler.Client from github.com/aws/aws-sdk-go-v2/service/scheduler satisfies the interface directly. A Scheduled-model route given to a consumer without a scheduler client fails fast at Run with errors.ErrNoSchedulerClient and never begins consuming.

consumer.WithMetrics(consumer.MetricsRecorder) wires a single recorder whose methods report each outcome, labeled by route name. The whole recorder is no-op when nil, and a panicking method is recovered so the message outcome always completes:

Method Emitted when
IncSuccess(routeName string) A handler succeeds and the original message is deleted.
IncRetry(routeName string) A retry schedule is created successfully.
IncDeadLetter(routeName string) An exhausted message is published to the DLQ successfully.
IncDLQ(routeName string) Under the Visibility model, a message is observed as exhausted (observe-only DLQ).
Example
package main

import (
    "context"
    "errors"
    "log/slog"
    "time"

    "github.com/aws/aws-sdk-go-v2/service/scheduler"
    "github.com/aws/aws-sdk-go-v2/service/sqs"

    "github.com/silviolleite/loafer-awsx/conn"
    "github.com/silviolleite/loafer-awsx/consumer"
    "github.com/silviolleite/loafer-awsx/logger"
    "github.com/silviolleite/loafer-awsx/middleware"
    "github.com/silviolleite/loafer-awsx/router"
)

func main() {
    ctx := context.Background()
    log := logger.New()

    cfg, err := conn.New(ctx, conn.WithRegion("us-east-1"))
    if err != nil {
        log.Error("failed to build AWS config", slog.Any("error", err))
        return
    }

    sqsClient := sqs.NewFromConfig(cfg)
    schedulerClient := scheduler.NewFromConfig(cfg)

    handler := func(ctx context.Context, msg middleware.Message) error {
        // Return an error (or call msg.Backoff) to exercise the scheduled-retry path.
        return errors.New("transient failure")
    }

    route, err := router.New("orders.fifo", handler,
        router.WithRunMode(router.PerGroupID),
        router.WithScheduledRetry(
            router.WithSchedulerIdentity(
                "arn:aws:sqs:us-east-1:000000000000:orders.fifo",       // target Entry_Queue ARN
                "arn:aws:iam::000000000000:role/loafer-scheduler-role", // execution role ARN
            ),
            router.WithScheduledDLQ("https://sqs.us-east-1.amazonaws.com/000000000000/orders-dlq.fifo"),
            router.WithMaxRetryCount(5),
            router.WithBackoff(1*time.Second, 15*time.Minute),
        ),
    )
    if err != nil {
        log.Error("failed to build route", slog.Any("error", err))
        return
    }

    // The Scheduled model is wired through consumer.New directly, not broker.New:
    // the scheduler client and metrics recorder are consumer options.
    c, err := consumer.New(sqsClient, route,
        consumer.WithLogger(log),
        consumer.WithSchedulerClient(schedulerClient),
        consumer.WithMetrics(logMetrics{log: log}),
    )
    if err != nil {
        log.Error("failed to build consumer", slog.Any("error", err))
        return
    }

    if err := c.Run(ctx); err != nil {
        log.Error("consumer stopped", slog.Any("error", err))
    }
}

// logMetrics is a consumer.MetricsRecorder that logs each outcome. A production
// implementation would back these methods with the counters registered by the
// Metrics middleware.
type logMetrics struct{ log *slog.Logger }

func (m logMetrics) IncDLQ(route string)        { m.log.Info("dlq", slog.String("route", route)) }
func (m logMetrics) IncSuccess(route string)    { m.log.Info("success", slog.String("route", route)) }
func (m logMetrics) IncRetry(route string)      { m.log.Info("retry", slog.String("route", route)) }
func (m logMetrics) IncDeadLetter(route string) { m.log.Info("dead-letter", slog.String("route", route)) }
Required AWS resources and IAM permissions

The Scheduled model creates one-time schedules and publishes to a DLQ, so the identities involved need these permissions:

  • The consumer's credentials need scheduler:CreateSchedule to create retry schedules and iam:PassRole on the execution role passed via WithSchedulerIdentity (EventBridge Scheduler requires the caller to be allowed to pass the role it will assume). They also need sqs:SendMessage to the DLQ so exhausted messages can be published.
  • The execution role (the second argument to WithSchedulerIdentity) is the role EventBridge Scheduler assumes when a schedule fires. It needs sqs:SendMessage to the Entry_Queue so the re-published retry can be delivered, and its trust policy must allow scheduler.amazonaws.com to assume it.

Each one-time schedule is created with ActionAfterCompletion = DELETE and a disabled flexible time window, so EventBridge Scheduler self-cleans the schedule after its single invocation. The library never tracks or reaps schedule resources.

Entry_Queue must use explicit deduplication

A scheduled retry re-publishes the message with an unchanged body but an explicit MessageDeduplicationId distinct from the original. The FIFO Entry_Queue must not rely on content-based deduplication: it must be configured for explicit deduplication (MessageDeduplicationId provided per message). If the queue used content-based deduplication, the re-published retry would be discarded as a duplicate of the original because the body is identical.

Accepted tradeoffs

The Scheduled model deliberately trades two FIFO guarantees for group liveness:

  • At-least-once delivery. The retry (schedule or DLQ publish) is created before the original is deleted. If the delete step fails after a successful schedule or publish, both the original and the re-published copy can be in play. Handlers must be idempotent.
  • In-group ordering is not preserved for retried messages. Because a failed message is deleted and re-published later while the next message in the same MessageGroupId is processed immediately, strict ordering within a group does not hold for messages that are retried.
  • Handler-owned success publishing. On success the library only deletes the message and emits the success metric. Any success-side publishing (to a topic, an API, or elsewhere) is the handler's responsibility.

Benchmarks

The numbers below compare the per-message processing overhead of loafer-awsx with JustCodes/loafer-go for both standard and FIFO (PerGroupID) routing.

Both libraries are driven by the same in-memory SQS client, a no-op handler, and an identical 8-worker pool, so the results isolate library overhead (dispatch, worker routing, visibility bookkeeping) and deliberately exclude AWS and network latency. In production, end-to-end throughput is dominated by SQS round-trips, so treat these figures as a measure of framework cost, not real-world throughput.

Mode Library Time/op Throughput Allocs/op Bytes/op
Standard loafer-awsx ~5.4 µs ~184k msg/s 19 1,175 B
Standard loafer-go ~9.3 µs ~105k msg/s 19 1,245 B
FIFO loafer-awsx ~6.1 µs ~165k msg/s 22 1,518 B
FIFO loafer-go ~9.9 µs ~100k msg/s 22 1,589 B

Medians of -benchtime=2s -count=6 on an Intel Core i5-8265U (Go 1.26, linux/amd64). Absolute numbers are machine-specific; the relative gap is what matters, and both the code and methodology are reproducible.

Relative to loafer-go, on this run:

  • Standard queue: ~41% lower latency, ~70% higher throughput, ~6% less memory per message, and the same number of allocations.
  • FIFO queue: ~38% lower latency, ~62% higher throughput, ~5% less memory per message, and the same number of allocations.

The benchmarks live in their own module under benchmarks/ (kept separate so the competitor dependency never touches the library's go.mod). To reproduce:

cd benchmarks
go test -run '^$' -bench . -benchtime=2s -count=6

Acknowledgements

This project was inspired by JustCodes/loafer-go.


License

See LICENSE.

Directories

Path Synopsis
Package broker provides the top-level orchestrator that creates and manages Consumer instances for multiple routes, offering coordinated startup, graceful shutdown, and fail-fast behavior.
Package broker provides the top-level orchestrator that creates and manages Consumer instances for multiple routes, offering coordinated startup, graceful shutdown, and fail-fast behavior.
Package client provides constructors that build AWS SQS, SNS, and EventBridge Scheduler service clients from an aws.Config produced by the conn package, so that consuming applications do not need to import the AWS SDK for Go v2 service packages directly.
Package client provides constructors that build AWS SQS, SNS, and EventBridge Scheduler service clients from an aws.Config produced by the conn package, so that consuming applications do not need to import the AWS SDK for Go v2 service packages directly.
Package conn provides a factory for AWS SDK v2 configuration.
Package conn provides a factory for AWS SDK v2 configuration.
Package consumer implements the SQS polling loop, worker-pool dispatch, visibility-timeout management, and the message commit/backoff lifecycle for a single queue.
Package consumer implements the SQS polling loop, worker-pool dispatch, visibility-timeout management, and the message commit/backoff lifecycle for a single queue.
Package errors defines the sentinel errors used across loafer-awsx and a Wrap helper that preserves errors.Is matchability.
Package errors defines the sentinel errors used across loafer-awsx and a Wrap helper that preserves errors.Is matchability.
examples
basic command
Command basic demonstrates standard SQS queue consumption with loafer-awsx.
Command basic demonstrates standard SQS queue consumption with loafer-awsx.
fifo command
Command fifo demonstrates ordered consumption from an SQS FIFO queue with loafer-awsx.
Command fifo demonstrates ordered consumption from an SQS FIFO queue with loafer-awsx.
fifo-scheduled-retry command
Command fifo-scheduled-retry demonstrates the Scheduled Retry model for an SQS FIFO queue with loafer-awsx.
Command fifo-scheduled-retry demonstrates the Scheduled Retry model for an SQS FIFO queue with loafer-awsx.
localscheduler command
Command localscheduler is a local development helper that emulates what AWS EventBridge Scheduler does when a one-time schedule fires.
Command localscheduler is a local development helper that emulates what AWS EventBridge Scheduler does when a one-time schedule fires.
middleware command
Command middleware demonstrates observability middleware with loafer-awsx.
Command middleware demonstrates observability middleware with loafer-awsx.
producer command
Command producer demonstrates publishing messages to SNS topics with loafer-awsx.
Command producer demonstrates publishing messages to SNS topics with loafer-awsx.
typed command
Command typed demonstrates strongly-typed message handling with loafer-awsx.
Command typed demonstrates strongly-typed message handling with loafer-awsx.
Package fake provides configurable test doubles for the core loafer-awsx interfaces used across package tests: Message (consumer.Message and middleware.Message), SQSClient (consumer.SQSClient), and SNSClient (producer.SNSClient).
Package fake provides configurable test doubles for the core loafer-awsx interfaces used across package tests: Message (consumer.Message and middleware.Message), SQSClient (consumer.SQSClient), and SNSClient (producer.SNSClient).
Package idgen generates MessageGroupId and MessageDeduplicationId values using deterministic (key-based, composite) or random (UUID) strategies.
Package idgen generates MessageGroupId and MessageDeduplicationId values using deterministic (key-based, composite) or random (UUID) strategies.
Package logger provides constructors for the standard library *slog.Logger used throughout loafer-awsx.
Package logger provides constructors for the standard library *slog.Logger used throughout loafer-awsx.
Package middleware defines the Handler and Middleware types, the Chain combinator, and the built-in middlewares (Recovery, Logging, Metrics, and OpenTelemetry) used to add cross-cutting concerns to message processing.
Package middleware defines the Handler and Middleware types, the Chain combinator, and the built-in middlewares (Recovery, Logging, Metrics, and OpenTelemetry) used to add cross-cutting concerns to message processing.
Package producer publishes messages to AWS SNS topics.
Package producer publishes messages to AWS SNS topics.
Package router defines a Route as the binding between a queue name, a handler, a middleware chain, and route-level configuration.
Package router defines a Route as the binding between a queue name, a handler, a middleware chain, and route-level configuration.
Package typed provides generic, type-safe handlers and producers built on a Codec interface, eliminating manual JSON marshaling boilerplate.
Package typed provides generic, type-safe handlers and producers built on a Codec interface, eliminating manual JSON marshaling boilerplate.

Jump to

Keyboard shortcuts

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