Replace BLite with Surreal embedded persistence
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m21s
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m21s
This commit is contained in:
124
README.md
124
README.md
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user