Production-grade Event Sourcing library for Go, ported from Eventuous (.NET).
- Functional-first — pure functions over OOP, type switch fold over handler registration
- Idiomatic Go — composition over inheritance, middleware chains,
context.Context+ errors - Multi-module — import only what you need, no transitive dependency bloat
| Module | Import | Description |
|---|---|---|
| core | github.com/eventuous/eventuous-go/core | Domain, persistence, command services, subscriptions — near-zero external deps |
| kurrentdb | github.com/eventuous/eventuous-go/kurrentdb | KurrentDB/EventStoreDB store and subscriptions |
| otel | github.com/eventuous/eventuous-go/otel | OpenTelemetry tracing and metrics |
go get github.com/eventuous/eventuous-go/core
go get github.com/eventuous/eventuous-go/kurrentdbtypeRoomBookedstruct {
BookingIDstring`json:"bookingId"`RoomIDstring`json:"roomId"`
}
typeBookingCancelledstruct {
BookingIDstring`json:"bookingId"`Reasonstring`json:"reason"`
}
typeBookingStatestruct {
IDstringRoomIDstringActivebool
}
funcbookingFold(stateBookingState, eventany) BookingState {
switche:=event.(type) {
caseRoomBooked:
returnBookingState{ID: e.BookingID, RoomID: e.RoomID, Active: true}
caseBookingCancelled:
state.Active=falsereturnstatedefault:
returnstate
}
}types:=codec.NewTypeMap()
codec.Register[RoomBooked](types, "RoomBooked")
codec.Register[BookingCancelled](types, "BookingCancelled")
jsonCodec:=codec.NewJSON(types)// Connect to KurrentDBsettings, _:=kurrentdb.ParseConnectionString("kurrentdb://localhost:2113?tls=false")
client, _:=kurrentdb.NewClient(settings)
store:=kdb.NewStore(client, jsonCodec)
// Functional command service — state in, events outsvc:=command.New[BookingState](store, store, bookingFold, BookingState{})
command.On(svc, command.Handler[BookRoom, BookingState]{
Expected: eventuous.IsNew,
Stream: func(cmdBookRoom) eventuous.StreamName { returneventuous.NewStreamName("Booking", cmd.BookingID) },
Act: func(ctx context.Context, stateBookingState, cmdBookRoom) ([]any, error) {
return []any{RoomBooked{BookingID: cmd.BookingID, RoomID: cmd.RoomID}}, nil
},
})
// Handle a commandresult, err:=svc.Handle(ctx, BookRoom{BookingID: "123", RoomID: "room-42"})sub:=kdb.NewCatchUp(client, jsonCodec, "MyProjection",
kdb.FromAll(),
kdb.WithHandler(subscription.HandlerFunc(func(ctx context.Context, msg*subscription.ConsumeContext) error {
fmt.Printf("Event: %s on stream %s\n", msg.EventType, msg.Stream)
returnnil
})),
)
// Blocks until context is cancelledsub.Start(ctx)For teams that prefer the DDD aggregate pattern:
funcBookRoom(agg*aggregate.Aggregate[BookingState], roomIDstring) error {
iferr:=agg.EnsureNew(); err!=nil {
returnerr
}
agg.Apply(RoomBooked{RoomID: roomID})
returnnil
}
svc:=command.NewAggregateService[BookingState](store, store, bookingFold, BookingState{})
command.OnAggregate(svc, command.AggregateHandler[BookRoomCmd, BookingState]{
Expected: eventuous.IsNew,
ID: func(cmdBookRoomCmd) string { returncmd.BookingID },
Act: func(ctx context.Context, agg*aggregate.Aggregate[BookingState], cmdBookRoomCmd) error {
returnBookRoom(agg, cmd.RoomID)
},
})core/
├── eventuous.go StreamName, Metadata, errors
├── aggregate/ Aggregate[S] — domain building block
├── codec/ TypeMap + JSON codec
├── store/ EventReader/Writer/Store interfaces, LoadState, LoadAggregate
├── command/ Functional Service[S] + AggregateService[S]
└── subscription/ EventHandler, middleware, checkpoint committer
kurrentdb/ KurrentDB store + catch-up & persistent subscriptions
otel/ OpenTelemetry tracing + metrics
# Build all modules
(cd core && go build ./...)
(cd kurrentdb && go build ./...)
(cd otel && go build ./...)
# Test core (no infrastructure needed)
(cd core && go test ./...)
# Test kurrentdb (uses testcontainers — Docker required)
(cd kurrentdb && go test -timeout 300s ./...)
# Test otel
(cd otel && go test ./...)Apache License 2.0 — see LICENSE.