https://github.com/ccamel/erlang-event-sourcing-xp
π§ͺ Experimenting with Event Sourcing in Erlang
https://github.com/ccamel/erlang-event-sourcing-xp
Last synced: about 1 month ago
JSON representation
π§ͺ Experimenting with Event Sourcing in Erlang
- Host: GitHub
- URL: https://github.com/ccamel/erlang-event-sourcing-xp
- Owner: ccamel
- License: bsd-3-clause
- Created: 2025-03-13T08:43:33.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2025-03-23T12:45:35.000Z (about 1 month ago)
- Last Synced: 2025-03-23T13:29:48.042Z (about 1 month ago)
- Language: Erlang
- Size: 71.3 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-ccamel - ccamel/erlang-event-sourcing-xp - π§ͺ Experimenting with Event Sourcing in Erlang (Erlang)
README
# erlang-event-sourcing-xp
> π§ͺ Experimenting with Event Sourcing in Erlang using _pure functional_ principles, [gen_server](https://www.erlang.org/doc/apps/stdlib/gen_server.html)-based aggregates, and _pluggable_ Event Store backends.
## About
I'm a big fan of [Erlang/OTP][Erlang] and [Event Sourcing], and I strongly believe that the _Actor Model_ and _Event Sourcing_ are a natural fit. This repository is my way of exploring how these two concepts can work together in practice.
As an **experiment**, this repo won't cover every facet of event sourcing in depth, but it should provide some insights and spark ideas on the potential of this approach in [Erlang].
[Erlang]: https://www.erlang.org/
[Event Sourcing]: https://learn.microsoft.com/en-us/azure/architecture/patterns/event-sourcing## Architecture
### Overview
This project is structured around the core principles of Event Sourcing:
- All changes are represented as immutable events.
- Aggregates handle commands and apply events to evolve their state.
- State is rehydrated by replaying historical events. Possible optimizations include snapshots and caching.### Event store
The event store is a core component in this experiment, designed as a customizable `behaviour` that any `module` can implement to handle event storage. Its primary responsibilities include storing and retrieving events.
```erlang
% Initializes the event store
-callback start() -> {ok, initialized | already_initialized} | {error, term()}.% Shuts down the event store.
-callback stop() -> {ok} | {error, term()}.% Persists a list of events for a given stream.
-callback persist_events(StreamId, Events) -> ok | {error, term()}
when StreamId :: stream_id(),
Events :: [event()].% Retrieves events from a stream and folds them using a provided function
-callback retrieve_and_fold_events(StreamId, Options, FoldFun, InitialAcc) -> {ok, Acc} | {error, term()}
when StreamId :: stream_id(),
Options :: fold_events_opts(),
FoldFun :: fold_events_fun(),
InitialAcc :: Acc.
```#### Future Features
While not yet implemented, the event store could be extended to:
- Store snapshots of the aggregateβs state for efficient retrieval.
- Support event subscriptions for real-time updates.#### Current Implementation
- [Mnesia](https://www.erlang.org/doc/apps/mnesia/mnesia.html)
- [ETS](https://www.erlang.org/doc/apps/stdlib/ets.html)### Aggregate
The _aggregate_ is implemented as a [gen_server](https://www.erlang.org/doc/apps/stdlib/gen_server.html) that encapsulates _domain logic_ and delegates event persistence to a pluggable Event Store (e.g. [ETS](https://www.erlang.org/doc/apps/stdlib/ets.html) or [Mnesia](https://www.erlang.org/doc/apps/mnesia/mnesia.html)).
The core idea is to separate concerns between domain behavior and infrastructure. To achieve this, the system is structured into three main components:
- π§© **Domain Module** β a pure module that implements domain-specific logic via _behaviour_ callbacks.
- βοΈ **`aggregate`** β the glue that bridges domain logic and infrastructure (event sourcing logic, event persistence, etc.).
- π¦ [`gen_server`](https://www.erlang.org/doc/apps/stdlib/gen_server.html) β the OTP mechanism that provides lifecycle management and message orchestration.The `aggregate` provides:
- A [behaviour](https://www.erlang.org/doc/system/design_principles.html#behaviours) for domain-specific modules to implement.
- A generic [OTP](https://www.erlang.org/doc/system/design_principles.html) [gen_server](https://www.erlang.org/doc/apps/stdlib/gen_server.html) that:
- Rehydrates state from events on startup.
- Processes commands to produce events.
- Applies events to evolve internal state.
- Automatically passivates (shuts down) after inactivity.The following diagram shows how the system processes a command using the event-sourced aggregate infrastructure.
```mermaid
sequenceDiagram
actor User
participant GenAggregate as aggregate
participant GenServer as gen_server
participant DomainModule as AggregateModule (callback)User ->> GenAggregate: gen_aggregate:start_link(...)
activate GenAggregate
GenAggregate ->>+ GenServer: gen_server:start_link(Module, State)
GenServer ->> GenAggregate: gen_aggregate:init/1
deactivate GenAggregateUser ->> GenAggregate: gen_aggregate:dispatch(Pid, Command)
activate GenAggregate
GenAggregate ->> GenServer: gen_server:call(Pid, Command)
GenServer ->> GenAggregate: gen_aggregate:handle_call/3GenAggregate ->> DomainModule: handle_command(Command, State)
GenAggregate ->> GenAggregate: persist_events(Store, Events)loop For each Event
GenAggregate ->> DomainModule: apply_event(Event, State)
end
deactivate GenAggregate
```### File Structure
```plaintext
apps/event_sourcing_core
βββ include
β βββ event_sourcing_core.hrl % Shared types and macros
βββ src
β βββ event_sourcing_core.app.src % Application definition
β βββ event_sourcing_core_aggregate.erl % Aggregate process (gen_server)
β βββ event_sourcing_core_aggregate_behaviour.erl % Aggregate behaviour (domain contract)
β βββ event_sourcing_core_store.erl % Event store behaviour
β βββ event_sourcing_core_store_ets.erl % ETS-backed store implementation
β βββ event_sourcing_core_store_mnesia.erl % Mnesia-backed store implementation
βββ test
βββ bank_account_aggregate.erl % Sample domain aggregate
βββ event_sourcing_core_aggregate_tests.erl % Aggregate process tests
βββ event_sourcing_core_store_tests.erl % Store behaviour tests
```## Build
```sh
rebar3 compile
```## Test
```sh
rebar3 eunit
```