Skip to main content

MongoDB configuration

Transactions and atomic batches

The adapter probes transaction support lazily on the first write. With the default configuration, a deployment without transactions refuses to write instead of silently weakening EventStorageInterface::storeEvents().

If a standalone deployment is an explicit operational decision, opt into the best-effort compensating path:

use DomainFlow\EventSourcingMongoDB\Storage\MongoDbEventStorage;

$storage = new MongoDbEventStorage(
$database,
allowNonAtomicBatches: true,
);

This path validates versions, inserts in order, and removes documents that landed when a later insert fails. A process dying before compensation completes can still leave a partial batch. The normal replica-set path has the stronger transactional guarantee.

The adapter uses w: majority with journaling for event and counter writes. This is fixed by the adapter so an acknowledged event is not accepted with a weaker durability setting by accident.

BSON event documents

Events are stored one per document in the events collection. The payload and metadata are native BSON documents, not JSON strings. The standard fields are event_id, aggregate_id, event_class, version, occurred_on, global_position, payload, and metadata.

Use MongoDbEventEntryFactory when you need a custom event factory or an EventTypeRegistry:

use DomainFlow\EventSourcing\Event\EventTypeRegistry;
use DomainFlow\EventSourcingMongoDB\Storage\MongoDbEventEntryFactory;

$types = EventTypeRegistry::fromClasses([OrderPlaced::class]);
$entryFactory = new MongoDbEventEntryFactory(
eventTypes: $types,
);

$storage = new MongoDbEventStorage($database, $entryFactory);

An event with getDatabaseFields() can add top-level document fields. Those fields are adapter data, not part of the event payload.

Indexes and ordering

The schema manager creates a unique (aggregate_id, version) index for event concurrency and a unique global_position index for global reads. Events in an aggregate are read by version. Global reads use a monotonic counter and are read through CatchUpReader when concurrent writers must be tolerated.

If custom collection names are used in storage constructors, create equivalent indexes for those collections yourself; MongoDbSchemaManager describes the default collection names.

Snapshots and process managers

MongoDbSnapshotStorage, MongoDbSnapshotHistoryStorage, and MongoDbProcessManagerStorage store state as native BSON documents. Process manager writes use the loaded state version as an optimistic concurrency check. Timeout lookup is supported by an index on timeout.

Transactional outbox

The MongoDB outbox must use the same database as the event storage:

use DomainFlow\EventSourcing\Facade\EventSourcingFacade;
use DomainFlow\EventSourcing\Outbox\OutboxRelay;
use DomainFlow\EventSourcingMongoDB\Outbox\MongoDbOutboxStorage;
use DomainFlow\EventSourcingMongoDB\Storage\MongoDbEventStorage;

$outbox = new MongoDbOutboxStorage($database);
$storage = new MongoDbEventStorage($database, outbox: $outbox);

// Do not pass an inline dispatcher to this facade. The relay is the delivery path.
$facade = new EventSourcingFacade($storage);
$relay = new OutboxRelay($outbox, $dispatcher);

The outbox entry is enrolled in the same MongoDB session as the event transaction. Delivery remains at least once and consumers must be idempotent.