Choosing a Transport
Choosing a Transport
Redelay supports four event transport backends. Module code never changes between them — only the bootstrap wiring differs. This guide helps you pick the right one.
Decision guide
Transport comparison
| Memory | NATS core | NATS JetStream | Redis Streams | Kafka | |
|---|---|---|---|---|---|
| Persistence | ✗ | ✗ | ✓ | ✓ | ✓ |
| Consumer groups | ✓ | ✗ | ✓ | ✓ | ✓ |
| At-least-once | ✓* | ✗ | ✓ | ✓ | ✓ |
| Replay from offset | ✗ | ✗ | ✓ | ✓ | ✓ |
| Cluster support | ✗ | ✓ | ✓ | ✓ | ✓ |
| Infra required | None | NATS | NATS | Redis | Kafka |
| Best for | Tests | Low-latency notifications | Durable; no Kafka | Lightweight teams | High-throughput production |
*Memory at-least-once is within a single process only.
Memory
Use for all unit and integration tests. No infrastructure required.
import (
"github.com/redelay/go-events/eventbus"
"github.com/redelay/go-events/transport/memory"
)
tr := memory.New()
bus := eventbus.New(tr)
Supports full consumer-group semantics: multiple groups each receive every message; within a group, messages are dispatched round-robin across subscribers.
NATS core
Use when you need low-latency, fire-and-forget delivery and can accept that messages published when no subscriber is active are dropped.
import "github.com/redelay/go-events/transport/nats"
// From env (recommended):
t, err := nats.NewFromEnv()
// Explicit:
t, err := nats.New(nats.Config{
URL: "nats://localhost:4222",
UseJetStream: false,
})
# Environment
NATS_URL=nats://localhost:4222
NATS_JETSTREAM=false
NATS JetStream
Use when you want durable delivery, consumer groups, and replay without running Kafka. JetStream is built into the same NATS binary — enable it with one flag.
t, err := nats.New(nats.Config{
URL: "nats://nats:4222",
UseJetStream: true,
AckWait: 30 * time.Second,
MaxDeliver: 3,
})
# Environment
NATS_URL=nats://nats:4222
NATS_JETSTREAM=true
NATS_ACK_WAIT_SECONDS=30
NATS_MAX_DELIVER=3
# Cluster:
NATS_URL=nats://nats1:4222,nats://nats2:4222,nats://nats3:4222
JetStream streams are auto-created by the transport on first subscribe — no manual NATS admin required.
Redis Streams
Use when Redis is already part of your stack (caching, rate limiting, sessions) and throughput is moderate. Avoids running a separate message broker.
t, err := redis.NewFromEnv()
// Explicit:
t, err := redis.New(redis.Config{
Addr: "redis:6379",
BlockDuration: 200 * time.Millisecond,
BatchSize: 10,
MaxLen: 10_000,
})
# Environment — falls back to REDIS_URL if REDIS_STREAMS_URL is unset
REDIS_STREAMS_URL=redis://redis:6379
REDIS_STREAMS_BLOCK_MS=200
REDIS_STREAMS_BATCH_SIZE=10
REDIS_STREAMS_MAX_LEN=10000
Kafka
Use for high-throughput production workloads, long-term retention, or when integrating with an existing Kafka ecosystem (Kafka Connect, ksqlDB, Schema Registry, etc.).
t, err := kafka.NewFromEnv()
// Explicit:
t, err := kafka.New(kafka.Config{
Brokers: []string{"kafka:9092"},
GroupID: "my-service",
})
# Environment — single broker
KAFKA_BOOTSTRAP_SERVERS=kafka:9092
# Cluster with SASL/TLS (Confluent Cloud, MSK, Aiven):
KAFKA_BOOTSTRAP_SERVERS=broker1:9092,broker2:9092,broker3:9092
KAFKA_SECURITY_PROTOCOL=sasl_ssl
KAFKA_SASL_MECHANISM=scram-sha-256
KAFKA_SASL_USERNAME=myuser
KAFKA_SASL_PASSWORD=mysecret
Local development (docker-compose)
The infra/docker-compose.yml starts all four backends. The API service is pre-configured with env vars for each:
environment:
KAFKA_BOOTSTRAP_SERVERS: kafka:29092
NATS_URL: nats://nats:4222
REDIS_STREAMS_URL: redis://redis:6379
Switch transports by changing the bootstrap wiring in cmd/api/main.go — no module code changes required.
Integration tests
Running make test-integration in go-events/ starts each backend in a Docker container via testcontainers-go and runs the same 5 scenarios against every transport to ensure behavioral consistency.
cd go-events && make test-integration
See go-events reference for the full list of test scenarios.
Transport vs coordination — different jobs
Transports deliver business events (user.created, order.paid). They're durable, ordered, and consumer-group aware. Picking Kafka vs NATS vs Redis here is about delivery semantics.
Coordination is a separate surface for cross-container state sync — invalidating caches, fanning out live metrics, watching the currently-published flow route. It lives in go-framework/runtime/coordination with its own COORD env switch (noop / nats / redis) and is independent of the TRANSPORT choice. A common deployment uses Kafka for events and NATS for coordination.
See Coordination reference for when and how to use it.