Skip to content

effect-cqrs

Event sourcing and CQRS for Effect v4. Commands decide on events, events are the only source of truth, and read models are projections you can throw away and rebuild.

pnpm add effect-cqrs effect

A trial port of moleculer-cqrs to Effect v4, so the API may change.

account/aliceappend only
  1. #1AccountOpened{ owner: "Alice" }
  2. #2MoneyDeposited{ amount: 250 }+250
  3. #3MoneyWithdrawn{ amount: 40 }−40
  4. #4TransferSent{ to: "bob", amount: 60 }−60
  5. #5MoneyDeposited{ amount: 120 }+120
  6. #6TransferRefunded{ amount: 60, reason: "AccountNotOpen" }+60
state = events.reduce(projection, initial){ status: "open", owner: "Alice", balance: 330, version: 6 }

How a command flows

  1. Command

    A typed call, or JSON from a transport through dispatch. The payload is checked against its schema first.

    Commands guide
  2. Aggregate

    The service rebuilds the aggregate's state from its events, and the handler decides what happened, or fails with a typed error.

    Aggregates guide
  3. Event store

    The events are appended, but only if nobody wrote to that aggregate since its state was loaded.

    Storage guide
  4. View models

    Each one reads the new events from its own checkpoint. caughtUp waits until your own write is there.

    View models guide

A bank, running in your browser

Accounts are aggregates, three view models project them, and a saga moves money between them. The customer app on the left only shows read models, so you can watch them trail the store. The devtools on the right slow every stage down, pause and step commands, crash a projection, and replay the log.

Tests read like moleculer-cqrs

Fixture runs commands through the real service on an in-memory store, so validation and encoding are tested too. The chain is an Effect, which it.effect runs.

it.effect("deletes a todo", () =>
Fixture.make(Todo)
.givenEvents([created])
.when("deleteTodo", {})
.expectEvent("TodoDeleted")
.inspectState((state) => assert.isTrue(state.deleted)))
Testing guide

Keep the log where it fits

Every store passes the same tests. The numbers come from one laptop, so read them as orders of magnitude.

  • In memoryFor tests, examples and the browser.60,000 commands/s
  • SQLiteOne process, one file, no server.0.1 ms a command
  • PostgreSQLSeveral processes writing, positions in commit order.1 ms a command
Storage guidePerformance