One event. Many handlers.
Fan out to independent handlers. One handler’s error doesn’t keep the others from doing their work.
dispatcher.Register(...)Route with rules. Remember with cache.
Let Jev handle the ambiguous.
Keep execution in Go.
“The property at 1 Main St
was sold for $500,000.”
property.soldYour rules run first. Your cache comes next.
Only the unresolved reaches the model.
Choose the event before you enqueue it. Then let a bounded MPMC ring buffer and Go handlers do what they do best.
Already know the event type? Call Subscribe directly. The decision layer is entirely optional.
store.Subscribe(ctx, event)store.Publish()dispatch pending eventsFrom local events to concurrent pipelines.
The execution layer stays under your control.
Fan out to independent handlers. One handler’s error doesn’t keep the others from doing their work.
dispatcher.Register(...)Preserve FIFO delivery per ordering key in async mode, while unrelated entities run concurrently.
store.RegisterOrdered(...)Collect pending events into chunks during a publish cycle. A natural fit for bulk writes and metrics.
store.RegisterBatch(...)Choose what happens when the buffer fills: drop the oldest event, wait for capacity, or return an error.
DropOldest · Block · ReturnErrorAdd middleware and lifecycle hooks. Capture handler failures and recovered panics in a replayable dead-letter queue.
store.DLQ.Replay(ctx, store)Bring events in with Redis Streams or RabbitMQ providers. Keep the local EventStore as your execution engine.
store.Consume(ctx, provider)Low latency. Predictable execution.
A Go event bus for real systems.
3.91 ns/op
PublishIntelligent routing stays outside
the core dispatch benchmark.
| BENCHMARK RESULTS | LATENCY (ns/op) | ||
|---|---|---|---|
| BenchmarkPublish | 3.91 ns/op | ||
| BenchmarkSubscribeParallel | 38.42 ns/op | ||
| BenchmarkSubscribe | 40.37 ns/op | ||
| BenchmarkFastHTTPSync | 163.8 ns/op | ||
| EVENT STORE (SYNC + ASYNC) | |||
| BenchmarkEventStore_Sync | 428.5 ns/op | ||
| BenchmarkEventStore_Async | 436.5 ns/op | ||
LOWER IS BETTER BARS: 0–500 ns/op
| Benchmark | Iterations | ns/op |
|---|---|---|
| BenchmarkSubscribe | 27,080,376 | 40.37 |
| BenchmarkSubscribeParallel | 26,418,999 | 38.42 |
| BenchmarkPublish | 295,661,464 | 3.91 |
| BenchmarkPublishAfterPrefill | 252,943,526 | 4.59 |
| BenchmarkSubscribeLargePayload | 1,613,017 | 771.5 |
| BenchmarkPublishLargePayload | 296,434,225 | 3.91 |
| BenchmarkEventStore_Async | 2,816,988 | 436.5 |
| BenchmarkEventStore_Sync | 2,638,519 | 428.5 |
| BenchmarkFastHTTPSync | 6,275,112 | 163.8 |
| BenchmarkFastHTTPAsync | 1,954,884 | 662.0 |
| BenchmarkFastHTTPParallel | 4,489,274 | 262.3 |
Benchmarks run on Apple M-seriesgo test -bench . -benchtime=3s
Rules, cache, and Jev have different latency profiles and are intentionally not part of these core dispatch benchmarks.
Register a handler. Enqueue an event. Publish.
Bring in Jev when your input needs a decision.
go get github.com/Protocol-Lattice/GoEventBusMake it yours.
Use string or typed projections for local dispatch.
Keep the choice bounded.
Define the candidates. Jev selects between them.
Bring your own control.
Plug in your selectors and cache implementations.
package main
import (
"context"
"fmt"
"log"
bus "github.com/Protocol-Lattice/GoEventBus"
)
func main() {
ctx := context.Background()
dispatcher := bus.Dispatcher{}
dispatcher.Register("order.created", func(
_ context.Context, ev bus.Event,
) (bus.Result, error) {
fmt.Println("Received:", ev.Data)
return bus.Result{}, nil
})
store := bus.NewEventStore(
&dispatcher, 1024, bus.Block,
)
defer store.Close(ctx)
if err := store.Subscribe(ctx, bus.Event{
ID: "evt-1",
Projection: "order.created",
Data: "Hello, GoEventBus",
}); err != nil {
log.Fatal(err)
}
store.Publish()
}No. Use Subscribe and Publish for a conventional event bus. You can add deterministic rules, a decision cache, or a Jev fallback whenever your workload needs them.
With RuleCacheSelector, rules are evaluated first. If none match, the cache is checked. The fallback selector runs only when neither resolves the decision. Successful fallback decisions can then be cached.
JevSelector calls the OpenRouter Decisions API using the Go standard library. Set OPENROUTER_API_KEY in your server environment and supply a bounded list of event candidates. It returns the selected choice, confidence, and probabilities. Read the integration guide.
Yes. Call SelectEvent, check the returned decision, and then enqueue it with Subscribe. The convenience method DecideAndSubscribe selects and enqueues immediately; it doesn’t apply a confidence threshold for you.
Yes. Redis Streams and RabbitMQ providers move events between processes, while the local EventStore runs your handlers. Remote providers need string projections for stable broker routing names.
Keep the fast path fast.
Give the uncertain a way forward.