Bee is a minimal Go library for implementing CQRS & Event-Sourcing using NATS JetStream as the transport and persistence layer. It offers clear abstractions with minimal dependencies and overhead.
- Minimal Infrastructure: Leverages your existing NATS JetStream.
- Type-Safe: Commands and events are protobuf-based.
- Fast Replay: Efficiently rebuild aggregate states from event streams.
- Small Footprint: Less than 1500 lines of simple, maintainable Go.
Bee is great for small, event-centric scenarios:
- Task-Oriented Microservices: Independent scaling of read/write sides.
- Audit Trails & Ledgers: Immutable event history for compliance.
- Sagas & Workflows: Event-driven state transitions.
- Edge/IoT Deployments: Compact deployment on resource-limited devices.
- Real-Time Game States: Fast catch-up of player states.
- SaaS On-Prem Plugins: Easy local deployment without infrastructure complexity.
- Ad-Hoc Analytics: Quickly spin up event projections.
To start using bee, install Go and run go get:
go get github.com/blinkinglight/beeThis will retrieve the library and update your go.mod and go.sum files.
bee.EventsPrefix="events"bee.CommandsPrefix="cmds"bee.QueryPrefix="query"// Command handlertypeCommandHandlerinterface {
Handle(m*gen.CommandEnvelope) ([]*gen.EventEnvelope, error)
}
// Projection handlertypeEventApplierinterface {
ApplyEvent(event*gen.EventEnvelope) error
}
// Query handlertypeQuerierinterface {
Query(query*gen.QueryEnvelope) (interface{}, error)
}
// Replay handlertypeReplayHandlerinterface {
ApplyEvent(m*gen.EventEnvelope) error
}
// Event process manager handlertypeManagerEventApplierinterface {
Handle(event*gen.EventEnvelope) ([]*gen.CommandEnvelope, error)
}funcCommand(ctx context.Context, handlerCommandHandler, opts...co.Options)
funcProject(ctx context.Context, fnEventApplier, opts...po.Options) errorfuncQuery(ctx context.Context, fnQuerier, opts...qo.Options) errorfuncReplay(ctx context.Context, fnReplayHandler, opts...ro.Options)
funcReplayAndSubscribe[TEventApplier](ctx context.Context, aggT, opts...ro.Options) <-chanTfuncEvent(ctx context.Context, fnManagerEventApplier, opts...eo.Options)Command options
funcWithSubject(subjectstring) OptionsfuncWithAggreate(aggregatestring) OptionsProjection options
funcWithSubject(subjectstring) OptionsfuncWithDurable(namestring) OptionsfuncWithAggreate(aggregatestring) OptionsfuncWithAggrateID(aggregateIDstring) OptionsfuncWithPrefix(prefixstring) OptionsQuery options
funcWithSubject(subjectstring) OptionsfuncWithAggreate(aggregatestring) OptionsReplay options
funcWithEventType(eventTypestring) OptionsfuncWithParent(aggreate, idstring) OptionsfuncWithSubject(subjectstring) OptionsfuncWithAggreate(aggregatestring) OptionsfuncWithStartSeq(sequint64) OptionsfuncWithAggregateID(idstring) OptionsfuncWithTimeout(timeout time.Duration) OptionsEvent options
funcWithSubject(subjectstring) funcWithAggreate(aggregatestring)
funcWithAggregateID(aggregateIDstring)
funcWithDurableName(durableNamestring)
funcWithPrefix(prefixstring)ctx=bee.WithNats(ctx, nc)
ctx=bee.WithJetStream(ctx, js)
gobee.Command(ctx, NewService(), co.WithAggreate("users"))
gobee.Project(ctx, NewUserProjection(), po.WithAggreate("users"))
gobee.Query(ctx, NewUserProjection(), qo.WithAggreate("users"))
gobee.Event(ctx, NewProcessManager(), eo.WithAggreate("users"))agg:=NewAggregate(m.AggregateId)
bee.Replay(ctx, agg, ro.WithAggreate(m.Aggregate), ro.WithAggregateID(m.AggregateId))router.Get("/stream/{id}", func(w http.ResponseWriter, r*http.Request) {
w.WriteHeader(200)
id:=chi.URLParam(r, "id")
sse:=datastar.NewSSE(w, r)
_=ssectx:=bee.WithJetStream(r.Context(), js)
ctx=bee.WithNats(ctx, nc)
agg:=&Aggregate{}
updates:=bee.ReplayAndSubscribe(ctx, agg, ro.WithAggreate(users.Aggregate), ro.WithAggregateID(id))
for {
select {
case<-r.Context().Done():
returncaseupdate:=<-updates:
sse.MergeFragmentTempl(partials.History(update.History))
}
}
})and live projection aggrate:
typeAggregatestruct {
History []string
}
func (a*Aggregate) ApplyEvent(e*gen.EventEnvelope) error {
event, err:=bee.UnmarshalEvent(e)
iferr!=nil {
returnfmt.Errorf("unmarshal event: %w", err)
}
switchevent:=event.(type) {
case*users.UserCreated:
a.History=append(a.History, "User created: "+event.Name+" from "+event.Country)
case*users.UserUpdated:
a.History=append(a.History, "User updated: "+event.Name+" from "+event.Country)
case*users.UserNameChanged:
a.History=append(a.History, "User name changed to: "+event.Name)
default:
log.Printf("unknown event type: %T", event)
returnnil// Ignore other event types
}
returnnil
}to run examples, first you need "nats server" to run with jetstream enabled. If you dont have one, first run this:
go run ./examples/natsserver
and then all other apps from examples:
go run ./examples/subscribers
and
go run ./examples/publishers
also
go run ./examples/query
to work with this package you need 2 apps:
https://buf.build/docs/ and go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
| Version | Planned Features |
|---|---|
| v0.3 | Snapshots |
| v1.0 | Stable API, full pkg.go.dev docs |
Pull requests are welcome!
Apache-2.0 © 2025 BlinkLight
