{"id":34144015,"url":"https://github.com/stackus/es","last_synced_at":"2026-06-01T04:02:07.146Z","repository":{"id":278516717,"uuid":"935236242","full_name":"stackus/es","owner":"stackus","description":"Event Sourcing library for Go","archived":false,"fork":false,"pushed_at":"2025-07-24T02:18:03.000Z","size":51,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-17T16:41:43.987Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stackus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-02-19T05:53:12.000Z","updated_at":"2025-07-24T02:18:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"37ccd4ed-50eb-443a-b061-be5447b2b991","html_url":"https://github.com/stackus/es","commit_stats":null,"previous_names":["stackus/es"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/stackus/es","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stackus%2Fes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stackus%2Fes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stackus%2Fes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stackus%2Fes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stackus","download_url":"https://codeload.github.com/stackus/es/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stackus%2Fes/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33759178,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-15T03:35:03.956Z","updated_at":"2026-06-01T04:02:07.140Z","avatar_url":"https://github.com/stackus.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# es \u0026mdash; Event Sourcing in Go\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/stackus/es.svg)](https://pkg.go.dev/github.com/stackus/es)\n[![Go Report Card](https://goreportcard.com/badge/github.com/stackus/es)](https://goreportcard.com/report/github.com/stackus/es)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nEvent Sourcing library for Go, designed for building scalable, event-driven applications with **CQRS** and **DDD** principles.\n\n## ✨ Features\n\n- Lightweight Event Sourcing primitives for building event-driven systems.\n- Supports generics for flexible aggregate IDs of nearly any Go type.\n- Flexible Event Store abstractions for custom persistence layers.\n- Built-in snapshot support to optimize aggregate state recovery.\n- Pluggable aggregate root implementations for domain modeling.\n- Support for multiple storage backends, including SQL and NoSQL.\n- Extensible pre- and post-hook system for custom event processing.\n\n## 🚀 Getting Started\n\n### Installation\n\n```sh\ngo get github.com/stackus/es\n```\n\n## 📚 Usage\n\nThere are a few basic concepts to understand when using this library:\n\n- **Aggregate Root**: The primary entity in an event-sourced system.\n- **Aggregate ID**: The unique identifier for an aggregate root.\n- **Event**: A change that has occurred to an aggregate root.\n- **Aggregate Store**: The persistence layer for storing and retrieving events and snapshots for an aggregate root.\n\n### 1. Define an Aggregate\n\n```go\n// type Aggregate[K comparable] interface {\n// \tAggregateType() string\n// \tApplyChange(event es.EventPayload) error\n// }\n\ntype Order struct {\n\tes.AggregateBase[uuid.UUID] // embed the AggregateBase\n\tTotal int\n}\n\n// implement the Aggregate[K] interface; implement the AggregateType method\nfunc (o *Order) AggregateType() string { return \"Order\" }\n\n// implement the ApplyChange method\nfunc (o *Order) ApplyChange(event es.EventPayload) error {\n\tswitch e := event.(type) {\n\tcase *OrderCreated:\n\t\to.Total = e.Total\n\t}\n\treturn nil\n}\n```\n\n### 2. Create an Aggregate ID\n\n```go\n// type AggregateID[K comparable] interface {\n//\tGet() K\n//\tNew() K\n//\tSet(id K)\n//\tIsSet() bool\n// }\n\ntype RootID uuid.UUID\n\n// implement the AggregateID interface for the RootID type\nfunc (r *RootID) Get() uuid.UUID    { return uuid.UUID(*r) }\nfunc (r *RootID) New() uuid.UUID    { return uuid.New() }\nfunc (r *RootID) Set(id uuid.UUID)  { *r = RootID(id) }\nfunc (r *RootID) IsSet() bool       { return *r != RootID(uuid.Nil) }\n```\nYou are free to use whatever kind of ID you want, as long as it you implement the `es.AggregateID[K]` interface.\nThere are tests and examples in this repository that show the usage of `string` and `int` IDs as well.\n\n\u003e TODO: Move docs for the ID before the Aggregate? It seems like it would be more logical to explain the ID before the Aggregate.\n\n### 3. Define Events\nA simple Go struct with exported fields and a `Kind() string` method will do just fine:\n```go\ntype OrderCreated struct {\n\tTotal   int\n}\nfunc (o *OrderCreated) Kind() string { return \"OrderCreated\" }\n```\n\n### 4. Create a Constructor or Factory Function\n\n```go\n// example of simple constructor\nfunc NewOrder(id *RootID) *Order {\n\treturn \u0026Order{\n\t\tAggregateBase: es.NewAggregateBase(id),\n\t}\n}\n\n// example of factory function\nfunc CreateOrder(id *RootID, total int) (*Order, error) {\n\torder := NewOrder(id)\n\n\t// record a change to the new aggregate\n\tif err := order.TrackChange(order, \u0026OrderCreated{\n\t\tTotal: total,\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn order, nil\n}\n```\n\nThe `TrackChange(aggregate es.AggregateRoot[K], event any) error` method is used to apply changes to an aggregate root.\nThis method is provided by the embedded `es.AggregateRoot[K]` in the aggregate struct.\n\nThe changes are applied to the aggregate with the previously seen `ApplyChange(event any) error` method implemented in `Order`.\n\n### 5. Create an Event Store\n\n#### a. Create a repository that implements the `es.EventRepository` interface:\n\n```go\nrepository := memory.NewEventRepository[uuid.UUID]()\n```\n\n#### b. Create an instance of the event store:\n\n```go\neventStore := es.NewEventStore(reg, repository)\n```\n#### c. All events that you want to store must be registered with the store:\n```go\nes.RegisterEvent(eventStore, \u0026OrderCreated{})\n// register more events ...\n```\n\n### 6. Loading and Saving Events\n\nTo load all changes for an aggregate, you will do something similar to this:\n\n```go\nid := RootID(someOrderID)\norder := NewOrder(\u0026id)\n\nerr := eventStore.Load(ctx, order)\nif err != nil {\n\treturn err\n}\n```\n\nTo save uncommitted changes made to an aggregate, you will do something similar to this:\n\n```go\nerr := eventStore.Save(ctx, order)\nif err != nil {\n\treturn err\n}\n```\n\nBoth of these methods will use the hooks you provide to process the events before and after they are saved or loaded.\n\nHooks are an optional third variadic parameter to the `Load` and `Save` methods.\nThe types of hooks available include pre-hooks and post-hooks, for example `EventsPreSave` and `EventsPostLoad`.\n\n```go\n\nvar hooks []es.Hook[uuid.UUID]\n// add a pre-save hook\nhooks = append(hooks, es.EventsPreSaveHook(func(ctx context.Context, aggregate es.Aggregate[uuid.UUID], events []es.Event[uuid.UUID]) error {\n\t// do something before saving\n\treturn nil\n}))\n// add a post-save hook\nhooks = append(hooks, es.EventsPostSaveHook(func(ctx context.Context, aggregate es.Aggregate[uuid.UUID], events []es.Event[uuid.UUID]) error {\n\t// do something after saving\n\treturn nil\n}))\n\nerr := eventStore.Save(ctx, order, hooks...)\n```\n\nUse these hooks to add custom behavior to the saving and loading of events. Logging, \"domain events\", and other behaviors can be added here.\n\n## Snapshots\n\nSnapshots are a way to optimize the loading of an aggregate by storing the state of the aggregate at a certain point in time.\n\nUsing Snapshots is entirely optional, but can be invaluable when you have aggregates with a large number of events.\n\n### 1. Define a Snapshot\nLike the events, a snapshot is a simple Go struct with exported fields and a `Kind() string` method.\n```go\ntype OrderSnapshot struct {\n\tTotal int\n}\nfunc (o *OrderSnapshot) Kind() string { return \"OrderSnapshot\" }\n```\n\n### 2. Add the required methods to your Aggregate\n\n```go\n// type SnapshotAggregate[K comparable] interface {\n// \tCreateSnapshot() es.SnapshotPayload\n//\tApplySnapshot(snapshot es.SnapshotPayload) error\n// }\n\nfunc (o *Order) CreateSnapshot() es.SnapshotPayload {\n\treturn \u0026OrderSnapshot{\n\t\tTotal: o.Total,\n\t}\n}\n\nfunc (o *Order) ApplySnapshot(snapshot es.SnapshotPayload) error {\n\tswitch s := snapshot.(type) {\n\tcase *OrderSnapshot:\n\t\to.Total = s.Total\n\t}\n\t\n\treturn nil\n}\n```\n\n### 3. Create a Snapshot Store\n\n#### a. Create a repository that implements the `es.SnapshotRepository` interface:\n\n```go\nsnapshotRepository := memory.NewSnapshotRepository[uuid.UUID]()\n```\n\n#### b. Create an instance of the snapshot store:\n\n```go\nsnapshotStore := es.NewSnapshotStore(\n\teventStore, // we will use the event store we created earlier to save events\n\tsnapshotRepository, \n\tes.NewFrequencySnapshotStrategy(10), // create a new snapshot every 10 events\n)\n```\n\u003e There are other strategies available, such as `es.NewParticularChangesSnapshotStrategy(changes...)`, which creates a new snapshot when a particular change has occurred. Of course, you can also create your own strategy by implementing the `es.SnapshotStrategy` interface.\n\n#### c. Register the snapshots with the snapshot store:\n\n```go\nes.RegisterSnapshot(snapshotStore, \u0026OrderSnapshot{})\n```\n\n### 4. Loading and Saving Snapshots\n\nThe `SnapshotStore` has the same `Load` and `Save` methods as the `EventStore`. They both implement the `AggregateStore[K]` interface.\n\nThis means that we also have access to the same hooks that we used with the `EventStore`.\nThe only difference is that the hooks are applied to snapshots instead of events, so they are of type `SnapshotPre*` and `SnapshotPost*`.\n\nThese hooks are used by the snapshot store to hook into the saving and loading of events.\n\n## 📜 License\n\nThis project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.\n\n## 🤝 Contributing\n\nContributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://github.com/stackus/es/issues).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstackus%2Fes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstackus%2Fes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstackus%2Fes/lists"}