eventsourcing.nodejs
repository·main·Indexed 19 days ago
https://github.com/oskardudycz/eventsourcing.nodejsSamples, tutorials, and resources for implementing Event Sourcing patterns in Node.js using JavaScript and TypeScript. The repository covers core concepts such as event streams, state rehydration, and event stores (including EventStoreDB), and provides practical samples for Basic CQRS and transitioning from CRUD to Event Sourcing. It also includes a structured self-paced training kit covering business logic, optimistic concurrency, and projections.
What's inside eventsourcing.nodejs
- EventSourcing.NodeJS is a repository providing tutorials, practical samples, and resources for implementing Event Sourcing patterns within the NodeJS ecosystem. It is part of a series of similar repositories for .NET and JVM.
Overview of Event Sourcing workshop exercises
mainThe workshop is structured into several progressive modules. You can find the instructions for each in their respective folders under
src/:- Events definition:
./src/01_events_definition/ - Getting State from events:
./src/02_getting_state_from_events/ - Appending Events:
- Raw EventStoreDB:
./src/03_appending_events_eventstoredb/ - Emmett with various storages (PostgreSQL, EventStoreDB, MongoDB):
./src/04_appending_events_emmett/
- Raw EventStoreDB:
- Getting State from events:
- Raw EventStoreDB:
./src/05_getting_state_from_events_eventstoredb/ - Emmett with various storages:
./src/06_getting_state_from_events_emmett/
- Raw EventStoreDB:
- Business Logic: Writing (
./src/07_business_logic/) and Testing (./src/08_business_logic/). - Application logic:
./src/09_application_logic_eventstoredb/ - Optimistic Concurrency:
./src/10_optimistic_concurrency_eventstoredb/ - Projections:
- General:
./src/11_projections_single_stream/ - Idempotency:
./src/12_projections_single_stream_idempotency/ - Eventual Consistency:
./src/13_projections_single_stream_eventual_consistency/
- General:
- Events definition:
Overview of Event Sourcing exercises
mainThe Hotel Management sample provides a structured learning path through the following exercises:
- Events definition: Defining the core events.
- Getting State from events: Reconstructing state from an event stream.
- Appending Events: Using EventStoreDB to persist events.
- Getting State from events (EventStoreDB): Retrieving state specifically from the database.
- Business logic: Implementing logic (General and EventStoreDB specific).
- Optimistic Concurrency: Handling concurrency conflicts in EventStoreDB.
- Projections: Implementing projections, including handling Idempotency and Eventual Consistency.
Core Concepts of Event Sourcing
mainEvent Sourcing is a design pattern where business operation results are stored as a sequence of events rather than just the current state.
Key Abstractions:
- Event: An immutable fact representing something that happened in the past (e.g.,
user_added,order_confirmed). Events are broadcasted and cannot be retracted, only ignored. - Stream: A logical grouping of events that represents a specific entity. All state mutations for an entity are stored in its dedicated stream.
- Stream Position: A unique, incremental numeric value (often called
versionororder of occurrence) assigned to each event within a stream. It is used to maintain event order and detect concurrency issues. - Event Store: The database responsible for persisting the append-only log of events. While any database can act as an event store if it supports append-only chronological storage, specialized databases like EventStoreDB are designed specifically for this purpose.
- Event: An immutable fact representing something that happened in the past (e.g.,
Event Data Representation and Schema
mainTechnically, events are messages (JSON, Binary, or XML) that typically contain the following fields:
id: Unique event identifier.type: Name of the event (e.g.,invoice-issued).streamId: The ID of the object the event belongs to.streamPosition: The order of the event within the stream.timestamp: When the event occurred.metadata: Optional context likecorrelationIdorcausationId.
In TypeScript, it is recommended to use a base
Eventtype withReadonly<>wrappers to ensure immutability of both the type and the data payload.export type Event< EventType extends string = string, EventData extends Record<string, unknown> = Record<string, unknown> > = Readonly<{ type: Readonly<EventType>; data: Readonly<EventData>; }>;Rebuild entity state from events
mainIn an event-sourced system, the current state of an entity (e.g., a Shopping Cart) is not stored directly. Instead, it is reconstructed by replaying a sequence of events from a stream. This process is often called 'rehydrating' or 'aggregating' the state.
There are two primary architectural approaches to implementing this logic:
- Immutable Approach: The state is rebuilt by applying events to a data structure that returns a new version of the state for every event. This is common in functional programming patterns.
- Object-Oriented (OOP) Approach: The state is rebuilt by calling methods on a mutable entity instance that updates its internal properties based on the incoming events.
When implementing this, you can choose different levels of complexity:
- Basic: Events contain only primitive types.
- Typed Mapping: Using specific types to map raw event payloads to structured data during the reconstruction process.
- Generalised/Repository Pattern: Using a generalised stream aggregator or a repository to handle the retrieval and reconstruction logic automatically.
Explore Optimistic Concurrency implementation patterns
mainThe workshop provides four distinct architectural patterns for implementing application logic with optimistic concurrency. You can examine the following test files to see how each pattern handles business logic and concurrency:
- Classical, mutable aggregates (rich domain model): Uses traditional OOP where the aggregate maintains state and handles logic.
- File:
./oop/aggregate/applicationLogic.exercise.test.ts
- File:
- Mixed approach, mutable aggregates (rich domain model), returning events from methods: Aggregates handle logic but return events to be persisted rather than applying them internally.
- File:
./oop/aggregate_returning_events/applicationLogic.exercise.test.ts
- File:
- Immutable, with functional command handlers composition and entities as anemic data model: Uses pure functions and immutable data structures.
- File:
./immutable/functions/applicationLogic.exercise.test.ts
- File:
- Immutable with composition using the Decider pattern and entities as anemic data model: Uses the Decider pattern for functional event sourcing.
- File:
./immutable/businessLogic.exercise.test.ts
- File:
- Classical, mutable aggregates (rich domain model): Uses traditional OOP where the aggregate maintains state and handles logic.
Implementation variations for state reconstruction
mainWhen implementing state reconstruction from events, this project provides two distinct patterns for the entity structure:
- Mutable approach: Use the implementation found in
./oop/gettingStateFromEvents.exercise.test.tsas a reference for object-oriented, mutable state updates. - Immutable approach: Use the implementation found in
./immutable/gettingStateFromEvents.exercise.test.tsas a reference for functional, immutable state updates.
- Mutable approach: Use the implementation found in
Testing variations for Event Sourcing
mainDepending on your architectural choice (Mutable vs. Immutable), there are four primary ways to implement business logic tests:
- Mutable aggregates: Testing logic where the aggregate object's internal state is updated directly.
- Mutable aggregates returning events: Testing logic where the aggregate is updated, but the method also explicitly returns the resulting events.
- Fully immutable structures with functions: Testing pure functions that take the current state and a command, then return a new state and/or events.
- Fully immutable structures with decider: Testing the 'Decider' pattern, where a function (the decider) accepts a command and current state, returning a list of new events and/or a new state.
Testing pattern for Event Sourcing
mainWhen writing unit tests for an Event Sourced system, follow the GIVEN/WHEN/THEN pattern applied to events rather than state:
- GIVEN: A set of events already recorded for the entity (to reconstruct its current state).
- WHEN: A command is executed against the state built from those events.
- THEN: Verify that the business logic produces the expected new event(s) or throws the appropriate exception.
This differs from traditional CRUD testing where you assert against the final state of a database record; in Event Sourcing, you assert against the resulting stream of events.
Core Event Sourcing Concepts
mainThe repository covers fundamental Event Sourcing concepts including:
- Event Sourcing: The architectural pattern of storing state changes as a sequence of events.
- Event: The fundamental unit of change representing a fact that occurred in the system.
- Stream: A sequence of related events.
- Event Representation: How events are structured and modeled.
- Retrieving State: The process of reconstructing the current state of an entity by replaying its events.
- Event Store: The specialized database used to persist and manage event streams.
Set up the workshop environment with Docker
mainTo run the workshop, you need to start the required infrastructure (EventStoreDB, MongoDB, and PostgreSQL) using Docker Compose.
Run the following command:
docker compose upIf you are on a Mac, use the ARM-specific configuration:
docker compose -f docker-compose.arm.yml upInfrastructure Access
Once started, you can access the following UIs:
Service URL Credentials EventStoreDB UI http://localhost:2113/N/A Mongo Express UI http://localhost:8081/login: admin, password:passPgAdmin http://localhost:5050/login: admin@pgadmin.org, password:adminPostgreSQL Database Credentials:
- Host:
postgres - Login:
postgres - Password:
postgres|
- Host: