Files
CBDDC/README.md
Joseph Doherty ce727eb30d
All checks were successful
CI / verify (push) Successful in 2m33s
docs: align internal docs to enterprise standards
Add canonical operations/security/access/feature docs and fix path integrity to improve onboarding and incident readiness.
2026-02-20 13:23:55 -05:00

27 KiB
Executable File

CBDDC

Peer-to-Peer Data Synchronization Middleware for .NET

.NET Version

Status

Version Build

CBDDC is not a database - it's a sync layer that plugs into your existing data store and enables automatic peer-to-peer replication across nodes in a mesh network.

Architecture | Quick Start | Integration Guide | Documentation


Table of Contents


Overview

CBDDC is a lightweight, embeddable data synchronization middleware for .NET. It observes changes in your database via Change Data Capture (CDC), records them in an append-only Oplog, and replicates them across nodes connected via a P2P mesh network.

Your application continues to read and write to its database as usual. CBDDC works in the background.

[LAN] Designed for Local Area Networks (LAN)
Built for trusted environments: offices, retail stores, edge deployments. Cross-platform (Windows, Linux, macOS).

[Cloud] Cloud Ready
ASP.NET Core hosting for controlled, server-side deployments.


Ownership and Support

  • Owning team: CBDDC Core Maintainers
  • Issue reporting: GitHub Issues
  • Usage/support questions: GitHub Discussions
  • Incident escalation for active deployments: Follow your local on-call process first, then attach CBDDC diagnostics from docs/runbook.md

Architecture

+---------------------------------------------------+
|                  Your Application                 |
|  db.Users.InsertAsync(user)                       |
|  db.Users.Find(u => u.Age > 18)                   |
+---------------------------------------------------+
               | uses your DbContext directly
+---------------------------------------------------+
|             Your Database (BLite)                 |
|  +---------------------------------------------+  |
|  |  Users    |  Orders    |  Products    | ...   |
|  +---------------------------------------------+  |
|        | CDC (Change Data Capture)                |
|        |                                          |
|  +---------------------------------------------+  |
|  |  CBDDC Sync Engine                       |  |
|  |  - Oplog (append-only hash-chained journal)|  |
|  |  - Vector Clock (causal ordering)          |  |
|  |  - Conflict Resolution (LWW / Custom Merge)|  |
|  +---------------------------------------------+  |
+---------------------------------------------------+
              | P2P Network (TCP + UDP Discovery)
+---------------------------------------------------+
|           Other Nodes (same setup)                |
|  Node-A <-----> Node-B <-----> Node-C             |
+---------------------------------------------------+

Core Concepts

Concept Description
Oplog Append-only journal of changes, hash-chained per node for integrity
Vector Clock Tracks causal ordering - knows who has what across the mesh
CDC Change Data Capture - watches your registered collections for local writes
Document Store Your bridge class - maps between your entities and the sync engine
Conflict Resolution Pluggable strategy (Last-Write-Wins or custom recursive merge)
VectorClockService Shared singleton keeping the Vector Clock in sync between CDC and OplogStore

Sync Flow

Local Write -> CDC Trigger -> OplogEntry Created -> VectorClock Updated
                                                        |
                                                        v
                                                  SyncOrchestrator
                                                  (gossip every 2s)
                                                        |
                                              +---------+----------+
                                              |                    |
                                          Push changes       Pull changes
                                          to peers           from peers
                                              |                    |
                                              v                    v
                                        Remote node          Apply to local
                                        applies via          OplogStore +
                                        ApplyBatchAsync      DocumentStore

Key Features

[Select] Selective Collection Sync

Only collections registered via WatchCollection() are tracked. Your database can have hundreds of tables - only the ones you opt-in participate in replication.

[Gossip] Interest-Aware Gossip

Nodes advertise which collections they sync. The orchestrator prioritizes peers sharing common interests, reducing unnecessary traffic.

[Offline] Offline First

  • Read/write operations work offline - they're direct database operations
  • Automatic sync when peers reconnect
  • Oplog-based gap recovery and snapshot fallback

[Secure] Secure Networking

  • Noise Protocol handshake with ECDH key exchange
  • AES-256 encryption for data in transit
  • HMAC authentication
  • Brotli compression for bandwidth efficiency

[Conflict] Conflict Resolution

  • Last Write Wins (LWW) - default, HLC timestamp-based
  • Recursive Merge - deep JSON merge for concurrent edits
  • Custom - implement IConflictResolver for your business logic

[Cloud] Cloud Infrastructure

  • ASP.NET Core hosting (single-cluster mode)
  • BLite embedded persistence
  • shared-token authentication

Installation

Packages

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+)
CBDDC.Network TCP sync, UDP discovery, Protobuf protocol (.NET Standard 2.0+)
# BLite (embedded document DB)
dotnet add package ZB.MOM.WW.CBDDC.Core
dotnet add package ZB.MOM.WW.CBDDC.Persistence
dotnet add package CBDDC.Network

Prerequisites

Before onboarding a new node, verify:

  • .NET SDK/runtime required by selected packages (recommended: .NET 8+)
  • Network access for configured TCP/UDP sync ports
  • Persistent storage path with write permissions for database and backups
  • Access to deployment secrets (cluster AuthToken, connection strings, environment-specific config)
  • Ability to run health checks (/health in hosted mode)

Configuration and Secrets

CBDDC runtime behavior should be configured from environment-specific settings, not hardcoded values.

  • Required runtime settings:
    • NodeId
    • TcpPort (and UdpPort when discovery is enabled)
    • AuthToken (secret; do not commit)
    • Persistence connection/database path
  • Secret handling guidance:
    • Keep secrets in a secret manager or protected environment variables
    • Rotate tokens during maintenance windows and validate mesh convergence after rotation
    • Avoid exposing tokens in logs, screenshots, or troubleshooting output

See docs/security.md and docs/production-hardening.md for detailed control guidance.


Build, Test, and Quality Gates

Run these checks before merge or release:

dotnet restore
dotnet build
dotnet test

Recommended release gates:

  • Unit/integration tests pass
  • Health endpoint reports healthy in staging after deployment
  • No unresolved high-severity issues in deployment or runbook checklists

Deployment and Rollback

Canonical deployment procedures are documented in:

  • docs/deployment.md (promotion flow, validation gates, rollback path)
  • docs/deployment-lan.md (LAN deployment details)
  • docs/deployment-modes.md (hosted mode behavior)
  • docs/production-hardening.md (operational hardening controls)

Operations and Incident Response

Use docs/runbook.md as the primary operational runbook. It includes:

  • Alert triage sequence
  • Escalation path and severity mapping
  • Recovery and verification steps
  • Post-incident follow-up expectations

For peer lifecycle incidents, use docs/peer-deprecation-removal-runbook.md.


Security and Compliance Posture

  • Primary protections: authenticated peer communication and encrypted transport options in CBDDC networking components
  • Sensitive data handling: classify data by environment and apply least privilege on runtime access
  • Operational controls: hardening, key rotation, and monitoring requirements are documented in docs/security.md and docs/access.md

Troubleshooting

Common issue patterns and step-by-step remediation are documented in docs/troubleshooting.md.

High-priority troubleshooting topics:

  • Peer unreachable or lagging confirmation state
  • Persistence faults and backup restore flow
  • Authentication mismatch or configuration drift

Change Governance

  • Use short-lived branches and pull requests for every change
  • Require review before merge to protected branches
  • Run build/test/health validation before release
  • Record release-impacting changes in CHANGELOG.md and link related deployment notes

Quick Start

1. Define Your Database Context

public class MyDbContext : CBDDCDocumentDbContext
{
    public DocumentCollection<string, Customer> Customers { get; private set; }
    public DocumentCollection<string, Order> Orders { get; private set; }

    public MyDbContext(string dbPath) : base(dbPath) { }
}

2. Create Your Document Store (the Sync Bridge)

This is where you tell CBDDC which collections to sync and how to map between your entities and the sync engine:

public class MyDocumentStore : BLiteDocumentStore<MyDbContext>
{
    public MyDocumentStore(
        MyDbContext context,
        IPeerNodeConfigurationProvider configProvider,
        IVectorClockService vectorClockService,
        ILogger<MyDocumentStore>? logger = null)
        : base(context, configProvider, vectorClockService, logger: logger)
    {
        // Register collections for CDC - only these will be synced
        WatchCollection("Customers", context.Customers, c => c.Id);
        WatchCollection("Orders", context.Orders, o => o.Id);
    }

    // Map incoming sync data back to your entities
    protected override async Task ApplyContentToEntityAsync(
        string collection, string key, JsonElement content, CancellationToken ct)
    {
        switch (collection)
        {
            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);
                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);
                break;
        }
        await _context.SaveChangesAsync(ct);
    }

    protected override 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(),
            _ => null
        };
        return Task.FromResult(entity != null
            ? (JsonElement?)JsonSerializer.SerializeToElement(entity) : null);
    }

    protected override async Task RemoveEntityAsync(
        string collection, string key, CancellationToken ct)
    {
        switch (collection)
        {
            case "Customers": _context.Customers.Delete(key); break;
            case "Orders": _context.Orders.Delete(key); break;
        }
        await _context.SaveChangesAsync(ct);
    }

    protected override Task<IEnumerable<(string Key, JsonElement Content)>>
        GetAllEntitiesAsJsonAsync(string collection, CancellationToken ct)
    {
        IEnumerable<(string, JsonElement)> result = collection switch
        {
            "Customers" => _context.Customers.FindAll()
                .Select(c => (c.Id, JsonSerializer.SerializeToElement(c))),
            "Orders" => _context.Orders.FindAll()
                .Select(o => (o.Id, JsonSerializer.SerializeToElement(o))),
            _ => Enumerable.Empty<(string, JsonElement)>()
        };
        return Task.FromResult(result);
    }
}

3. Wire It Up

var builder = Host.CreateApplicationBuilder();

// Configure the node
builder.Services.AddSingleton<IPeerNodeConfigurationProvider>(
    new StaticPeerNodeConfigurationProvider(new PeerNodeConfiguration
    {
        NodeId = "node-1",
        TcpPort = 8580,
        AuthToken = "my-cluster-secret"
    }));

// Register CBDDC services
builder.Services
    .AddCBDDCCore()
    .AddCBDDCBLite<MyDbContext, MyDocumentStore>(
        sp => new MyDbContext("mydata.blite"))
    .AddCBDDCNetwork<StaticPeerNodeConfigurationProvider>();

await builder.Build().RunAsync();

4. Use Your Database Normally

public class MyService
{
    private readonly MyDbContext _db;

    public MyService(MyDbContext db) => _db = db;

    public async Task CreateCustomer(string name)
    {
        // Write directly - CBDDC handles sync automatically
        await _db.Customers.InsertAsync(
            new Customer { Id = Guid.NewGuid().ToString(), Name = name });
        await _db.SaveChangesAsync();

        // Changes are automatically:
        // 1. Detected via CDC
        // 2. Recorded in the Oplog with HLC timestamp + hash chain
        // 3. Pushed to connected peers via gossip
        // 4. Applied on remote nodes via conflict resolution
    }

    public async Task<List<Customer>> GetYoungCustomers()
    {
        // Read directly from your DB - no CBDDC API
        return _db.Customers.Find(c => c.Age < 30).ToList();
    }
}

Integrating with Your Database

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.

public class MyExistingDbContext : CBDDCDocumentDbContext
{
    // 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) { }
}

Step 2 - Create a DocumentStore

Extend BLiteDocumentStore<T>. This is the bridge between your data model and the sync engine.

public class MyDocumentStore : BLiteDocumentStore<MyExistingDbContext>
{
    public MyDocumentStore(MyExistingDbContext ctx, 
        IPeerNodeConfigurationProvider cfg,
        IVectorClockService vc,
        ILogger<MyDocumentStore>? log = null)
        : base(ctx, cfg, vc, logger: log)
    {
        // Continue to next step...
    }
    
    // Implement abstract methods (see below)...
}

Step 3 - Register only what you need

Call WatchCollection() in the constructor for each collection you want to replicate. Everything else is ignored by the sync engine.

public MyDocumentStore(...)
    : base(ctx, cfg, vc, logger: log)
{
    // Only these 2 collections will be synced across the mesh
    WatchCollection("Products", ctx.Products, p => p.Id);
    WatchCollection("Inventory", ctx.Inventory, i => i.Id);
    
    // All other collections in your DB are local-only
}

Step 4 - Implement the mapping methods

CBDDC stores data as JsonElement. You provide four mapping methods:

Method Purpose
ApplyContentToEntityAsync Write incoming sync data to your entities
GetEntityAsJsonAsync Read your entities for outbound sync
RemoveEntityAsync Handle remote deletes
GetAllEntitiesAsJsonAsync Provide full collection for snapshot sync
protected override async Task ApplyContentToEntityAsync(
    string collection, string key, JsonElement content, CancellationToken ct)
{
    switch (collection)
    {
        case "Products":
            var product = content.Deserialize<Product>()!;
            product.Id = key;
            var existing = _context.Products.Find(p => p.Id == key).FirstOrDefault();
            if (existing != null) _context.Products.Update(product);
            else _context.Products.Insert(product);
            break;
        case "Inventory":
            var inv = content.Deserialize<Inventory>()!;
            inv.Id = key;
            var existingInv = _context.Inventory.Find(i => i.Id == key).FirstOrDefault();
            if (existingInv != null) _context.Inventory.Update(inv);
            else _context.Inventory.Insert(inv);
            break;
    }
    await _context.SaveChangesAsync(ct);
}

protected override Task<JsonElement?> GetEntityAsJsonAsync(
    string collection, string key, CancellationToken ct)
{
    object? entity = collection switch
    {
        "Products" => _context.Products.Find(p => p.Id == key).FirstOrDefault(),
        "Inventory" => _context.Inventory.Find(i => i.Id == key).FirstOrDefault(),
        _ => null
    };
    return Task.FromResult(entity != null
        ? (JsonElement?)JsonSerializer.SerializeToElement(entity) : null);
}

protected override async Task RemoveEntityAsync(
    string collection, string key, CancellationToken ct)
{
    switch (collection)
    {
        case "Products": _context.Products.Delete(key); break;
        case "Inventory": _context.Inventory.Delete(key); break;
    }
    await _context.SaveChangesAsync(ct);
}

protected override Task<IEnumerable<(string Key, JsonElement Content)>>
    GetAllEntitiesAsJsonAsync(string collection, CancellationToken ct)
{
    IEnumerable<(string, JsonElement)> result = collection switch
    {
        "Products" => _context.Products.FindAll()
            .Select(p => (p.Id, JsonSerializer.SerializeToElement(p))),
        "Inventory" => _context.Inventory.FindAll()
            .Select(i => (i.Id, JsonSerializer.SerializeToElement(i))),
        _ => Enumerable.Empty<(string, JsonElement)>()
    };
    return Task.FromResult(result);
}

// Optional: Batch operations for better performance
protected override async Task ApplyContentToEntitiesBatchAsync(
    IEnumerable<(string Collection, string Key, JsonElement Content)> documents,
    CancellationToken ct)
{
    foreach (var (collection, key, content) in documents)
    {
        // Call the single-item method (you can optimize this further)
        await ApplyContentToEntityAsync(collection, key, content, ct);
    }
}

Your existing CRUD code stays unchanged. CBDDC plugs in alongside it.

What Happens Under the Hood

Your Code: db.Users.InsertAsync(user)
                    |
                    v
BLite: SaveChangesAsync()
                    |
                    | CDC fires (WatchCollection observer)
DocumentStore: CreateOplogEntryAsync()
                    |
                    +-> OplogEntry written (hash-chained, HLC timestamped)
                    +-> VectorClockService.Update() -> sync sees it immediately
                              |
                              v
                    SyncOrchestrator (background, every 2s)
                    +-> Compare VectorClocks with peers
                    +-> Push local changes (interest-filtered)
                    +-> Pull remote changes -> ApplyBatchAsync
                              |
                              v
                    Remote DocumentStore: ApplyContentToEntityAsync()
                              |
                              v
                    Remote Database: Updated!

Cloud Deployment

CBDDC supports ASP.NET Core hosting with BLite persistence for cloud deployments.

Example: ASP.NET Core with BLite

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCBDDCCore()
    .AddCBDDCBLite<MyDbContext, MyDocumentStore>(sp => new MyDbContext("cbddc.blite"))
    .AddCBDDCNetwork<MyPeerConfigProvider>();

builder.Services.AddCBDDCHostingSingleCluster(options =>
{
    options.TcpPort = 5001;
});

var app = builder.Build();
app.MapHealthChecks("/health");
await app.RunAsync();

Production Features

Configuration

{
  "CBDDC": {
    "KnownPeers": [
      {
        "NodeId": "gateway-1",
        "Address": "192.168.1.10:5000",
        "Type": "StaticRemote"
      }
    ],
    "RetentionHours": 24,
    "SyncIntervalSeconds": 2
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "CBDDC": "Warning"
    }
  }
}

Health Monitoring

var healthCheck = new CBDDCHealthCheck(store, syncTracker);
var status = await healthCheck.CheckAsync();

Console.WriteLine($"Database: {status.DatabaseHealthy}");
Console.WriteLine($"Network: {status.NetworkHealthy}");
Console.WriteLine($"Peers: {status.ConnectedPeers}");

Resilience

  • Exponential Backoff: Automatic retry for unreachable peers
  • Offline Queue: Buffer local changes when network is down
  • Snapshot Recovery: Fast catch-up after long disconnects
  • Hash Chain Validation: Detect and recover from oplog gaps

Performance

  • VectorClock Cache: In-memory tracking of node states
  • Brotli Compression: 70-80% bandwidth reduction
  • Batch Operations: Group changes for efficient network transfer
  • Interest Filtering: Only sync collections both peers care about

Security

  • Noise Protocol Handshake: XX pattern with ECDH key exchange
  • AES-256 Encryption: Protect data in transit
  • Auth Tokens: Shared secret validation
  • LAN Isolation: Designed for trusted network environments

Use Cases

Ideal For

  • Retail POS Systems - Terminals syncing inventory and sales across a store
  • Office Applications - Shared task lists, calendars, CRM data on LAN
  • Edge Computing - Distributed sensors and controllers at a facility
  • Offline-First Apps - Work without internet, sync when connected
  • Multi-Site Replication - Keep regional databases in sync (over VPN)
  • Existing Database Modernization - Add P2P sync without rewriting your app

Not Designed For

  • Public internet without HTTPS/VPN (P2P mesh mode, use ASP.NET Core mode instead)
  • Sub-millisecond consistency requirements (eventual consistency model, typical convergence < 5s)
  • Unstructured data (designed for document collections with keys)
  • Append-only event logs (oplog pruning after 24h retention)

Documentation

Getting Started

Concepts

Deployment

API


Examples

See samples/ZB.MOM.WW.CBDDC.Sample.Console/ for a complete working example with:

  • Two-node sync simulation on different ports
  • Interactive CLI for testing operations
  • Conflict resolution demo (switchable LWW/Merge)
  • User and TodoList entities with full CRUD
  • Health monitoring and cache inspection
  • Automatic peer discovery via UDP

Run two instances:

# Terminal 1
cd samples/ZB.MOM.WW.CBDDC.Sample.Console
dotnet run -- node-1 8580

# Terminal 2
dotnet run -- node-2 8581

# Create a user on node-1 with command "n"
# Watch it appear on node-2 automatically!

Roadmap

  • Core P2P mesh networking (v0.1.0)
  • Secure networking (ECDH + AES-256) (v0.6.0)
  • Conflict resolution strategies (LWW, Recursive Merge) (v0.6.0)
  • Hash-chain sync with gap recovery (v0.7.0)
  • Brotli compression (v0.7.0)
  • Persistence snapshots (v0.8.6)
  • ASP.NET Core hosting (v0.8.0)
  • VectorClockService refactor (v1.0.0)
  • CDC-aware sync (v1.0.0)
  • Query optimization & advanced indexing
  • Admin UI / monitoring dashboard
  • Mobile support (Xamarin/MAUI)

Contributing

We welcome contributions! CBDDC is open-source and we'd love your help.

How to Contribute

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes with clear commit messages
  4. Add tests for new functionality
  5. Ensure all tests pass (dotnet test)
  6. Submit a Pull Request

Development Setup

# Clone the repository
git clone https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net.git
cd CBDDC.Net

# Restore dependencies
dotnet restore

# Build
dotnet build

# Run all tests (69 tests)
dotnet test

# Run sample
cd samples/ZB.MOM.WW.CBDDC.Sample.Console
dotnet run

Areas We Need Help

  • [Bug] Bug Reports - Found an issue? Let us know!
  • [Docs] Documentation - Improve guides and examples
  • [Feature] Features - Implement items from the roadmap
  • [Test] Testing - Add integration and performance tests
  • [Sample] Samples - Build example applications

Code of Conduct

Be respectful, inclusive, and constructive. We're all here to learn and build great software together.