Storage overview¶
OpStream persists two things:
| What | Where | Interface |
|---|---|---|
| The op log (append-only) | IDocumentStore |
AppendOpAsync / StreamOpsAsync |
| Snapshots (compacted state) | IDocumentStore |
SaveSnapshotAsync / LoadSnapshotAsync |
Plus an optional history store (IHistoryStore) for the
history / milestone subsystem.
Supported backends¶
| Backend | Best for | Setup |
|---|---|---|
| In-memory | Tests, demos | Default |
| EF Core | Existing EF Core code-first apps | UseEfCoreStorage<TContext>() |
| SQL Server | Microsoft-centric stacks | UseSqlServer(connStr) |
| PostgreSQL | OSS / cloud-native | UsePostgreSqlStorage(...) |
| MySQL | LAMP-style stacks | UseMySqlStorage(...) |
| SQLite | Embedded / desktop / single-process | UseSqliteStorage(...) |
| MongoDB | Document-first stacks | UseMongoDbStorage(...) |
| Redis | High-throughput, RAM-resident | UseRedisStorage(...) |
Use* is singleton-style — calling it twice replaces the previous
registration. See Builder API conventions.
Choosing a backend¶
- You already use EF Core → EF Core. Migrations land in your existing context.
- You want maximum write throughput → Redis. Sub-ms appends; persistence via AOF / RDB.
- You want maximum simplicity for a single-server deploy → SQLite. One file, no infrastructure.
- You want operational maturity in production → PostgreSQL or SQL Server.
- Your business data is in MongoDB → MongoDB keeps the op log next to it.
Implementing a custom backend¶
The interface is small:
public interface IDocumentStore
{
Task AppendOpAsync(string documentId, StoredOp op, CancellationToken ct = default);
IAsyncEnumerable<StoredOp> StreamOpsAsync(string documentId, long fromExclusive, CancellationToken ct = default);
Task<DocumentSnapshot?> LoadSnapshotAsync(string documentId, CancellationToken ct = default);
Task SaveSnapshotAsync(string documentId, DocumentSnapshot snapshot, CancellationToken ct = default);
}
Register your implementation with services.AddSingleton<IDocumentStore, MyStore>()
after AddOpStream(), or via a custom builder extension method.
Default warnings¶
The default MemoryDocumentStore is registered as a fallback so
AddOpStream() works out of the box. The DocumentRouter logs a
warning at startup when this default is still active — that's your
signal to plug in a real backend before going live.