Replace BLite with Surreal embedded persistence
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m21s

This commit is contained in:
Joseph Doherty
2026-02-22 05:21:53 -05:00
parent 7ebc2cb567
commit 9c2a77dc3c
56 changed files with 6613 additions and 3177 deletions

124
README.md
View File

@@ -76,7 +76,7 @@ Your application continues to read and write to its database as usual. CBDDC wor
+---------------------------------------------------+
| uses your DbContext directly
+---------------------------------------------------+
| Your Database (BLite) |
| Your Database (Surreal embedded RocksDB) |
| +---------------------------------------------+ |
| | Users | Orders | Products | ... |
| +---------------------------------------------+ |
@@ -155,7 +155,7 @@ Nodes advertise which collections they sync. The orchestrator prioritizes peers
### [Cloud] Cloud Infrastructure
- ASP.NET Core hosting (single-cluster mode)
- BLite embedded persistence
- Surreal embedded RocksDB persistence
- shared-token authentication
---
@@ -167,11 +167,11 @@ Nodes advertise which collections they sync. The orchestrator prioritizes peers
| Package | Purpose |
|---------|---------|
| `ZB.MOM.WW.CBDDC.Core` | Interfaces, models, conflict resolution (.NET Standard 2.0+) |
| `ZB.MOM.WW.CBDDC.Persistence` | BLite persistence provider, OplogStore, VectorClockService (.NET 10+) |
| `ZB.MOM.WW.CBDDC.Persistence` | Surreal embedded RocksDB provider, OplogStore, VectorClockService (.NET 10+) |
| `CBDDC.Network` | TCP sync, UDP discovery, Protobuf protocol (.NET Standard 2.0+) |
```bash
# BLite (embedded document DB)
# Surreal embedded (RocksDB)
dotnet add package ZB.MOM.WW.CBDDC.Core
dotnet add package ZB.MOM.WW.CBDDC.Persistence
dotnet add package CBDDC.Network
@@ -285,12 +285,25 @@ High-priority troubleshooting topics:
### 1. Define Your Database Context
```csharp
public class MyDbContext : CBDDCDocumentDbContext
public sealed class MyDbContext
{
public DocumentCollection<string, Customer> Customers { get; private set; }
public DocumentCollection<string, Order> Orders { get; private set; }
public MyDbContext(
ICBDDCSurrealEmbeddedClient embeddedClient,
ICBDDCSurrealSchemaInitializer schemaInitializer)
{
EmbeddedClient = embeddedClient;
SchemaInitializer = schemaInitializer;
Customers = new SampleSurrealCollection<Customer>("customers", c => c.Id, embeddedClient, schemaInitializer);
Orders = new SampleSurrealCollection<Order>("orders", o => o.Id, embeddedClient, schemaInitializer);
}
public MyDbContext(string dbPath) : base(dbPath) { }
public ICBDDCSurrealEmbeddedClient EmbeddedClient { get; }
public ICBDDCSurrealSchemaInitializer SchemaInitializer { get; }
public SampleSurrealCollection<Customer> Customers { get; }
public SampleSurrealCollection<Order> Orders { get; }
public Task SaveChangesAsync(CancellationToken ct = default)
=> SchemaInitializer.EnsureInitializedAsync(ct);
}
```
@@ -299,14 +312,20 @@ public class MyDbContext : CBDDCDocumentDbContext
This is where you tell CBDDC **which collections to sync** and **how to map** between your entities and the sync engine:
```csharp
public class MyDocumentStore : BLiteDocumentStore<MyDbContext>
public sealed class MyDocumentStore : SurrealDocumentStore<MyDbContext>
{
public MyDocumentStore(
MyDbContext context,
IPeerNodeConfigurationProvider configProvider,
IVectorClockService vectorClockService,
ILogger<MyDocumentStore>? logger = null)
: base(context, configProvider, vectorClockService, logger: logger)
: base(
context,
context.EmbeddedClient,
context.SchemaInitializer,
configProvider,
vectorClockService,
logger: logger)
{
// Register collections for CDC - only these will be synced
WatchCollection("Customers", context.Customers, c => c.Id);
@@ -322,34 +341,31 @@ public class MyDocumentStore : BLiteDocumentStore<MyDbContext>
case "Customers":
var customer = content.Deserialize<Customer>()!;
customer.Id = key;
var existing = _context.Customers
.Find(c => c.Id == key).FirstOrDefault();
if (existing != null) _context.Customers.Update(customer);
else _context.Customers.Insert(customer);
var existing = await _context.Customers.FindByIdAsync(key, ct);
if (existing != null) await _context.Customers.UpdateAsync(customer, ct);
else await _context.Customers.InsertAsync(customer, ct);
break;
case "Orders":
var order = content.Deserialize<Order>()!;
order.Id = key;
var existingOrder = _context.Orders
.Find(o => o.Id == key).FirstOrDefault();
if (existingOrder != null) _context.Orders.Update(order);
else _context.Orders.Insert(order);
var existingOrder = await _context.Orders.FindByIdAsync(key, ct);
if (existingOrder != null) await _context.Orders.UpdateAsync(order, ct);
else await _context.Orders.InsertAsync(order, ct);
break;
}
await _context.SaveChangesAsync(ct);
}
protected override Task<JsonElement?> GetEntityAsJsonAsync(
protected override async Task<JsonElement?> GetEntityAsJsonAsync(
string collection, string key, CancellationToken ct)
{
object? entity = collection switch
{
"Customers" => _context.Customers.Find(c => c.Id == key).FirstOrDefault(),
"Orders" => _context.Orders.Find(o => o.Id == key).FirstOrDefault(),
"Customers" => await _context.Customers.FindByIdAsync(key, ct),
"Orders" => await _context.Orders.FindByIdAsync(key, ct),
_ => null
};
return Task.FromResult(entity != null
? (JsonElement?)JsonSerializer.SerializeToElement(entity) : null);
return entity != null ? JsonSerializer.SerializeToElement(entity) : null;
}
protected override async Task RemoveEntityAsync(
@@ -357,8 +373,8 @@ public class MyDocumentStore : BLiteDocumentStore<MyDbContext>
{
switch (collection)
{
case "Customers": _context.Customers.Delete(key); break;
case "Orders": _context.Orders.Delete(key); break;
case "Customers": await _context.Customers.DeleteAsync(key, ct); break;
case "Orders": await _context.Orders.DeleteAsync(key, ct); break;
}
await _context.SaveChangesAsync(ct);
}
@@ -395,9 +411,15 @@ builder.Services.AddSingleton<IPeerNodeConfigurationProvider>(
// Register CBDDC services
builder.Services
.AddSingleton<MyDbContext>()
.AddCBDDCCore()
.AddCBDDCBLite<MyDbContext, MyDocumentStore>(
sp => new MyDbContext("mydata.blite"))
.AddCBDDCSurrealEmbedded<MyDocumentStore>(_ => new CBDDCSurrealEmbeddedOptions
{
Endpoint = "rocksdb://local",
DatabasePath = "data/mydata.rocksdb",
Namespace = "myapp",
Database = "main"
})
.AddCBDDCNetwork<StaticPeerNodeConfigurationProvider>();
await builder.Build().RunAsync();
@@ -442,31 +464,40 @@ If you have an **existing database** and want to add P2P sync:
### Step 1 - Wrap your context
Create a `DbContext` extending `CBDDCDocumentDbContext`. This can wrap your existing collections/tables.
Create a context that exposes your collections and holds the Surreal embedded services.
```csharp
public class MyExistingDbContext : CBDDCDocumentDbContext
public sealed class MyExistingDbContext
{
// Your existing collections
public DocumentCollection<string, Product> Products { get; private set; }
public DocumentCollection<string, Inventory> Inventory { get; private set; }
public MyExistingDbContext(string dbPath) : base(dbPath) { }
public MyExistingDbContext(
ICBDDCSurrealEmbeddedClient embeddedClient,
ICBDDCSurrealSchemaInitializer schemaInitializer)
{
EmbeddedClient = embeddedClient;
SchemaInitializer = schemaInitializer;
Products = new SampleSurrealCollection<Product>("products", p => p.Id, embeddedClient, schemaInitializer);
Inventory = new SampleSurrealCollection<Inventory>("inventory", i => i.Id, embeddedClient, schemaInitializer);
}
public ICBDDCSurrealEmbeddedClient EmbeddedClient { get; }
public ICBDDCSurrealSchemaInitializer SchemaInitializer { get; }
public SampleSurrealCollection<Product> Products { get; }
public SampleSurrealCollection<Inventory> Inventory { get; }
}
```
### Step 2 - Create a DocumentStore
Extend `BLiteDocumentStore<T>`. This is the **bridge** between your data model and the sync engine.
Extend `SurrealDocumentStore<T>`. This is the **bridge** between your data model and the sync engine.
```csharp
public class MyDocumentStore : BLiteDocumentStore<MyExistingDbContext>
public class MyDocumentStore : SurrealDocumentStore<MyExistingDbContext>
{
public MyDocumentStore(MyExistingDbContext ctx,
IPeerNodeConfigurationProvider cfg,
IVectorClockService vc,
ILogger<MyDocumentStore>? log = null)
: base(ctx, cfg, vc, logger: log)
: base(ctx, ctx.EmbeddedClient, ctx.SchemaInitializer, cfg, vc, logger: log)
{
// Continue to next step...
}
@@ -481,7 +512,7 @@ Call `WatchCollection()` in the constructor for each collection you want to repl
```csharp
public MyDocumentStore(...)
: base(ctx, cfg, vc, logger: log)
: base(ctx, ctx.EmbeddedClient, ctx.SchemaInitializer, cfg, vc, logger: log)
{
// Only these 2 collections will be synced across the mesh
WatchCollection("Products", ctx.Products, p => p.Id);
@@ -585,7 +616,7 @@ protected override async Task ApplyContentToEntitiesBatchAsync(
Your Code: db.Users.InsertAsync(user)
|
v
BLite: SaveChangesAsync()
Surreal context write committed
|
| CDC fires (WatchCollection observer)
DocumentStore: CreateOplogEntryAsync()
@@ -610,15 +641,22 @@ DocumentStore: CreateOplogEntryAsync()
## Cloud Deployment
CBDDC supports ASP.NET Core hosting with BLite persistence for cloud deployments.
CBDDC supports ASP.NET Core hosting with Surreal embedded RocksDB persistence for cloud deployments.
### Example: ASP.NET Core with BLite
### Example: ASP.NET Core with Surreal embedded
```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<MyDbContext>();
builder.Services.AddCBDDCCore()
.AddCBDDCBLite<MyDbContext, MyDocumentStore>(sp => new MyDbContext("cbddc.blite"))
.AddCBDDCSurrealEmbedded<MyDocumentStore>(_ => new CBDDCSurrealEmbeddedOptions
{
Endpoint = "rocksdb://local",
DatabasePath = "data/cbddc.rocksdb",
Namespace = "cbddc",
Database = "main"
})
.AddCBDDCNetwork<MyPeerConfigProvider>();
builder.Services.AddCBDDCHostingSingleCluster(options =>
@@ -737,7 +775,7 @@ Console.WriteLine($"Peers: {status.ConnectedPeers}");
### API
- **[API Reference](docs/api-reference.md)** - Complete API documentation
- **[Persistence Providers](docs/persistence-providers.md)** - BLite, custom
- **[Persistence Providers](docs/persistence-providers.md)** - Surreal embedded RocksDB, custom
---