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