Skip to content

Comments

Add MongoDB storage implementation for EventStore#255

Merged
MPapst merged 5 commits intomainfrom
copilot/build-mongodb-storage
Feb 13, 2026
Merged

Add MongoDB storage implementation for EventStore#255
MPapst merged 5 commits intomainfrom
copilot/build-mongodb-storage

Conversation

Copy link
Contributor

Copilot AI commented Feb 8, 2026

Implements MongoDB storage provider for the EventStore library with full IEventStore and IEventStream interface support.

Implementation

  • MongoDBEventStore - Stream creation/retrieval with duplicate key handling, comprehensive logging
  • MongoDBEventStream - Event/snapshot appending, bidirectional listing, version tracking with logging
  • MongoDBTransactionalBatch - Transaction support for replica sets, automatic fallback to non-transactional for standalone instances with logging
  • Custom serializers - JObjectBsonSerializer for BSON compatibility with JObject and Guid (serialized as strings)
  • Logging - Comprehensive logging using LoggerMessage source generator for all operations
  • Validation - PostConfigure validation for required configuration options
  • Indexing - Optimized indexes on StreamId, Version, and Name fields

Usage

services.AddMongoDBEventStore(options => {
    options.ConnectionString = "mongodb://localhost:27017";
    options.DatabaseName = "EventStore";
});

// Or simplified
services.AddMongoDBEventStore("mongodb://localhost:27017", "EventStore");

Architecture

Two-Collection Design

The implementation uses separate collections for events and metadata:

  • EventStreams Collection - Stores individual event documents and snapshots
  • StreamMetadata Collection - Stores stream metadata (version, creation time, target type)

This separation provides:

  • Performance: Faster stream queries, optimized version tracking, efficient index usage
  • Data Integrity: Atomic stream creation with unique index, consistent version tracking
  • Scalability: Small metadata collection for O(1) lookups, separation of concerns as event streams grow

Technical Notes

  • MongoDBEventStore implements IDisposable for SemaphoreSlim cleanup
  • Transaction failures caught via NotSupportedException for standalone MongoDB with automatic fallback and logging
  • GUID serialization registered globally via BsonSerializer on first instantiation
  • Configuration validation ensures required options (ConnectionString, DatabaseName) are provided
  • Index creation handled lazily to avoid blocking constructor
  • LoggerMessage source generator used for high-performance structured logging
Original prompt

This section details on the original issue you should resolve

<issue_title>MongoDB Implementation</issue_title>
<issue_description>Build a Mongo DB Storage for EventStore</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits February 8, 2026 23:39
Co-authored-by: MPapst <16494676+MPapst@users.noreply.github.com>
Co-authored-by: MPapst <16494676+MPapst@users.noreply.github.com>
Copilot AI changed the title [WIP] Add MongoDB implementation for EventStore Add MongoDB storage implementation for EventStore Feb 8, 2026
Copilot AI requested a review from MPapst February 8, 2026 23:47
Copy link
Collaborator

@MPapst MPapst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot: Check for comments and modify the sources.

In addition add:

  • Logging using LoggerMessage Code Generator

Explain why the Metadata is stored in a separate Collection.

/// <summary>
/// Custom BSON serializer for Newtonsoft.Json JObject
/// </summary>
internal class JObjectSerializer : SerializerBase<JObject>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to JObjectBsonSerializer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to JObjectBsonSerializer in commit 7ce073a.

{
if (_indexesCreated) return;

// Index on StreamId and Version for efficient querying
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an Index on the Name of the Event for future use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added index on the Name field for future event name queries in commit 7ce073a. The index is created in the EnsureIndexesAsync method.

/// <summary>
/// Configuration options for MongoDB EventStore
/// </summary>
public class MongoDBEventStoreOptions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace Default values by string.Empty.
Add a Validation using PostConfigure for the Values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced default values with string.Empty and added PostConfigure validation in commit 7ce073a. The validation ensures ConnectionString and DatabaseName are provided, and sets default values for collection names if not specified.

…ng, add validation, document metadata collection separation

Co-authored-by: MPapst <16494676+MPapst@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 9, 2026

@copilot: Check for comments and modify the sources.

In addition add:...

Added comprehensive logging using LoggerMessage Code Generator in commit 7ce073a. Logging includes:

  • Stream creation and retrieval
  • Event and snapshot appending
  • Transaction operations (with warnings for standalone MongoDB)
  • Index creation

Updated README with detailed explanation of why metadata is stored in a separate collection, covering performance benefits (faster queries, optimized version tracking), data integrity (atomic stream creation, version consistency), and scalability advantages.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • compass.mongodb.com
    • Triggering command: /usr/bin/mongosh mongosh --host 127.0.0.1 --port 27017 --quiet admin t[bot] (dns block)
    • Triggering command: /usr/bin/mongosh mongosh --host 127.0.0.1 --port 27017 --quiet admin cal/bin/git (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from MPapst February 9, 2026 14:40
@MPapst
Copy link
Collaborator

MPapst commented Feb 9, 2026

@copilot please explain why a second collection is used for metadata instead of having all entities in one collection.
What are the benefits of this solution?

Copy link
Contributor Author

Copilot AI commented Feb 9, 2026

@copilot please explain why a second collection is used for metadata instead of having all entities in one collection.
What are the benefits of this solution?

The two-collection design separates EventStreams (individual events/snapshots) from StreamMetadata (stream-level information) for several key reasons:

Performance Benefits:

  1. O(1) Stream Lookups - Checking if a stream exists or getting its current version requires querying only a single small document in StreamMetadata, not scanning through potentially thousands of events
  2. Faster Version Updates - Updating the current version is a single document update in StreamMetadata vs. querying all events to find the max version
  3. Efficient Filtering - The unique index on StreamId in StreamMetadata enables instant lookups without touching the events collection

Data Integrity:

  1. Atomic Stream Creation - The unique index on StreamId in StreamMetadata provides database-level enforcement preventing duplicate streams during concurrent creation attempts
  2. Consistent Version Tracking - A single source of truth for the current version prevents race conditions

Scalability:

  1. Constant Metadata Size - As streams grow to millions of events, the metadata collection stays small (one doc per stream)
  2. Independent Growth - Events collection can grow infinitely without impacting metadata queries
  3. Query Isolation - Operations needing only metadata (existence checks, version queries) never touch the events collection

Alternative (Single Collection):
If everything was in one collection, you'd need to:

  • Query all documents with a given StreamId to find the latest version (expensive)
  • Use an aggregation pipeline or sorting to get current state (slower)
  • Risk version inconsistencies during concurrent appends
  • Lose the ability to enforce stream uniqueness at the database level

This pattern is common in event sourcing (similar to having separate "streams" and "events" tables in SQL).

@MPapst MPapst marked this pull request as ready for review February 11, 2026 08:28
Copy link
Collaborator

@MPapst MPapst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@MPapst MPapst merged commit 0490c83 into main Feb 13, 2026
1 of 2 checks passed
@MPapst MPapst deleted the copilot/build-mongodb-storage branch February 13, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MongoDB Implementation

2 participants