commanded_eventsourcingdb_adapter

Copy Markdown View Source

An Elixir event store adapter that integrates Commanded with EventSourcingDB – a purpose-built database for event sourcing.

Documentation:

Supported Features

  • append_to_stream - Write events to a stream with expected version handling
  • stream_forward - Read events from a stream
  • subscribe - Transient subscriptions for real-time notifications
  • subscribe_to - Persistent subscriptions with checkpointing
  • ack_event - Event acknowledgment for checkpoint updates
  • unsubscribe - Cancel subscriptions
  • delete_subscription - Remove subscriptions and checkpoints
  • ✅ Correlation and causation ID tracking via metadata
  • ✅ CloudEvents format for event storage
  • ❌ Snapshots - ESDB has no snapshot storage/feature. Read more about the snapshot paradox

Installation

The package can be installed by adding commanded_eventsourcingdb_adapter to your list of dependencies in mix.exs:

def deps do
  [
    {:commanded_eventsourcingdb_adapter, "~> 0.0.1"}
  ]
end

Configuration

Configure the adapter in your application config:

# config/config.exs
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"
  ]

Configuration Options

  • :client - Required. ESDB client configuration containing :url and :token.
  • :stream_prefix - Optional. Prefix for stream subjects. Defaults to "".
  • :source - Required. Event source URI for CloudEvents.

CloudEvents

Commanded has its own internal event representation as RecordedEvent whereas EventSourcingDB follows the CloudEvents specification. The adapter maps between the two as described in ADR 0001.

Event Source

Define in your config, see above.

Event ID

The event ID within commanded's RecordedEvent is a concatenation of the source + event id from ESDB: #{event.source}/#{event.id}

Event Types

Commanded has a default type provider, that (de)serializes your module name as event type. In the example below, the event has Elixir.AccountOpened as event type. This is perhaps not the best value for your event type, consider your custom type provider for naming your events types.

Event Subject

Commanded is stream-oriented, which translates to subjects as per CloudEvents spec, ADR 0002 specifies the used semantics.

The subject: /#{stream_prefix}/#{identity_prefix}/#{aggregate_uuid}

Metadata Storage

Commanded stores correlation_id, causation_id, and metadata as part of the event's data field using a special __commanded_metadata__ key:

# Data field stored in ESDB
%{
  "__commanded_metadata__" => %{
    "correlation_id" => "uuid-string",
    "causation_id" => "uuid-string",
    "metadata" => %{"key" => "value"}
  },
  # ... event data fields
}

Sample CloudEvent

This is what an event looks like when stored in EventSourcingDB:

{
  "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",
  "predecessorhash": "0000000000000000000000000000000000000000000000000000000000000000",
  "hash": "abc123..."
}

Testing

Run tests using:

mix test

Tests use Testcontainers to spin up an EventSourcingDB instance.