We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Comparisons
Documentation
evoq vs Commanded (Elixir)
Both evoq and Commanded are Elixir CQRS frameworks, they provide command dispatch, aggregate lifecycle, event handling, process managers, and projection support. Neither is an event store; both need a backend.
The practical comparison is between the full stacks:
| evoq + ReckonDB | Commanded + commanded/eventstore | |
|---|---|---|
| Framework | evoq | Commanded |
| Default store | reckon-db (Khepri/Ra Raft) | commanded/eventstore (PostgreSQL) |
| Test adapter | mem-evoq | in-memory adapter |
| Command dispatch | ✅ | ✅ |
| Aggregate lifecycle | ✅ | ✅ |
| Process managers | ✅ | ✅ (EventReactor) |
| Projections / read models | ✅ | ✅ (commanded-ecto) |
| DCB (cross-aggregate consistency) | ✅ | ❌ |
| Secondary indexes | ✅ | ❌ |
| Competing consumers | 🟡 (in development) | ✅ concurrency_limit |
| Polyglot gRPC access to the store | ✅ | ❌ |
| Store HA | ✅ BEAM Raft | 🟡 PostgreSQL HA + advisory locks |
| Tamper resistance | ✅ | ❌ |
| Ash framework integration | ❌ | ✅ ash_commanded |
| Latest version | evoq 1.23.x | Commanded 1.4.10 (May 2026) |
Framework Design
evoq
evoq is process-centric: a command flows into a handler, the handler loads or creates an aggregate (an in-memory process), the aggregate emits events, the events are persisted by the adapter, and projections receive those events to update read models. The framework is the Erlang OTP process model, aggregates are GenServers, projections are supervised workers.
The adapter interface is a behaviour: reckon_evoq wires it to reckon-db;
mem_evoq wires it to an in-memory table. Your domain code calls the framework
API; the store is underneath.
Commanded
Commanded is structurally similar: commands dispatch to aggregate modules, which
emit events, which are persisted by the adapter and forwarded to event handlers and
process managers. commanded-ecto projections write to PostgreSQL read models.
The main structural difference is that Commanded is a library that wraps an OTP application you configure; evoq is more closely coupled to the OTP supervision tree design pattern where each domain vertical owns its own supervisor.
Where evoq + ReckonDB Leads
DCB: the cross-aggregate consistency primitive
Commanded has no built-in answer to cross-aggregate consistency. The canonical approaches are sagas (process managers with compensation), set-based stream partitioning, or dedicated uniqueness aggregates. All are workarounds.
evoq + ReckonDB exposes the store’s DCB primitive directly. When you need to enforce that a username is unique across all users:
%% In the maybe_register_user handler
Filter = {any_of, [<<"username:", Username/binary>>]},
{ok, Ctx} = reckon_dcb:read_context(StoreId, Filter),
case username_taken(Ctx) of
true -> {error, username_taken};
false ->
reckon_dcb:append_if_no_match(StoreId, Filter, Ctx#ctx.position, Events)
end
No saga. No eventual consistency window. Atomic at the Raft consensus level.
Indexed cross-stream reads
Commanded/EventStore (PostgreSQL backend) has no indexed cross-stream reads. To
answer “give me all order_placed_v1 events” you either project them into a read
model first or do a full scan.
ReckonDB indexes at append time; evoq exposes those indexes in event queries:
%% Direct indexed read, no projection needed
reckon_streams:read_by_event_type(StoreId, <<"order_placed_v1">>)
reckon_streams:read_by_tags(StoreId, [<<"tenant:acme">>])
Store-level HA without external dependencies
commanded/eventstore uses PostgreSQL for storage and PostgreSQL advisory locks to ensure each subscription runs on at most one node. HA is PostgreSQL HA, streaming replication, managed failover.
ReckonDB’s Ra cluster is Raft inside the BEAM. Membership, leader election, and log replication are part of the store, supervised by OTP trees. There is no external coordinator to operate.
Polyglot access
Commanded is Elixir-only. There is no gRPC API, no Go client, no Python client. ReckonDB’s gRPC API (via reckon-gateway) gives any language access to the same event store that your evoq aggregates write to.
Tamper resistance
Configurable HMAC + hash chain per store. Commanded/EventStore has no equivalent.
Where Commanded Leads
Competing consumers today
commanded/eventstore supports competing consumers via concurrency_limit on
persistent subscriptions, with round-robin distribution and optional
partition_by for ordered delivery within a partition. This is production-ready.
evoq’s competing consumer support is in development.
Maturity and ecosystem depth
Commanded has been in production since ~2016. There is a large body of examples,
blog posts, and community knowledge. The Ash framework integration
(ash_commanded) brings a declarative DSL that reduces CQRS boilerplate
significantly.
Zero new infrastructure if you’re on PostgreSQL
If your stack already includes PostgreSQL, commanded_eventstore_adapter adds a
Hex package and a schema migration, nothing else. ReckonDB adds a Ra cluster
(embedded, but a new runtime concept) to your infrastructure.
EventStoreDB adapter option
If you need EventStoreDB compatibility, Commanded’s commanded_extreme adapter
gives you that path. evoq is currently reckon-db + mem-evoq only.
Migration Path
Because both frameworks follow the same adapter boundary, migration between them is straightforward at the framework level. The harder part is the store migration:
-
Commanded → evoq: Aggregates, process managers, event handlers, and projections are framework code and do not change structurally. Swap the adapter config, adapt the handler callback signatures where they differ. Historical events in PostgreSQL need a one-time migration or a parallel-run period.
-
evoq → Commanded: Same story in reverse. The adapter boundary is the seam.
Who Should Choose Which
Choose evoq + ReckonDB if:
- DCB is a requirement (cross-aggregate consistency without sagas)
- You need indexed cross-stream reads without building projections first
- You want polyglot gRPC access to the same event store
- You want the store embedded in the BEAM cluster without external DB dependency
- Tamper resistance is a compliance requirement
Choose Commanded + commanded/eventstore if:
- Competing consumers are a current, blocking requirement
- Your team has significant Commanded knowledge or an existing codebase
- You’re already running PostgreSQL and want zero new infrastructure
-
You need Ash framework integration (
ash_commanded) - Your domain does not require DCB or cross-stream indexed reads