Getting Started
Estoria requires Go >=1.26.
go get github.com/go-estoria/estoriaThe core Estoria components only depend on the Go standard library and
github.com/gofrs/uuid/v5for UUID support. Vendor-specific components are imported separately as needed from estoria-contrib.
Quickstart
Run all of the code below in a Go playground. For complete applications built on Estoria — a collaborative kanban board, an order-fulfillment service, a sensor fleet, chess — see Examples.
Entities
An entity is any type; no interface is required. Provide a factory function that builds one from a UUID:
type User struct {
ID uuid.UUID
Name string
}
func NewUser(id uuid.UUID) User {
return User{ID: id, Name: "Unknown"}
}Events
Events must implement estoria.DomainEvent:
type UserNameChanged struct {
NewName string
}
func (UserNameChanged) EventType() string { return "namechanged" }
func (UserNameChanged) New() estoria.DomainEvent[User] {
return &UserNameChanged{NewName: "Unknown User"}
}
func (e UserNameChanged) ApplyTo(user User) User {
user.Name = e.NewName
return user
}Aggregates
Create an event store to store events:
eventStore, _ := memory.NewEventStore()Then, create an aggregate store using the event store, an aggregate type name, your entity factory function, and your event types. The type name becomes part of every aggregate’s stream address, so it must remain stable for the lifetime of your data:
aggregateStore, _ := aggregatestore.New(eventStore, "user", NewUser,
aggregatestore.WithEventTypes(
UserNameChanged{},
),
)Now you can begin working with aggregates in your application:
userID := uuid.Must(uuid.NewV4())
// create a new User aggregate
newUser := aggregateStore.New(userID)
// append an event
newUser.Append(UserNameChanged{NewName: "Juliette"})
// save the aggregate
_ = aggregateStore.Save(ctx, newUser, nil)
// load the aggregate
loadedUser, _ := aggregateStore.Load(ctx, userID, nil)
// access the aggregate's state
user := loadedUser.State()