ddd_cqrs_es keeps the domain model portable, but production systems need to be explicit about where durability, idempotency, projection consistency, and realtime delivery guarantees come from.
Stable Production Surface
Use the native SQL adapters when you need production persistence guarantees:| Capability | SQLite | PostgreSQL | MySQL |
|---|---|---|---|
| Event append with optimistic concurrency | Yes | Yes | Yes |
| Atomic idempotent append | Yes | Yes | Yes |
| Durable checkpoints | Yes | Yes | Yes |
| Durable snapshots | Yes | Yes | Yes |
| Schema table-name validation | Yes | Yes | Yes |
Command Execution APIs
The generic repository APIs work with any event store implementation:IdempotencyStore:
execute_idempotent_atomic requires an event store implementing AtomicIdempotentEventStore. The SQLite, PostgreSQL, and MySQL adapters reserve the idempotency key, append events, and save the completed result inside one database transaction. Retrying the same key returns the original committed event stream without appending duplicates.
Async applications can use the matching AsyncRepository::execute_idempotent_atomic path with stores that implement AsyncAtomicIdempotentEventStore.
Durable Snapshots
Snapshots are optional accelerators for long streams. They must never replace the event log.SqliteSnapshotStore, PostgresSnapshotStore, and MySqlSnapshotStore store aggregate state and metadata as JSON. Saving an older revision will not overwrite a newer snapshot for the same aggregate stream.
Projection Consistency
The standard checkpointed projection runner keeps checkpoint state separate from the read model. That is fine for many replayable read models, but a crash between read-model write and checkpoint save can cause duplicate projection work on restart. For read models that need the read-model update and checkpoint update to commit together, implementTransactionalCheckpointedProjection:
TransactionalCheckpointedProjectionRunner. The library provides the runner pattern; your projection owns the database transaction because read-model schemas are application-specific.
Realtime Is Notification, Not Truth
Redis pub/sub, SSE, WebSocket, and polling are delivery mechanisms. They are not the source of truth for event delivery. The durable event store and checkpoint tables are the source of truth. Realtime notifications should wake clients or workers, then those clients or workers should replay durable events or reload read models from the last known sequence. See Database Query Patterns for the query and indexing rules behind that replay model. The counter app follows this rule:realtime=pollingwakes from SSE polling and replays durable events.realtime=redisuses Redis wake messages, then still replays durable events by sequence.db=redismeans Redis is also the experimental durable event/checkpoint/read-model store.
Error Sources and Contracts
EventStoreError keeps stable display messages while exposing sources for backend, connection, serialization, and deserialization failures when the adapter has a concrete cause.
For application and transport mapping, see Error Handling and Transport Mapping. That guide covers preserving RepositoryError and EventStoreError classifications, then adapting them to REST, Leptos server functions, Spin gRPC, and tracing.
Adapter authors should use the contract helpers in ddd_cqrs_es::testing:
assert_event_store_contractassert_event_store_global_replay_contractassert_checkpoint_store_contractassert_idempotency_store_contractassert_snapshot_store_contract