Overview
Bridging in-process events to distributed systems typically means writing broker-specific code, managing serialization, and wiring up error handling—repeatedly, for each broker.
Herald provides a unified abstraction: publish capitan events to any broker, subscribe from any broker to capitan events.
// Publish capitan events to Kafka
pub := herald.NewPublisher(kafkaProvider, orderCreated, orderKey, nil)
pub.Start()
// Subscribe from Redis to capitan events
sub := herald.NewSubscriber(redisProvider, paymentReceived, paymentKey, nil)
sub.Start(ctx)
Type-safe, bidirectional, with 12 providers out of the box.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Your Application │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Service │──Emit───────▶│ Capitan │ │
│ │ A │ │ (events) │ │
│ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ Publisher │ │
│ │ (herald) │ │
│ └───────┬───────┘ │
└──────────────────────────────────────┼──────────────────────────┘
│
┌───────▼───────┐
│ Provider │
│ (kafka/redis/ │
│ nats/sqs...) │
└───────┬───────┘
│
┌───────▼───────┐
│ Broker │
│ (external) │
└───────────────┘
Publishers observe capitan signals and forward events to brokers. Subscribers consume from brokers and emit to capitan. The provider abstraction handles broker-specific details.
Philosophy
Herald extends capitan's unified event stream to distributed systems. Just as capitan enables cross-cutting concerns within a process, herald enables cross-cutting concerns across processes and services.
// Service A: Emit order events locally
capitan.Emit(ctx, orderCreated, orderKey.Field(order))
// Herald: Bridge to Kafka
pub := herald.NewPublisher(kafkaProvider, orderCreated, orderKey, nil)
// Service B: Receive from Kafka via herald
sub := herald.NewSubscriber(kafkaProvider, orderCreated, orderKey, nil)
// Service B: Handle locally
capitan.Hook(orderCreated, processOrder)
Three services, one event contract, zero direct dependencies between them.
Capabilities
Herald bridges capitan to external systems:
Publishing — Forward capitan events to any supported broker with automatic serialization and metadata propagation.
Subscribing — Consume broker messages and emit them as capitan events, with automatic deserialization and acknowledgment.
Providers — 11 built-in providers for Kafka, NATS, JetStream, Pub/Sub, Redis, SQS, RabbitMQ, SNS, BoltDB, Firestore, and io.
Reliability — Pipeline options for retry, backoff, timeout, circuit breaker, and rate limiting.
Herald handles the bridging. What you build across your distributed system is up to you.
Priorities
Type Safety
Messages are typed at compile time through capitan keys:
type Order struct {
ID string `json:"id"`
Total float64 `json:"total"`
}
var orderKey = capitan.NewKey[Order]("order", "app.Order")
// Type-safe publishing
pub := herald.NewPublisher[Order](provider, signal, orderKey, nil)
// Type-safe subscribing
sub := herald.NewSubscriber[Order](provider, signal, orderKey, nil)
No runtime type assertions, no interface{} juggling.
Reliability
Events flow through composable pipelines:
pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{
herald.WithRetry[Order](3),
herald.WithBackoff[Order](3, 100*time.Millisecond),
herald.WithTimeout[Order](5*time.Second),
herald.WithCircuitBreaker[Order](5, 30*time.Second),
})
Failed publishes can retry with backoff. Circuit breakers prevent cascade failures. Rate limiters protect downstream systems.
Observability
All errors flow through capitan's event system:
capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {
err, _ := herald.ErrorKey.From(e)
log.Printf("[herald] %s failed: %v", err.Operation, err.Err)
})
Monitor publishing failures, deserialization errors, and acknowledgment issues through the same event infrastructure you use for everything else.