Event sourcing is a software design pattern that captures all changes to an application's state as a sequence of immutable events. Instead of storing the current state of an entity, event sourcing stores the history of all events that led to that state. The current state can then be reconstructed by replaying these events.
- Event - immutable record representing something that happened in the system
- Event Store - persistent, append-only log of events
- Aggregate - cluster of domain objects treated as a single unit
- Projection - view of the data built by applying events to create a readable state
- Command - instruction to perform an action (which will generate events)
- Every application state change is recorded as an event, providing a comprehensive history and audit trail
- Timetravel! Determine the state of the system at any point in time
- Fault tolerance - rebuild the system state by replaying events
- Observability - the exact sequence of state changes is observable through event logs
- Separation of write and read responsibilities
- Read performance - replaying a large number of events can be slow
- Handling changes to event schemas over time (i.e. schema evolution)
- Read models may be temporarily out of sync with writes (i.e. eventual consistency)
- Saving the current state periodically to avoid replaying all events (i.e. snapshotting)
Event sourcing is useful when:
- Your system needs an audit trail or historical state
- You have complex business domains with evolving requirements
- You often need to debug a sequence of state changes
To run the event sourcing application tests, execute the following command:
bun test
Install Bun if you don't already have it
Coming soon!
A simple in-memory event store.
Features:
Event schema registry and type definitions, powered by Valibot.
This application is written in TypeScript. There are a few points to note here.
Performance is a critical aspect of any event sourcing application. This implementation uses Bun as the JavaScript runtime, which is known for its high performance and low memory footprint.
You should be able to refactor the event sourcing application without fear of breaking existing functionality. This is supported by adhering to the following principles:
- Maintain a central, single source of truth of data models and type definitions. Derive all other types (e.g. partial types) from the central source. This means types only need to be changed once (e.g. when adding new fields to an event).
This project will be expanded to replace the in-memory event store with SlateDB, "an embedded storage engine built as a log-structured merge-tree" that writes all data to object storage.
The following properties of LSMs make them very suitable for implementing an event sourcing event store:
- Write performance optimisation
- Immutable by design
- Range query efficiency (e.g. for retrieving all events)
- Compaction (e.g. for snapshotting and data lifecycle management)
Other plans:
- Aggregates/partitions
- Optimistic concurrency
- CQRS and reporting database