A Haskell library for building event-sourced applications with CQRS.
Eventium provides composable, type-safe abstractions for event sourcing: event stores with optimistic concurrency, pure projections, command handlers, process managers, event subscriptions, and pluggable storage backends. It is a modernized fork of eventful, updated for GHC 9.10.
| Package | Description |
|---|---|
| eventium-core | Core abstractions: event stores, projections, command handlers, process managers, codecs, schema evolution, TH utilities |
| eventium-memory | STM-based in-memory event store for development and testing |
| eventium-sqlite | SQLite backend via persistent |
| eventium-postgresql | PostgreSQL backend via persistent |
| eventium-sql-common | Shared Persistent entity definitions and SQL operations |
| eventium-testkit | Shared hspec test utilities |
# Enter dev environment (requires Nix with flakes)
nix develop
# Build everything
just build
# Run all tests
just testAdd eventium-core plus a storage backend to your dependencies:
dependencies:
- eventium-core >= 0.1.0
- eventium-sqlite >= 0.1.0 # or eventium-postgresql, eventium-memoryimport Eventium
import Eventium.Store.Memory
main :: IO ()
main = do
tvar <- eventMapTVar
let writer = runEventStoreWriterUsing atomically (tvarEventStoreWriter tvar)
reader = runEventStoreReaderUsing atomically (tvarEventStoreReader tvar)
-- Apply a command through a command handler
result <- applyCommandHandler writer reader myCommandHandler aggregateId myCommand
case result of
Left err -> print err
Right events -> print events- Projection -- Pure fold: seed state + event handler. Rebuilds aggregate or read-model state from events.
- CommandHandler -- Validates a command against current state and produces events or a domain error.
applyCommandHandlerWithCacheintegrates withProjectionCachefor faster aggregate loading. - EventStoreReader / EventStoreWriter -- Polymorphic over key, position, monad, and event types. Supports versioned (per-aggregate) and global (cross-aggregate) streams.
- ProjectionCache -- Snapshot store for aggregate state, avoiding full event replay.
snapshotEventHandlerauto-updates the cache as events are written. - ReadModel -- Abstraction for queryable persistent views driven by the global event stream. Users define schema and event handlers; the library manages checkpointing.
rebuildReadModelreplays all events for one-shot rebuilds. - ProcessManager -- Coordinates long-running workflows across aggregates. Reacts to events with pure
[ProcessManagerEffect]values (includingIssueCommandWithCompensationfor saga compensation). - CommandDispatcher -- Routes commands to aggregates and reports
CommandDispatchResult(CommandSucceeded|CommandFailed).commandHandlerDispatcherprovides list-based multi-aggregate routing. - EventHandler -- Composable event consumer with
Contravariant,Semigroup, andMonoidinstances. - EventPublisher -- Decouples post-write notification from the store writer.
publishingEventStoreWriterwraps a writer to auto-dispatch after each write. - EventSubscription -- Push-based event delivery.
pollingSubscriptionpolls the global stream at a configurable interval. - Codec -- Bidirectional event encoding/decoding with JSON support and TH-generated sum-type codecs.
- SchemaEvolution -- Upcast-on-read event schema evolution against an immutable log: a versioned payload envelope plus a registry of pure single-hop upcasters (
atKey,addFieldIfAbsent,renameField,removeField).upcastingValueCodecnormalizes older stored events to the current shape on read without ever rewriting the log; version-skipping runs more hops.
Working examples demonstrate increasing complexity:
Minimal single-file example: bounded counter with in-memory store.
cabal run counter-cliRestaurant ordering system (inspired by Edument's CQRS tutorial): tab management, chef todo list as a polling read model.
cabal run cafe-main -- --help
cabal run cafe-chef-todo-main -- --database-path cafe.dbFull CQRS application: accounts, customers, money transfers via process manager, read models, event publishing.
cabal run bank-main -- --helpNix + Cabal with GHC 9.10.3. Cabal files are generated from package.yaml via hpack.
just build # cabal build all
just test # cabal test all
just hpack # regenerate .cabal from package.yaml
just format # ormolu formatting
just ghcid # continuous compilationPostgreSQL tests require a running instance (docker compose up -d). See CLAUDE.md for env var details.
- Architecture -- Architecture and design decisions
- Changelog -- Version history
- Examples -- Working applications
MIT -- see LICENSE.md.