Skip to content

Repository files navigation

tests

bee - eventsourcing on nats.io

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.

Bee

Why Bee?

  • 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.

Typical Use Cases

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.

Table of Contents

Getting Started

Installing

To start using bee, install Go and run go get:

go get github.com/blinkinglight/bee

This will retrieve the library and update your go.mod and go.sum files.

Settings

bee.EventsPrefix="events"bee.CommandsPrefix="cmds"bee.QueryPrefix="query"

Interfaces

// 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)
}

Functions

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)

Options

Command options

funcWithSubject(subjectstring) OptionsfuncWithAggreate(aggregatestring) Options

Projection options

funcWithSubject(subjectstring) OptionsfuncWithDurable(namestring) OptionsfuncWithAggreate(aggregatestring) OptionsfuncWithAggrateID(aggregateIDstring) OptionsfuncWithPrefix(prefixstring) Options

Query options

funcWithSubject(subjectstring) OptionsfuncWithAggreate(aggregatestring) Options

Replay options

funcWithEventType(eventTypestring) OptionsfuncWithParent(aggreate, idstring) OptionsfuncWithSubject(subjectstring) OptionsfuncWithAggreate(aggregatestring) OptionsfuncWithStartSeq(sequint64) OptionsfuncWithAggregateID(idstring) OptionsfuncWithTimeout(timeout time.Duration) Options

Event options

funcWithSubject(subjectstring) funcWithAggreate(aggregatestring)
funcWithAggregateID(aggregateIDstring)
funcWithDurableName(durableNamestring)
funcWithPrefix(prefixstring)

Usage:

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))

Example

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
}

Prebuild examples

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

Developing

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

Roadmap

VersionPlanned Features
v0.3Snapshots
v1.0Stable API, full pkg.go.dev docs

Development & Contribution

Pull requests are welcome!

License

Apache-2.0 © 2025 BlinkLight

About

EventSourcing on NATS.io

Topics

Resources

Stars

22 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages