Skip to content

Commanded, Meet EventSourcingDB

Today we're delighted to celebrate a new arrival in the EventSourcingDB ecosystem: Thomas Gossmann has released a Commanded event store adapter for EventSourcingDB. Commanded is the de-facto framework for building CQRS and Event Sourcing applications in Elixir, and with this adapter it can now store its events in EventSourcingDB. If you work in Elixir and you've wanted to put EventSourcingDB underneath your aggregates and process managers, that option exists today.

What makes this exciting isn't a new line in a dependency file. It's what that line unlocks: an entire language community gains a first-class path to EventSourcingDB, without giving up the framework they already know and trust. Let's look at what the adapter does, how you'd put it to work, and why this kind of growth is exactly what we'd hoped to see.

Commanded, Now on EventSourcingDB

If you've spent any time in the Elixir world, you've almost certainly come across Commanded. It's the most established toolkit for CQRS and Event Sourcing on the BEAM, and it gives you the building blocks you'd expect: aggregates, commands, events, process managers, and projections, all wired together with the kind of explicitness Elixir developers tend to appreciate.

Teams reach for Commanded when they want the discipline of Event Sourcing without assembling all of the plumbing themselves. It handles command dispatch and routing, enforces consistency at the aggregate boundary, runs process managers that coordinate long-lived workflows, and keeps read-model projections up to date as new events arrive. It has been quietly powering production systems for years, and it carries the kind of battle-tested maturity that's hard to fake.

Commanded has always kept one thing deliberately pluggable, namely where the events actually live. Its own EventStore library keeps them in PostgreSQL, a general-purpose relational database pressed into the role rather than a store built for events; there's an in-memory adapter for tests; and through a long-standing dedicated adapter it can use Kurrent, which is a purpose-built event store. The event store is an interface, not a hard-coded dependency, and that's precisely what makes adding another option like this one possible.

The news, in one sentence: you can now point Commanded at EventSourcingDB. You keep your aggregates, your process managers, your projections, and the entire programming model you already know. What changes is the substrate underneath, the place where your events are appended, read back, and streamed to subscribers.

For an Elixir team that has committed to Event Sourcing, that's a meaningful new choice. You're no longer limited to the stores Commanded has supported until now; you can put your events in EventSourcingDB instead, and you can do it without rewriting your application.

What This Unlocks for Elixir Teams

Let's be precise about the appeal, because it isn't that Commanded can finally use a purpose-built event store. With Kurrent, it already could; its default EventStore, by contrast, leans on PostgreSQL, a general-purpose database rather than a dedicated one. What EventSourcingDB adds is a second purpose-built option with a distinctly different character. EventSourcingDB is purpose-built for events, append-only by nature and built around first-class streaming, and it reaches you over open standards rather than a bespoke protocol.

That last point is where it differs most from what came before. Your events are stored as CloudEvents and travel over plain HTTP, so they stay in an open, portable format and the integration surface stays small. Events are organized into hierarchical subjects rather than flat streams, which maps cleanly onto domain hierarchies and multi-tenant setups. None of this makes the existing choices wrong; it simply hands Commanded users one more dedicated store to reach for, and the freedom to pick the trade-offs that suit their project.

Getting started looks the way Elixir developers expect. You add the adapter to your dependencies and configure it alongside the rest of your application:

config :my_app, MyApp,
  event_store: [
    adapter: Commanded.EventStore.Adapters.EventSourcingDB,
    client: [
      api_token: "your-api-token",
      base_url: "http://localhost:3000"
    ],
    stream_prefix: "myapp",
    source: "https://my.app"
  ]

Under the hood, the adapter builds on our official EventSourcingDB Elixir SDK, which we released earlier this year. That detail matters more than it might seem. The adapter didn't have to reinvent how to talk to the database; it could stand on a client library that already speaks the protocol fluently. One layer builds on the next, exactly as it should.

The supported feature set already covers what a real Commanded application needs day to day. You get appending to streams with expected-version checks, reading streams forward, transient subscriptions for live notifications, and persistent subscriptions with checkpointing, acknowledgements, cancellation, and removal. Correlation and causation IDs travel along with your events, so the metadata your projections and process managers depend on stays intact across the round trip.

That last point is easy to underestimate. Subscriptions are what bring an event-sourced system to life, the mechanism that lets a projection rebuild a read model or a process manager react to something that just happened. Persistent subscriptions in particular are what let a projection pick up exactly where it left off after a restart, instead of replaying everything from the beginning, and the adapter handles the checkpointing that makes that possible. Having both transient and persistent subscriptions supported from the start means the adapter isn't a toy; it's aimed at the way Commanded applications are actually built.

How It Fits Together

Here's where a little technical background helps, because two systems meeting always means reconciling two vocabularies. Commanded describes the world in terms of its internal RecordedEvent structure. EventSourcingDB stores events as CloudEvents, the open CNCF specification for describing events in a portable, vendor-neutral way. The adapter's central job is to translate cleanly between the two.

On the way in, a Commanded event becomes a CloudEvent. The event's type is derived from the Elixir module name, the payload becomes the CloudEvent's data, and Commanded's correlation ID, causation ID, and custom metadata are tucked into a dedicated key so nothing gets lost in translation. On the way out, the same mapping runs in reverse, and Commanded sees the familiar structure it expects.

A single stored event ends up looking like this:

{
  "specversion": "1.0",
  "id": "5",
  "source": "https://my.app",
  "subject": "/myapp/bank-account/ACC123",
  "type": "Elixir.AccountOpened",
  "datacontenttype": "application/json",
  "data": {
    "__commanded_metadata__": {
      "correlation_id": "aaa-bbb-ccc",
      "causation_id": "ddd-eee-fff",
      "metadata": {}
    },
    "account_number": "ACC123",
    "initial_balance": 1000
  },
  "time": "2025-04-15T10:00:00Z"
}

Streams map just as deliberately. Commanded thinks in terms of stream identifiers such as bank-account/ACC123, while EventSourcingDB organizes events into hierarchical subjects. The adapter combines a configurable prefix, the aggregate's identity, and its ID into a subject such as /myapp/bank-account/ACC123. That hierarchy isn't merely cosmetic. It gives you natural namespacing for multi-tenant setups, and it lets a subscription to everything fall out from reading a subject tree recursively, rather than requiring a separate mechanism.

Take event identity as an example of the kind of reconciliation involved. Commanded expects a globally increasing event number, as well as a per-stream version that counts 1, 2, 3 within a single stream. EventSourcingDB has its own global ordering and its own notion of identity, rooted in the CloudEvents combination of source and ID. The adapter bridges that gap, deriving the numbers Commanded relies on while preserving the uniqueness guarantees CloudEvents provides. It's the sort of detail you never want to think about again once it's solved.

If you enjoy this kind of detail, Thomas documented the reasoning in a pair of Architecture Decision Records inside the project, one for event semantics and one for stream semantics. They walk through the trade-offs he weighed, including exactly that question of how to reconcile EventSourcingDB's identity model with Commanded's expectations. They're a good read, and they make the design choices easy to follow instead of something you'd otherwise have to reverse-engineer.

There's another detail that signals how seriously the adapter takes correctness. Its test suite uses Testcontainers to spin up a real EventSourcingDB instance for every run, rather than mocking the database away. That means the behavior described in the docs is the behavior that's actually exercised against the real thing, which is precisely the level of rigor you want from the layer that sits between your domain and your data.

Built on Open Standards

Step back for a moment and notice why this integration was possible at all. EventSourcingDB speaks open standards and a plain HTTP API. Events are CloudEvents. The wire protocol is HTTP. There's an official Elixir SDK sitting on top of it. None of that is an accident; it's the result of deliberately choosing interoperability over a closed, proprietary surface.

That openness is what let a community developer connect EventSourcingDB to an entirely different framework, in a different language, without any involvement from us. We didn't have to build this adapter, and we didn't have to bless it in advance. The standards did the heavy lifting, and enabling exactly this kind of thing is the whole point of building on them.

There's a benefit in this for you, too, not just for the person writing an integration. Because your events are stored as CloudEvents, they aren't trapped in a format that only one tool understands. The same events that flow through Commanded today can be read by any other CloudEvents-aware consumer tomorrow, whether that's a different service, an analytics pipeline, or a tool that doesn't exist yet. Open standards are how you keep your options open.

One design decision is worth calling out, because it's a small sign of shared values. The adapter deliberately doesn't implement snapshots. Rather than bolt on a feature that tends to cause more problems than it solves, Thomas points to our own write-up on precisely this topic, The Snapshot Paradox. Seeing someone independently arrive at the same conclusion, and cite the reasoning, is its own quiet kind of validation.

An Ecosystem That Keeps Growing

This release fits a pattern we've watched accelerate over the past year. The JVM gained a serious, production-grade event-driven framework in OpenCQRS. The TypeScript world gained one in Nimbus. And now Elixir has a direct path to EventSourcingDB through Commanded.

Elixir is an especially fitting home for this. The BEAM was built around lightweight processes that communicate by passing messages and that recover cleanly when something fails. That's a worldview that already thinks in terms of events and reactions, of isolated state and explicit communication, rather than shared mutable data hidden behind a lock. Event Sourcing feels less like a pattern you impose on Elixir and more like one the runtime was quietly waiting for, and pairing it with a dedicated event store closes the loop.

Look at the combined picture and a theme emerges. The same events, described the same way, can now sit at the heart of applications written in Java, in TypeScript, and in Elixir, all backed by the same store. That's not three separate integrations so much as one shared idea taking root in three very different communities, and each new language that joins makes the next one easier.

We want to be clear about where this stands today. The adapter is at version 0.0.1. It's early, it's community-driven, and Thomas has been refreshingly candid about the rough edges, including the friction of writing an adapter against an interface whose assumptions were originally shaped by Kurrent. That kind of openness is exactly what early-stage open source needs, and it's how good libraries grow into great ones.

So consider this both a celebration and an invitation. The credit here belongs entirely to Thomas Gossmann, who saw the opportunity, did the work, and shared it with the community. If you write Elixir, the best thing you can do right now is kick the tires.

Go and try it. The adapter lives on GitHub, with full documentation on HexDocs. Add it to a small Commanded project, point it at a local EventSourcingDB instance, and build something with it. If you hit a rough edge, file an issue or open a pull request, because early projects grow fastest when the people using them get involved.

And we'd love to hear how it goes. Whether you're already running Commanded, curious about Event Sourcing in Elixir, or simply want to talk through whether this fits your stack, write to us at hello@thenativeweb.io. Our congratulations and our thanks go to Thomas. This is exactly the kind of ecosystem growth we love to see.