diff --git a/CBDDC.slnx b/CBDDC.slnx
index 63a69f6..9c7bc09 100644
--- a/CBDDC.slnx
+++ b/CBDDC.slnx
@@ -1,23 +1,23 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Directory.Build.props b/Directory.Build.props
index dc63a45..dc0722a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,7 +1,7 @@
-
- true
- latest
- true
-
+
+ true
+ latest
+ true
+
diff --git a/samples/ZB.MOM.WW.CBDDC.Sample.Console/ConsoleInteractiveService.cs b/samples/ZB.MOM.WW.CBDDC.Sample.Console/ConsoleInteractiveService.cs
index 5d2dafe..1dbc701 100755
--- a/samples/ZB.MOM.WW.CBDDC.Sample.Console/ConsoleInteractiveService.cs
+++ b/samples/ZB.MOM.WW.CBDDC.Sample.Console/ConsoleInteractiveService.cs
@@ -1,37 +1,33 @@
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-using ZB.MOM.WW.CBDDC.Core;
+using Serilog.Context;
using ZB.MOM.WW.CBDDC.Core.Cache;
using ZB.MOM.WW.CBDDC.Core.Diagnostics;
-using ZB.MOM.WW.CBDDC.Core.Sync;
-using ZB.MOM.WW.CBDDC.Core.Storage;
-using ZB.MOM.WW.CBDDC.Network;
-using ZB.MOM.WW.CBDDC.Persistence.BLite;
-using Microsoft.Extensions.DependencyInjection; // For IServiceProvider if needed
-using Serilog.Context;
-using ZB.MOM.WW.CBDDC.Sample.Console;
using ZB.MOM.WW.CBDDC.Core.Network;
+using ZB.MOM.WW.CBDDC.Core.Sync;
+using ZB.MOM.WW.CBDDC.Network;
+using ZB.MOM.WW.CBDDC.Network.Security;
+// For IServiceProvider if needed
namespace ZB.MOM.WW.CBDDC.Sample.Console;
public class ConsoleInteractiveService : BackgroundService
{
- private readonly ILogger _logger;
- private readonly SampleDbContext _db;
- private readonly ICBDDCNode _node;
- private readonly IHostApplicationLifetime _lifetime;
-
-
// Auxiliary services for status/commands
private readonly IDocumentCache _cache;
- private readonly IOfflineQueue _queue;
- private readonly ICBDDCHealthCheck _healthCheck;
- private readonly ISyncStatusTracker _syncTracker;
- private readonly IServiceProvider _serviceProvider;
private readonly IPeerNodeConfigurationProvider _configProvider;
+ private readonly SampleDbContext _db;
+ private readonly ICBDDCHealthCheck _healthCheck;
+ private readonly IHostApplicationLifetime _lifetime;
+ private readonly ILogger _logger;
+ private readonly ICBDDCNode _node;
+ private readonly IOfflineQueue _queue;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly ISyncStatusTracker _syncTracker;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The logger used by the interactive service.
/// The sample database context.
@@ -72,7 +68,7 @@ public class ConsoleInteractiveService : BackgroundService
{
var config = await _configProvider.GetConfiguration();
- System.Console.WriteLine($"--- Interactive Console ---");
+ System.Console.WriteLine("--- Interactive Console ---");
System.Console.WriteLine($"Node ID: {config.NodeId}");
PrintHelp();
@@ -85,7 +81,7 @@ public class ConsoleInteractiveService : BackgroundService
continue;
}
- var input = System.Console.ReadLine();
+ string? input = System.Console.ReadLine();
if (string.IsNullOrEmpty(input)) continue;
try
@@ -118,42 +114,53 @@ public class ConsoleInteractiveService : BackgroundService
System.Console.WriteLine(" [n]ew (auto), [s]pam (5x), [c]ount, [t]odos");
System.Console.WriteLine(" [h]ealth, cac[h]e");
System.Console.WriteLine(" [r]esolver [lww|merge], [demo] conflict");
- }
-
+ }
+
private async Task HandleInput(string input)
{
var config = await _configProvider.GetConfiguration();
if (input.StartsWith("n"))
{
var ts = DateTime.Now.ToString("HH:mm:ss.fff");
- var user = new User { Id = Guid.NewGuid().ToString(), Name = $"User-{ts}", Age = new Random().Next(18, 90), Address = new Address { City = "AutoCity" } };
+ var user = new User
+ {
+ Id = Guid.NewGuid().ToString(), Name = $"User-{ts}", Age = new Random().Next(18, 90),
+ Address = new Address { City = "AutoCity" }
+ };
await _db.Users.InsertAsync(user);
await _db.SaveChangesAsync();
System.Console.WriteLine($"[+] Created {user.Name} with Id: {user.Id}...");
}
else if (input.StartsWith("s"))
{
- for (int i = 0; i < 5; i++)
+ for (var i = 0; i < 5; i++)
{
var ts = DateTime.Now.ToString("HH:mm:ss.fff");
- var user = new User { Id = Guid.NewGuid().ToString(), Name = $"User-{ts}", Age = new Random().Next(18, 90), Address = new Address { City = "SpamCity" } };
+ var user = new User
+ {
+ Id = Guid.NewGuid().ToString(), Name = $"User-{ts}", Age = new Random().Next(18, 90),
+ Address = new Address { City = "SpamCity" }
+ };
await _db.Users.InsertAsync(user);
System.Console.WriteLine($"[+] Created {user.Name} with Id: {user.Id}...");
await Task.Delay(100);
}
+
await _db.SaveChangesAsync();
}
else if (input.StartsWith("c"))
{
- var userCount = _db.Users.FindAll().Count();
- var todoCount = _db.TodoLists.FindAll().Count();
+ int userCount = _db.Users.FindAll().Count();
+ int todoCount = _db.TodoLists.FindAll().Count();
System.Console.WriteLine($"Collection 'Users': {userCount} documents");
System.Console.WriteLine($"Collection 'TodoLists': {todoCount} documents");
}
else if (input.StartsWith("p"))
{
- var alice = new User { Id = Guid.NewGuid().ToString(), Name = "Alice", Age = 30, Address = new Address { City = "Paris" } };
- var bob = new User { Id = Guid.NewGuid().ToString(), Name = "Bob", Age = 25, Address = new Address { City = "Rome" } };
+ var alice = new User
+ { Id = Guid.NewGuid().ToString(), Name = "Alice", Age = 30, Address = new Address { City = "Paris" } };
+ var bob = new User
+ { Id = Guid.NewGuid().ToString(), Name = "Bob", Age = 25, Address = new Address { City = "Rome" } };
await _db.Users.InsertAsync(alice);
await _db.Users.InsertAsync(bob);
await _db.SaveChangesAsync();
@@ -162,17 +169,19 @@ public class ConsoleInteractiveService : BackgroundService
else if (input.StartsWith("g"))
{
System.Console.Write("Enter user Id: ");
- var id = System.Console.ReadLine();
+ string? id = System.Console.ReadLine();
if (!string.IsNullOrEmpty(id))
{
var u = _db.Users.FindById(id);
- System.Console.WriteLine(u != null ? $"Got: {u.Name}, Age {u.Age}, City: {u.Address?.City}" : "Not found");
+ System.Console.WriteLine(u != null
+ ? $"Got: {u.Name}, Age {u.Age}, City: {u.Address?.City}"
+ : "Not found");
}
}
else if (input.StartsWith("d"))
{
System.Console.Write("Enter user Id to delete: ");
- var id = System.Console.ReadLine();
+ string? id = System.Console.ReadLine();
if (!string.IsNullOrEmpty(id))
{
await _db.Users.DeleteAsync(id);
@@ -183,8 +192,8 @@ public class ConsoleInteractiveService : BackgroundService
else if (input.StartsWith("l"))
{
var peers = _node.Discovery.GetActivePeers();
- var handshakeSvc = _serviceProvider.GetService();
- var secureIcon = handshakeSvc != null ? "π" : "π";
+ var handshakeSvc = _serviceProvider.GetService();
+ string secureIcon = handshakeSvc != null ? "π" : "π";
System.Console.WriteLine($"Active Peers ({secureIcon}):");
foreach (var p in peers)
@@ -203,7 +212,7 @@ public class ConsoleInteractiveService : BackgroundService
{
var health = await _healthCheck.CheckAsync();
var syncStatus = _syncTracker.GetStatus();
- var handshakeSvc = _serviceProvider.GetService();
+ var handshakeSvc = _serviceProvider.GetService();
System.Console.WriteLine("=== Health Check ===");
System.Console.WriteLine($"Database: {(health.DatabaseHealthy ? "β" : "β")}");
@@ -216,17 +225,18 @@ public class ConsoleInteractiveService : BackgroundService
if (health.Errors.Any())
{
System.Console.WriteLine("Errors:");
- foreach (var err in health.Errors.Take(3)) System.Console.WriteLine($" - {err}");
+ foreach (string err in health.Errors.Take(3)) System.Console.WriteLine($" - {err}");
}
}
else if (input.StartsWith("ch") || input == "cache")
{
var stats = _cache.GetStatistics();
- System.Console.WriteLine($"=== Cache Stats ===\nSize: {stats.Size}\nHits: {stats.Hits}\nMisses: {stats.Misses}\nRate: {stats.HitRate:P1}");
+ System.Console.WriteLine(
+ $"=== Cache Stats ===\nSize: {stats.Size}\nHits: {stats.Hits}\nMisses: {stats.Misses}\nRate: {stats.HitRate:P1}");
}
else if (input.StartsWith("r") && input.Contains("resolver"))
{
- var parts = input.Split(' ');
+ string[] parts = input.Split(' ');
if (parts.Length > 1)
{
var newResolver = parts[1].ToLower() switch
@@ -240,7 +250,7 @@ public class ConsoleInteractiveService : BackgroundService
{
// Note: Requires restart to fully apply. For demo, we inform user.
System.Console.WriteLine($"β οΈ Resolver changed to {parts[1].ToUpper()}. Restart node to apply.");
- System.Console.WriteLine($" (Current session continues with previous resolver)");
+ System.Console.WriteLine(" (Current session continues with previous resolver)");
}
else
{
@@ -262,7 +272,7 @@ public class ConsoleInteractiveService : BackgroundService
System.Console.WriteLine($"π {list.Name} ({list.Items.Count} items)");
foreach (var item in list.Items)
{
- var status = item.Completed ? "β" : " ";
+ string status = item.Completed ? "β" : " ";
System.Console.WriteLine($" [{status}] {item.Task}");
}
}
@@ -281,8 +291,8 @@ public class ConsoleInteractiveService : BackgroundService
Name = "Shopping List",
Items = new List
{
- new TodoItem { Task = "Buy milk", Completed = false },
- new TodoItem { Task = "Buy bread", Completed = false }
+ new() { Task = "Buy milk", Completed = false },
+ new() { Task = "Buy bread", Completed = false }
}
};
@@ -325,24 +335,20 @@ public class ConsoleInteractiveService : BackgroundService
System.Console.WriteLine($" List: {merged.Name}");
foreach (var item in merged.Items)
{
- var status = item.Completed ? "β" : " ";
+ string status = item.Completed ? "β" : " ";
System.Console.WriteLine($" [{status}] {item.Task}");
}
var resolver = _serviceProvider.GetRequiredService();
- var resolverType = resolver.GetType().Name;
+ string resolverType = resolver.GetType().Name;
System.Console.WriteLine($"\nβΉοΈ Resolution Strategy: {resolverType}");
if (resolverType.Contains("Recursive"))
- {
System.Console.WriteLine(" β Items merged by 'id', both edits preserved");
- }
else
- {
System.Console.WriteLine(" β Last write wins, Node B changes override Node A");
- }
}
System.Console.WriteLine("\nβ Demo complete. Run 'todos' to see all lists.\n");
}
-}
+}
\ No newline at end of file
diff --git a/samples/ZB.MOM.WW.CBDDC.Sample.Console/Program.cs b/samples/ZB.MOM.WW.CBDDC.Sample.Console/Program.cs
index f033c88..356742b 100755
--- a/samples/ZB.MOM.WW.CBDDC.Sample.Console/Program.cs
+++ b/samples/ZB.MOM.WW.CBDDC.Sample.Console/Program.cs
@@ -1,33 +1,26 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using ZB.MOM.WW.CBDDC.Core;
-using ZB.MOM.WW.CBDDC.Core.Storage;
-using ZB.MOM.WW.CBDDC.Core.Cache;
-using ZB.MOM.WW.CBDDC.Core.Sync;
-using ZB.MOM.WW.CBDDC.Core.Diagnostics;
-using ZB.MOM.WW.CBDDC.Core.Resilience;
-using ZB.MOM.WW.CBDDC.Network;
-using ZB.MOM.WW.CBDDC.Network.Security;
-using ZB.MOM.WW.CBDDC.Persistence.BLite;
-using ZB.MOM.WW.CBDDC.Sample.Console;
using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
using Serilog;
using ZB.MOM.WW.CBDDC.Core.Network;
+using ZB.MOM.WW.CBDDC.Core.Sync;
+using ZB.MOM.WW.CBDDC.Network;
+using ZB.MOM.WW.CBDDC.Persistence.BLite;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
// Local User/Address classes removed in favor of Shared project
-class Program
+internal class Program
{
- static async Task Main(string[] args)
+ private static async Task Main(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
// Configuration
builder.Configuration.SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
+ .AddJsonFile("appsettings.json", true, true);
// Logging
builder.Logging.ClearProviders();
@@ -38,39 +31,36 @@ class Program
.Enrich.WithProperty("Application", "CBDDC.Sample.Console")
.WriteTo.Console());
- var randomPort = new Random().Next(1000, 9999);
+ int randomPort = new Random().Next(1000, 9999);
// Node ID
- string nodeId = args.Length > 0 ? args[0] : ("node-" + randomPort);
+ string nodeId = args.Length > 0 ? args[0] : "node-" + randomPort;
int tcpPort = args.Length > 1 ? int.Parse(args[1]) : randomPort;
// Conflict Resolution Strategy (can be switched at runtime via service replacement)
- var useRecursiveMerge = args.Contains("--merge");
- if (useRecursiveMerge)
- {
- builder.Services.AddSingleton();
- }
+ bool useRecursiveMerge = args.Contains("--merge");
+ if (useRecursiveMerge) builder.Services.AddSingleton();
IPeerNodeConfigurationProvider peerNodeConfigurationProvider = new StaticPeerNodeConfigurationProvider(
new PeerNodeConfiguration
{
NodeId = nodeId,
TcpPort = tcpPort,
- AuthToken = "Test-Cluster-Key",
+ AuthToken = "Test-Cluster-Key"
//KnownPeers = builder.Configuration.GetSection("CBDDC:KnownPeers").Get>() ?? new()
});
- builder.Services.AddSingleton(peerNodeConfigurationProvider);
+ builder.Services.AddSingleton(peerNodeConfigurationProvider);
// Database path
- var dataPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data");
+ string dataPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data");
Directory.CreateDirectory(dataPath);
- var databasePath = Path.Combine(dataPath, $"{nodeId}.blite");
+ string databasePath = Path.Combine(dataPath, $"{nodeId}.blite");
// Register CBDDC Services using Fluent Extensions with BLite, SampleDbContext, and SampleDocumentStore
builder.Services.AddCBDDCCore()
- .AddCBDDCBLite(sp => new SampleDbContext(databasePath))
- .AddCBDDCNetwork(); // useHostedService = true by default
+ .AddCBDDCBLite(sp => new SampleDbContext(databasePath))
+ .AddCBDDCNetwork(); // useHostedService = true by default
builder.Services.AddHostedService(); // Runs the Input Loop
@@ -86,12 +76,7 @@ class Program
private class StaticPeerNodeConfigurationProvider : IPeerNodeConfigurationProvider
{
///
- /// Gets or sets the current peer node configuration.
- ///
- public PeerNodeConfiguration Configuration { get; set; }
-
- ///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The initial peer node configuration.
public StaticPeerNodeConfigurationProvider(PeerNodeConfiguration configuration)
@@ -100,12 +85,17 @@ class Program
}
///
- /// Occurs when the peer node configuration changes.
+ /// Gets or sets the current peer node configuration.
+ ///
+ public PeerNodeConfiguration Configuration { get; }
+
+ ///
+ /// Occurs when the peer node configuration changes.
///
public event PeerNodeConfigurationChangedEventHandler? ConfigurationChanged;
///
- /// Gets the current peer node configuration.
+ /// Gets the current peer node configuration.
///
/// A task that returns the current configuration.
public Task GetConfiguration()
@@ -114,7 +104,7 @@ class Program
}
///
- /// Raises the configuration changed event.
+ /// Raises the configuration changed event.
///
/// The new configuration value.
protected virtual void OnConfigurationChanged(PeerNodeConfiguration newConfig)
@@ -122,5 +112,4 @@ class Program
ConfigurationChanged?.Invoke(this, newConfig);
}
}
-
-}
+}
\ No newline at end of file
diff --git a/samples/ZB.MOM.WW.CBDDC.Sample.Console/README.md b/samples/ZB.MOM.WW.CBDDC.Sample.Console/README.md
index 966279e..4c38012 100755
--- a/samples/ZB.MOM.WW.CBDDC.Sample.Console/README.md
+++ b/samples/ZB.MOM.WW.CBDDC.Sample.Console/README.md
@@ -5,21 +5,25 @@ This sample demonstrates the core features of CBDDC, a distributed peer-to-peer
## Features Demonstrated
### π Primary Keys & Auto-Generation
+
- Automatic GUID generation for entities
- Convention-based key detection (`Id` property)
- `[PrimaryKey]` attribute support
### π― Generic Type-Safe API
+
- `Collection()` for compile-time type safety
- Keyless `Put(entity)` with auto-key extraction
- IntelliSense-friendly operations
### π LINQ Query Support
+
- Expression-based queries
- Paging and sorting
- Complex predicates (>, >=, ==, !=, nested properties)
### π Network Synchronization
+
- UDP peer discovery
- TCP synchronization
- Automatic conflict resolution (Last-Write-Wins)
@@ -35,16 +39,19 @@ dotnet run
### Multi-Node (Peer-to-Peer)
Terminal 1:
+
```bash
dotnet run -- --node-id node1 --tcp-port 5001 --udp-port 6001
```
Terminal 2:
+
```bash
dotnet run -- --node-id node2 --tcp-port 5002 --udp-port 6002
```
Terminal 3:
+
```bash
dotnet run -- --node-id node3 --tcp-port 5003 --udp-port 6003
```
@@ -53,20 +60,20 @@ Changes made on any node will automatically sync to all peers!
## Available Commands
-| Command | Description |
-|---------|-------------|
-| `p` | Put Alice and Bob (auto-generated IDs) |
-| `g` | Get user by ID (prompts for ID) |
-| `d` | Delete user by ID (prompts for ID) |
-| `n` | Create new user with auto-generated ID |
-| `s` | Spam 5 users with auto-generated IDs |
-| `c` | Count total documents |
-| `f` | Demo various Find queries |
-| `f2` | Demo Find with paging (skip/take) |
-| `a` | Demo auto-generated primary keys |
-| `t` | Demo generic typed API |
-| `l` | List active peers |
-| `q` | Quit |
+| Command | Description |
+|---------|----------------------------------------|
+| `p` | Put Alice and Bob (auto-generated IDs) |
+| `g` | Get user by ID (prompts for ID) |
+| `d` | Delete user by ID (prompts for ID) |
+| `n` | Create new user with auto-generated ID |
+| `s` | Spam 5 users with auto-generated IDs |
+| `c` | Count total documents |
+| `f` | Demo various Find queries |
+| `f2` | Demo Find with paging (skip/take) |
+| `a` | Demo auto-generated primary keys |
+| `t` | Demo generic typed API |
+| `l` | List active peers |
+| `q` | Quit |
## Example Session
diff --git a/samples/ZB.MOM.WW.CBDDC.Sample.Console/SampleDbContext.cs b/samples/ZB.MOM.WW.CBDDC.Sample.Console/SampleDbContext.cs
index 9e94934..7943a78 100755
--- a/samples/ZB.MOM.WW.CBDDC.Sample.Console/SampleDbContext.cs
+++ b/samples/ZB.MOM.WW.CBDDC.Sample.Console/SampleDbContext.cs
@@ -2,28 +2,13 @@
using BLite.Core.Metadata;
using BLite.Core.Storage;
using ZB.MOM.WW.CBDDC.Persistence.BLite;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
-public partial class SampleDbContext : CBDDCDocumentDbContext
+public class SampleDbContext : CBDDCDocumentDbContext
{
///
- /// Gets the users collection.
- ///
- public DocumentCollection Users { get; private set; } = null!;
-
- ///
- /// Gets the todo lists collection.
- ///
- public DocumentCollection TodoLists { get; private set; } = null!;
-
- ///
- /// Initializes a new instance of the SampleDbContext class using the specified database file path.
+ /// Initializes a new instance of the SampleDbContext class using the specified database file path.
///
/// The file system path to the database file. Cannot be null or empty.
public SampleDbContext(string databasePath) : base(databasePath)
@@ -31,8 +16,8 @@ public partial class SampleDbContext : CBDDCDocumentDbContext
}
///
- /// Initializes a new instance of the SampleDbContext class using the specified database file path and page file
- /// configuration.
+ /// Initializes a new instance of the SampleDbContext class using the specified database file path and page file
+ /// configuration.
///
/// The file system path to the database file. Cannot be null or empty.
/// The configuration settings for the page file. Cannot be null.
@@ -40,6 +25,16 @@ public partial class SampleDbContext : CBDDCDocumentDbContext
{
}
+ ///
+ /// Gets the users collection.
+ ///
+ public DocumentCollection Users { get; private set; } = null!;
+
+ ///
+ /// Gets the todo lists collection.
+ ///
+ public DocumentCollection TodoLists { get; private set; } = null!;
+
///
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -52,4 +47,4 @@ public partial class SampleDbContext : CBDDCDocumentDbContext
.ToCollection("TodoLists")
.HasKey(t => t.Id);
}
-}
+}
\ No newline at end of file
diff --git a/samples/ZB.MOM.WW.CBDDC.Sample.Console/SampleDocumentStore.cs b/samples/ZB.MOM.WW.CBDDC.Sample.Console/SampleDocumentStore.cs
index 6f867d0..f781176 100755
--- a/samples/ZB.MOM.WW.CBDDC.Sample.Console/SampleDocumentStore.cs
+++ b/samples/ZB.MOM.WW.CBDDC.Sample.Console/SampleDocumentStore.cs
@@ -1,16 +1,15 @@
-using ZB.MOM.WW.CBDDC.Core;
+using System.Text.Json;
+using Microsoft.Extensions.Logging;
using ZB.MOM.WW.CBDDC.Core.Network;
using ZB.MOM.WW.CBDDC.Core.Storage;
using ZB.MOM.WW.CBDDC.Core.Sync;
using ZB.MOM.WW.CBDDC.Persistence.BLite;
-using Microsoft.Extensions.Logging;
-using System.Text.Json;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
///
-/// Document store implementation for CBDDC Sample using BLite persistence.
-/// Extends BLiteDocumentStore to automatically handle Oplog creation via CDC.
+/// Document store implementation for CBDDC Sample using BLite persistence.
+/// Extends BLiteDocumentStore to automatically handle Oplog creation via CDC.
///
public class SampleDocumentStore : BLiteDocumentStore
{
@@ -18,7 +17,7 @@ public class SampleDocumentStore : BLiteDocumentStore
private const string TodoListsCollection = "TodoLists";
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The sample database context.
/// The peer node configuration provider.
@@ -37,6 +36,16 @@ public class SampleDocumentStore : BLiteDocumentStore
WatchCollection(TodoListsCollection, context.TodoLists, t => t.Id);
}
+ #region Helper Methods
+
+ private static JsonElement? SerializeEntity(T? entity) where T : class
+ {
+ if (entity == null) return null;
+ return JsonSerializer.SerializeToElement(entity);
+ }
+
+ #endregion
+
#region Abstract Method Implementations
///
@@ -49,12 +58,10 @@ public class SampleDocumentStore : BLiteDocumentStore
///
protected override async Task ApplyContentToEntitiesBatchAsync(
- IEnumerable<(string Collection, string Key, JsonElement Content)> documents, CancellationToken cancellationToken)
+ IEnumerable<(string Collection, string Key, JsonElement Content)> documents,
+ CancellationToken cancellationToken)
{
- foreach (var (collection, key, content) in documents)
- {
- UpsertEntity(collection, key, content);
- }
+ foreach ((string collection, string key, var content) in documents) UpsertEntity(collection, key, content);
await _context.SaveChangesAsync(cancellationToken);
}
@@ -91,10 +98,10 @@ public class SampleDocumentStore : BLiteDocumentStore
protected override Task GetEntityAsJsonAsync(
string collection, string key, CancellationToken cancellationToken)
{
- return Task.FromResult(collection switch
+ return Task.FromResult(collection switch
{
- UsersCollection => SerializeEntity(_context.Users.Find(u => u.Id == key).FirstOrDefault()),
- TodoListsCollection => SerializeEntity(_context.TodoLists.Find(t => t.Id == key).FirstOrDefault()),
+ UsersCollection => SerializeEntity(_context.Users.Find(u => u.Id == key).FirstOrDefault()),
+ TodoListsCollection => SerializeEntity(_context.TodoLists.Find(t => t.Id == key).FirstOrDefault()),
_ => null
});
}
@@ -111,10 +118,7 @@ public class SampleDocumentStore : BLiteDocumentStore
protected override async Task RemoveEntitiesBatchAsync(
IEnumerable<(string Collection, string Key)> documents, CancellationToken cancellationToken)
{
- foreach (var (collection, key) in documents)
- {
- DeleteEntity(collection, key);
- }
+ foreach ((string collection, string key) in documents) DeleteEntity(collection, key);
await _context.SaveChangesAsync(cancellationToken);
}
@@ -140,25 +144,15 @@ public class SampleDocumentStore : BLiteDocumentStore
{
return await Task.Run(() => collection switch
{
- UsersCollection => _context.Users.FindAll()
- .Select(u => (u.Id, SerializeEntity(u)!.Value)),
-
- TodoListsCollection => _context.TodoLists.FindAll()
- .Select(t => (t.Id, SerializeEntity(t)!.Value)),
-
- _ => Enumerable.Empty<(string, JsonElement)>()
- }, cancellationToken);
- }
-
- #endregion
-
- #region Helper Methods
-
- private static JsonElement? SerializeEntity(T? entity) where T : class
- {
- if (entity == null) return null;
- return JsonSerializer.SerializeToElement(entity);
- }
-
- #endregion
-}
+ UsersCollection => _context.Users.FindAll()
+ .Select(u => (u.Id, SerializeEntity(u)!.Value)),
+
+ TodoListsCollection => _context.TodoLists.FindAll()
+ .Select(t => (t.Id, SerializeEntity(t)!.Value)),
+
+ _ => Enumerable.Empty<(string, JsonElement)>()
+ }, cancellationToken);
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/samples/ZB.MOM.WW.CBDDC.Sample.Console/TodoList.cs b/samples/ZB.MOM.WW.CBDDC.Sample.Console/TodoList.cs
index 8a1f701..f28609c 100755
--- a/samples/ZB.MOM.WW.CBDDC.Sample.Console/TodoList.cs
+++ b/samples/ZB.MOM.WW.CBDDC.Sample.Console/TodoList.cs
@@ -1,23 +1,22 @@
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-
-namespace ZB.MOM.WW.CBDDC.Sample.Console;
-
+using System.ComponentModel.DataAnnotations;
+
+namespace ZB.MOM.WW.CBDDC.Sample.Console;
+
public class TodoList
{
///
- /// Gets or sets the document identifier.
+ /// Gets or sets the document identifier.
///
[Key]
public string Id { get; set; } = Guid.NewGuid().ToString();
///
- /// Gets or sets the list name.
+ /// Gets or sets the list name.
///
public string Name { get; set; } = string.Empty;
///
- /// Gets or sets the todo items in the list.
+ /// Gets or sets the todo items in the list.
///
public List Items { get; set; } = new();
}
@@ -25,17 +24,17 @@ public class TodoList
public class TodoItem
{
///
- /// Gets or sets the task description.
+ /// Gets or sets the task description.
///
public string Task { get; set; } = string.Empty;
///
- /// Gets or sets a value indicating whether the task is completed.
+ /// Gets or sets a value indicating whether the task is completed.
///
public bool Completed { get; set; }
///
- /// Gets or sets the UTC creation timestamp.
+ /// Gets or sets the UTC creation timestamp.
///
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
-}
+}
\ No newline at end of file
diff --git a/samples/ZB.MOM.WW.CBDDC.Sample.Console/User.cs b/samples/ZB.MOM.WW.CBDDC.Sample.Console/User.cs
index ce2787b..cf5eab0 100755
--- a/samples/ZB.MOM.WW.CBDDC.Sample.Console/User.cs
+++ b/samples/ZB.MOM.WW.CBDDC.Sample.Console/User.cs
@@ -1,27 +1,27 @@
-using System.ComponentModel.DataAnnotations;
-
-namespace ZB.MOM.WW.CBDDC.Sample.Console;
-
+using System.ComponentModel.DataAnnotations;
+
+namespace ZB.MOM.WW.CBDDC.Sample.Console;
+
public class User
{
///
- /// Gets or sets the unique user identifier.
+ /// Gets or sets the unique user identifier.
///
[Key]
public string Id { get; set; } = "";
///
- /// Gets or sets the user name.
+ /// Gets or sets the user name.
///
public string? Name { get; set; }
///
- /// Gets or sets the user age.
+ /// Gets or sets the user age.
///
public int Age { get; set; }
///
- /// Gets or sets the user address.
+ /// Gets or sets the user address.
///
public Address? Address { get; set; }
}
@@ -29,7 +29,7 @@ public class User
public class Address
{
///
- /// Gets or sets the city value.
+ /// Gets or sets the city value.
///
public string? City { get; set; }
-}
+}
\ No newline at end of file
diff --git a/samples/ZB.MOM.WW.CBDDC.Sample.Console/ZB.MOM.WW.CBDDC.Sample.Console.csproj b/samples/ZB.MOM.WW.CBDDC.Sample.Console/ZB.MOM.WW.CBDDC.Sample.Console.csproj
index 1b9c5bd..35d85c8 100755
--- a/samples/ZB.MOM.WW.CBDDC.Sample.Console/ZB.MOM.WW.CBDDC.Sample.Console.csproj
+++ b/samples/ZB.MOM.WW.CBDDC.Sample.Console/ZB.MOM.WW.CBDDC.Sample.Console.csproj
@@ -1,41 +1,41 @@
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
- PreserveNewest
-
-
+
+
+ PreserveNewest
+
+
-
- ZB.MOM.WW.CBDDC.Sample.Console
- ZB.MOM.WW.CBDDC.Sample.Console
- ZB.MOM.WW.CBDDC.Sample.Console
- Exe
- net10.0
- enable
- enable
- false
-
+
+ ZB.MOM.WW.CBDDC.Sample.Console
+ ZB.MOM.WW.CBDDC.Sample.Console
+ ZB.MOM.WW.CBDDC.Sample.Console
+ Exe
+ net10.0
+ enable
+ enable
+ false
+
-
+
diff --git a/samples/ZB.MOM.WW.CBDDC.Sample.Console/appsettings.json b/samples/ZB.MOM.WW.CBDDC.Sample.Console/appsettings.json
index de12b5e..52ec43c 100755
--- a/samples/ZB.MOM.WW.CBDDC.Sample.Console/appsettings.json
+++ b/samples/ZB.MOM.WW.CBDDC.Sample.Console/appsettings.json
@@ -1,51 +1,51 @@
{
-"Logging": {
+ "Logging": {
"LogLevel": {
- "Default": "Information",
- "Microsoft": "Warning",
- "System": "Warning",
- "CBDDC": "Information",
- "ZB.MOM.WW.CBDDC.Network.SyncOrchestrator": "Information",
- "ZB.MOM.WW.CBDDC.Core.Storage.OplogCoordinator": "Warning",
- "ZB.MOM.WW.CBDDC.Persistence": "Warning"
- }
-},
- "CBDDC": {
- "Network": {
- "TcpPort": 5001,
- "UdpPort": 6000,
- "AuthToken": "demo-secret-key",
- "ConnectionTimeoutMs": 5000,
- "RetryAttempts": 3,
- "RetryDelayMs": 1000,
- "LocalhostOnly": false
- },
- "Persistence": {
- "DatabasePath": "data/cbddc.db",
- "EnableWalMode": true,
- "CacheSizeMb": 50,
- "EnableAutoBackup": true,
- "BackupPath": "backups/",
- "BusyTimeoutMs": 5000
- },
- "Sync": {
- "SyncIntervalMs": 5000,
- "BatchSize": 100,
- "EnableOfflineQueue": true,
- "MaxQueueSize": 1000
- },
- "Logging": {
- "LogLevel": "Information",
- "LogFilePath": "logs/cbddc.log",
- "MaxLogFileSizeMb": 10,
- "MaxLogFiles": 5
- },
- "KnownPeers": [
- {
- "NodeId": "AspNetSampleNode",
- "Host": "localhost",
- "Port": 6001
- }
- ]
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "System": "Warning",
+ "CBDDC": "Information",
+ "ZB.MOM.WW.CBDDC.Network.SyncOrchestrator": "Information",
+ "ZB.MOM.WW.CBDDC.Core.Storage.OplogCoordinator": "Warning",
+ "ZB.MOM.WW.CBDDC.Persistence": "Warning"
}
+ },
+ "CBDDC": {
+ "Network": {
+ "TcpPort": 5001,
+ "UdpPort": 6000,
+ "AuthToken": "demo-secret-key",
+ "ConnectionTimeoutMs": 5000,
+ "RetryAttempts": 3,
+ "RetryDelayMs": 1000,
+ "LocalhostOnly": false
+ },
+ "Persistence": {
+ "DatabasePath": "data/cbddc.db",
+ "EnableWalMode": true,
+ "CacheSizeMb": 50,
+ "EnableAutoBackup": true,
+ "BackupPath": "backups/",
+ "BusyTimeoutMs": 5000
+ },
+ "Sync": {
+ "SyncIntervalMs": 5000,
+ "BatchSize": 100,
+ "EnableOfflineQueue": true,
+ "MaxQueueSize": 1000
+ },
+ "Logging": {
+ "LogLevel": "Information",
+ "LogFilePath": "logs/cbddc.log",
+ "MaxLogFileSizeMb": 10,
+ "MaxLogFiles": 5
+ },
+ "KnownPeers": [
+ {
+ "NodeId": "AspNetSampleNode",
+ "Host": "localhost",
+ "Port": 6001
+ }
+ ]
+ }
}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Cache/DocumentCache.cs b/src/ZB.MOM.WW.CBDDC.Core/Cache/DocumentCache.cs
index 94693eb..b835d90 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Cache/DocumentCache.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Cache/DocumentCache.cs
@@ -1,76 +1,75 @@
-using System;
-using System.Collections.Generic;
-using ZB.MOM.WW.CBDDC.Core;
-using ZB.MOM.WW.CBDDC.Core.Network;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using ZB.MOM.WW.CBDDC.Core.Network;
namespace ZB.MOM.WW.CBDDC.Core.Cache;
///
-/// LRU cache entry with linked list node.
+/// LRU cache entry with linked list node.
///
-internal class CacheEntry
-{
- ///
- /// Gets the cached document.
- ///
- public Document Document { get; }
-
- ///
- /// Gets the linked-list node used for LRU tracking.
- ///
- public LinkedListNode Node { get; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The cached document.
- /// The linked-list node used for LRU tracking.
- public CacheEntry(Document document, LinkedListNode node)
- {
- Document = document;
- Node = node;
+internal class CacheEntry
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The cached document.
+ /// The linked-list node used for LRU tracking.
+ public CacheEntry(Document document, LinkedListNode node)
+ {
+ Document = document;
+ Node = node;
}
+
+ ///
+ /// Gets the cached document.
+ ///
+ public Document Document { get; }
+
+ ///
+ /// Gets the linked-list node used for LRU tracking.
+ ///
+ public LinkedListNode Node { get; }
}
///
-/// In-memory LRU cache for documents.
+/// In-memory LRU cache for documents.
///
-public class DocumentCache : IDocumentCache
-{
- private readonly IPeerNodeConfigurationProvider _peerNodeConfigurationProvider;
- private readonly Dictionary _cache = new();
- private readonly LinkedList _lru = new();
- private readonly ILogger _logger;
+public class DocumentCache : IDocumentCache
+{
+ private readonly Dictionary _cache = new();
private readonly object _lock = new();
+ private readonly ILogger _logger;
+ private readonly LinkedList _lru = new();
+ private readonly IPeerNodeConfigurationProvider _peerNodeConfigurationProvider;
- // Statistics
- private long _hits = 0;
- private long _misses = 0;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The configuration provider used for cache size limits.
- /// The logger instance.
- public DocumentCache(IPeerNodeConfigurationProvider peerNodeConfigurationProvider, ILogger? logger = null)
- {
- _peerNodeConfigurationProvider = peerNodeConfigurationProvider;
- _logger = logger ?? NullLogger.Instance;
- }
+ // Statistics
+ private long _hits;
+ private long _misses;
- ///
- /// Gets a document from cache.
- ///
- /// The document collection name.
- /// The document key.
- /// A task whose result is the cached document, or if not found.
- public async Task Get(string collection, string key)
- {
- lock (_lock)
- {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration provider used for cache size limits.
+ /// The logger instance.
+ public DocumentCache(IPeerNodeConfigurationProvider peerNodeConfigurationProvider,
+ ILogger? logger = null)
+ {
+ _peerNodeConfigurationProvider = peerNodeConfigurationProvider;
+ _logger = logger ?? NullLogger.Instance;
+ }
+
+ ///
+ /// Gets a document from cache.
+ ///
+ /// The document collection name.
+ /// The document key.
+ /// A task whose result is the cached document, or if not found.
+ public async Task Get(string collection, string key)
+ {
+ lock (_lock)
+ {
var cacheKey = $"{collection}:{key}";
if (_cache.TryGetValue(cacheKey, out var entry))
@@ -90,16 +89,16 @@ public class DocumentCache : IDocumentCache
}
}
- ///
- /// Sets a document in cache.
- ///
- /// The document collection name.
- /// The document key.
- /// The document to cache.
- /// A task that represents the asynchronous operation.
- public async Task Set(string collection, string key, Document document)
- {
- var peerConfig = await _peerNodeConfigurationProvider.GetConfiguration();
+ ///
+ /// Sets a document in cache.
+ ///
+ /// The document collection name.
+ /// The document key.
+ /// The document to cache.
+ /// A task that represents the asynchronous operation.
+ public async Task Set(string collection, string key, Document document)
+ {
+ var peerConfig = await _peerNodeConfigurationProvider.GetConfiguration();
lock (_lock)
{
@@ -118,7 +117,7 @@ public class DocumentCache : IDocumentCache
// Evict if full
if (_cache.Count >= peerConfig.MaxDocumentCacheSize)
{
- var oldest = _lru.Last!.Value;
+ string oldest = _lru.Last!.Value;
_lru.RemoveLast();
_cache.Remove(oldest);
_logger.LogTrace("Evicted oldest cache entry {Key}", oldest);
@@ -130,15 +129,15 @@ public class DocumentCache : IDocumentCache
}
}
- ///
- /// Removes a document from cache.
- ///
- /// The document collection name.
- /// The document key.
- public void Remove(string collection, string key)
- {
- lock (_lock)
- {
+ ///
+ /// Removes a document from cache.
+ ///
+ /// The document collection name.
+ /// The document key.
+ public void Remove(string collection, string key)
+ {
+ lock (_lock)
+ {
var cacheKey = $"{collection}:{key}";
if (_cache.TryGetValue(cacheKey, out var entry))
@@ -151,13 +150,13 @@ public class DocumentCache : IDocumentCache
}
///
- /// Clears all cached documents.
+ /// Clears all cached documents.
///
public void Clear()
{
lock (_lock)
{
- var count = _cache.Count;
+ int count = _cache.Count;
_cache.Clear();
_lru.Clear();
_logger.LogInformation("Cleared cache ({Count} entries)", count);
@@ -165,15 +164,15 @@ public class DocumentCache : IDocumentCache
}
///
- /// Gets cache statistics.
+ /// Gets cache statistics.
///
public (long Hits, long Misses, int Size, double HitRate) GetStatistics()
{
lock (_lock)
{
- var total = _hits + _misses;
- var hitRate = total > 0 ? (double)_hits / total : 0;
+ long total = _hits + _misses;
+ double hitRate = total > 0 ? (double)_hits / total : 0;
return (_hits, _misses, _cache.Count, hitRate);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Cache/IDocumentCache.cs b/src/ZB.MOM.WW.CBDDC.Core/Cache/IDocumentCache.cs
index 81e710d..6a151ff 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Cache/IDocumentCache.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Cache/IDocumentCache.cs
@@ -1,45 +1,44 @@
-ο»Ώusing System.Threading.Tasks;
-
-namespace ZB.MOM.WW.CBDDC.Core.Cache
+ο»Ώusing System.Threading.Tasks;
+
+namespace ZB.MOM.WW.CBDDC.Core.Cache;
+
+///
+/// Defines operations for caching documents by collection and key.
+///
+public interface IDocumentCache
{
///
- /// Defines operations for caching documents by collection and key.
+ /// Clears all cached documents.
///
- public interface IDocumentCache
- {
- ///
- /// Clears all cached documents.
- ///
- void Clear();
+ void Clear();
- ///
- /// Gets a cached document by collection and key.
- ///
- /// The collection name.
- /// The document key.
- /// The cached document, or if not found.
- Task Get(string collection, string key);
+ ///
+ /// Gets a cached document by collection and key.
+ ///
+ /// The collection name.
+ /// The document key.
+ /// The cached document, or if not found.
+ Task Get(string collection, string key);
- ///
- /// Gets cache hit/miss statistics.
- ///
- /// A tuple containing hits, misses, current size, and hit rate.
- (long Hits, long Misses, int Size, double HitRate) GetStatistics();
+ ///
+ /// Gets cache hit/miss statistics.
+ ///
+ /// A tuple containing hits, misses, current size, and hit rate.
+ (long Hits, long Misses, int Size, double HitRate) GetStatistics();
- ///
- /// Removes a cached document by collection and key.
- ///
- /// The collection name.
- /// The document key.
- void Remove(string collection, string key);
+ ///
+ /// Removes a cached document by collection and key.
+ ///
+ /// The collection name.
+ /// The document key.
+ void Remove(string collection, string key);
- ///
- /// Adds or updates a cached document.
- ///
- /// The collection name.
- /// The document key.
- /// The document to cache.
- /// A task that represents the asynchronous operation.
- Task Set(string collection, string key, Document document);
- }
-}
+ ///
+ /// Adds or updates a cached document.
+ ///
+ /// The collection name.
+ /// The document key.
+ /// The document to cache.
+ /// A task that represents the asynchronous operation.
+ Task Set(string collection, string key, Document document);
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/ChangesAppliedEventArgs.cs b/src/ZB.MOM.WW.CBDDC.Core/ChangesAppliedEventArgs.cs
index 26c421c..711d96d 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/ChangesAppliedEventArgs.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/ChangesAppliedEventArgs.cs
@@ -1,24 +1,24 @@
-using System;
-using System.Collections.Generic;
-
-namespace ZB.MOM.WW.CBDDC.Core;
-
-///
-/// Event arguments for when changes are applied to the peer store.
-///
+using System;
+using System.Collections.Generic;
+
+namespace ZB.MOM.WW.CBDDC.Core;
+
+///
+/// Event arguments for when changes are applied to the peer store.
+///
public class ChangesAppliedEventArgs : EventArgs
{
///
- /// Gets the changes that were applied.
- ///
- public IEnumerable Changes { get; }
-
- ///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The changes that were applied.
public ChangesAppliedEventArgs(IEnumerable changes)
{
Changes = changes;
}
-}
+
+ ///
+ /// Gets the changes that were applied.
+ ///
+ public IEnumerable Changes { get; }
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/CBDDCHealthCheck.cs b/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/CBDDCHealthCheck.cs
index 0554c66..d9a50fe 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/CBDDCHealthCheck.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/CBDDCHealthCheck.cs
@@ -1,45 +1,44 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
-using ZB.MOM.WW.CBDDC.Core.Storage;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using ZB.MOM.WW.CBDDC.Core.Storage;
namespace ZB.MOM.WW.CBDDC.Core.Diagnostics;
///
-/// Provides health check functionality.
+/// Provides health check functionality.
///
-public class CBDDCHealthCheck : ICBDDCHealthCheck
-{
- private readonly IOplogStore _store;
- private readonly ISyncStatusTracker _syncTracker;
- private readonly ILogger _logger;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The oplog store used for database health checks.
- /// The tracker that provides synchronization status.
- /// The logger instance.
- public CBDDCHealthCheck(
- IOplogStore store,
- ISyncStatusTracker syncTracker,
- ILogger? logger = null)
- {
- _store = store ?? throw new ArgumentNullException(nameof(store));
- _syncTracker = syncTracker ?? throw new ArgumentNullException(nameof(syncTracker));
- _logger = logger ?? NullLogger.Instance;
- }
+public class CBDDCHealthCheck : ICBDDCHealthCheck
+{
+ private readonly ILogger _logger;
+ private readonly IOplogStore _store;
+ private readonly ISyncStatusTracker _syncTracker;
- ///
- /// Performs a comprehensive health check.
- ///
- /// A token used to cancel the health check.
- public async Task CheckAsync(CancellationToken cancellationToken = default)
- {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The oplog store used for database health checks.
+ /// The tracker that provides synchronization status.
+ /// The logger instance.
+ public CBDDCHealthCheck(
+ IOplogStore store,
+ ISyncStatusTracker syncTracker,
+ ILogger? logger = null)
+ {
+ _store = store ?? throw new ArgumentNullException(nameof(store));
+ _syncTracker = syncTracker ?? throw new ArgumentNullException(nameof(syncTracker));
+ _logger = logger ?? NullLogger.Instance;
+ }
+
+ ///
+ /// Performs a comprehensive health check.
+ ///
+ /// A token used to cancel the health check.
+ public async Task CheckAsync(CancellationToken cancellationToken = default)
+ {
var status = new HealthStatus();
// Check database health
@@ -65,9 +64,7 @@ public class CBDDCHealthCheck : ICBDDCHealthCheck
// Add error messages from sync tracker
foreach (var error in syncStatus.SyncErrors.Take(5)) // Last 5 errors
- {
status.Errors.Add($"{error.Timestamp:yyyy-MM-dd HH:mm:ss} - {error.Message}");
- }
// Add metadata
status.Metadata["TotalDocumentsSynced"] = syncStatus.TotalDocumentsSynced;
@@ -79,4 +76,4 @@ public class CBDDCHealthCheck : ICBDDCHealthCheck
return status;
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/DiagnosticsModels.cs b/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/DiagnosticsModels.cs
index 4b3a1f7..e4f2bc3 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/DiagnosticsModels.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/DiagnosticsModels.cs
@@ -4,145 +4,145 @@ using System.Collections.Generic;
namespace ZB.MOM.WW.CBDDC.Core.Diagnostics;
///
-/// Represents the health status of an CBDDC instance.
+/// Represents the health status of an CBDDC instance.
///
public class HealthStatus
{
///
- /// Indicates if the database is healthy.
+ /// Indicates if the database is healthy.
///
public bool DatabaseHealthy { get; set; }
///
- /// Indicates if network connectivity is available.
+ /// Indicates if network connectivity is available.
///
public bool NetworkHealthy { get; set; }
///
- /// Number of currently connected peers.
+ /// Number of currently connected peers.
///
public int ConnectedPeers { get; set; }
///
- /// Timestamp of the last successful sync operation.
+ /// Timestamp of the last successful sync operation.
///
public DateTime? LastSyncTime { get; set; }
///
- /// List of recent errors.
+ /// List of recent errors.
///
public List Errors { get; set; } = new();
///
- /// Overall health status.
+ /// Overall health status.
///
public bool IsHealthy => DatabaseHealthy && NetworkHealthy && Errors.Count == 0;
///
- /// Additional diagnostic information.
+ /// Additional diagnostic information.
///
public Dictionary Metadata { get; set; } = new();
}
///
-/// Represents the synchronization status.
+/// Represents the synchronization status.
///
public class SyncStatus
{
///
- /// Indicates if the node is currently online.
+ /// Indicates if the node is currently online.
///
public bool IsOnline { get; set; }
///
- /// Timestamp of the last sync operation.
+ /// Timestamp of the last sync operation.
///
public DateTime? LastSyncTime { get; set; }
///
- /// Number of pending operations in the offline queue.
+ /// Number of pending operations in the offline queue.
///
public int PendingOperations { get; set; }
///
- /// List of active peer nodes.
+ /// List of active peer nodes.
///
public List ActivePeers { get; set; } = new();
///
- /// Recent sync errors.
+ /// Recent sync errors.
///
public List SyncErrors { get; set; } = new();
///
- /// Total number of documents synced.
+ /// Total number of documents synced.
///
public long TotalDocumentsSynced { get; set; }
///
- /// Total bytes transferred.
+ /// Total bytes transferred.
///
public long TotalBytesTransferred { get; set; }
}
///
-/// Information about a peer node.
+/// Information about a peer node.
///
public class PeerInfo
{
///
- /// Unique identifier of the peer.
+ /// Unique identifier of the peer.
///
public string NodeId { get; set; } = "";
///
- /// Network address of the peer.
+ /// Network address of the peer.
///
public string Address { get; set; } = "";
///
- /// Last time the peer was seen.
+ /// Last time the peer was seen.
///
public DateTime LastSeen { get; set; }
///
- /// Indicates if the peer is currently connected.
+ /// Indicates if the peer is currently connected.
///
public bool IsConnected { get; set; }
///
- /// Number of successful syncs with this peer.
+ /// Number of successful syncs with this peer.
///
public int SuccessfulSyncs { get; set; }
///
- /// Number of failed syncs with this peer.
+ /// Number of failed syncs with this peer.
///
public int FailedSyncs { get; set; }
}
///
-/// Represents a synchronization error.
+/// Represents a synchronization error.
///
public class SyncError
{
///
- /// Timestamp when the error occurred.
+ /// Timestamp when the error occurred.
///
public DateTime Timestamp { get; set; }
///
- /// Error message.
+ /// Error message.
///
public string Message { get; set; } = "";
///
- /// Peer node ID if applicable.
+ /// Peer node ID if applicable.
///
public string? PeerNodeId { get; set; }
///
- /// Error code.
+ /// Error code.
///
public string? ErrorCode { get; set; }
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/ICBDDCHealthCheck.cs b/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/ICBDDCHealthCheck.cs
index 54761e9..ec8ed9d 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/ICBDDCHealthCheck.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/ICBDDCHealthCheck.cs
@@ -1,15 +1,14 @@
-ο»Ώusing System.Threading;
-using System.Threading.Tasks;
-
-namespace ZB.MOM.WW.CBDDC.Core.Diagnostics
+ο»Ώusing System.Threading;
+using System.Threading.Tasks;
+
+namespace ZB.MOM.WW.CBDDC.Core.Diagnostics;
+
+public interface ICBDDCHealthCheck
{
- public interface ICBDDCHealthCheck
- {
- ///
- /// Performs a health check for the implementing component.
- ///
- /// Cancellation token.
- /// The resulting health status.
- Task CheckAsync(CancellationToken cancellationToken = default);
- }
-}
+ ///
+ /// Performs a health check for the implementing component.
+ ///
+ /// Cancellation token.
+ /// The resulting health status.
+ Task CheckAsync(CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/ISyncStatusTracker.cs b/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/ISyncStatusTracker.cs
index 674526a..a1759d6 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/ISyncStatusTracker.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/ISyncStatusTracker.cs
@@ -1,63 +1,62 @@
-ο»Ώusing System;
-
-namespace ZB.MOM.WW.CBDDC.Core.Diagnostics
+ο»Ώusing System;
+
+namespace ZB.MOM.WW.CBDDC.Core.Diagnostics;
+
+///
+/// Tracks synchronization status and peer health metrics.
+///
+public interface ISyncStatusTracker
{
///
- /// Tracks synchronization status and peer health metrics.
+ /// Removes peer entries that have been inactive longer than the specified threshold.
///
- public interface ISyncStatusTracker
- {
- ///
- /// Removes peer entries that have been inactive longer than the specified threshold.
- ///
- /// The inactivity threshold used to prune peers.
- void CleanupInactivePeers(TimeSpan inactiveThreshold);
+ /// The inactivity threshold used to prune peers.
+ void CleanupInactivePeers(TimeSpan inactiveThreshold);
- ///
- /// Gets the current synchronization status snapshot.
- ///
- /// The current .
- SyncStatus GetStatus();
+ ///
+ /// Gets the current synchronization status snapshot.
+ ///
+ /// The current .
+ SyncStatus GetStatus();
- ///
- /// Records an error encountered during synchronization.
- ///
- /// The error message.
- /// The related peer node identifier, if available.
- /// An optional error code.
- void RecordError(string message, string? peerNodeId = null, string? errorCode = null);
+ ///
+ /// Records an error encountered during synchronization.
+ ///
+ /// The error message.
+ /// The related peer node identifier, if available.
+ /// An optional error code.
+ void RecordError(string message, string? peerNodeId = null, string? errorCode = null);
- ///
- /// Records a failed operation for the specified peer.
- ///
- /// The peer node identifier.
- void RecordPeerFailure(string nodeId);
+ ///
+ /// Records a failed operation for the specified peer.
+ ///
+ /// The peer node identifier.
+ void RecordPeerFailure(string nodeId);
- ///
- /// Records a successful operation for the specified peer.
- ///
- /// The peer node identifier.
- void RecordPeerSuccess(string nodeId);
+ ///
+ /// Records a successful operation for the specified peer.
+ ///
+ /// The peer node identifier.
+ void RecordPeerSuccess(string nodeId);
- ///
- /// Records synchronization throughput metrics.
- ///
- /// The number of synchronized documents.
- /// The number of bytes transferred.
- void RecordSync(int documentCount, long bytesTransferred);
+ ///
+ /// Records synchronization throughput metrics.
+ ///
+ /// The number of synchronized documents.
+ /// The number of bytes transferred.
+ void RecordSync(int documentCount, long bytesTransferred);
- ///
- /// Sets whether the local node is currently online.
- ///
- /// A value indicating whether the node is online.
- void SetOnlineStatus(bool isOnline);
+ ///
+ /// Sets whether the local node is currently online.
+ ///
+ /// A value indicating whether the node is online.
+ void SetOnlineStatus(bool isOnline);
- ///
- /// Updates peer connectivity details.
- ///
- /// The peer node identifier.
- /// The peer network address.
- /// A value indicating whether the peer is connected.
- void UpdatePeer(string nodeId, string address, bool isConnected);
- }
-}
+ ///
+ /// Updates peer connectivity details.
+ ///
+ /// The peer node identifier.
+ /// The peer network address.
+ /// A value indicating whether the peer is connected.
+ void UpdatePeer(string nodeId, string address, bool isConnected);
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/SyncStatusTracker.cs b/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/SyncStatusTracker.cs
index 3eae21a..553f1f2 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/SyncStatusTracker.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Diagnostics/SyncStatusTracker.cs
@@ -1,44 +1,44 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
namespace ZB.MOM.WW.CBDDC.Core.Diagnostics;
///
-/// Tracks synchronization status and provides diagnostics.
+/// Tracks synchronization status and provides diagnostics.
///
-public class SyncStatusTracker : ISyncStatusTracker
-{
- private readonly ILogger _logger;
- private readonly object _lock = new();
-
- private bool _isOnline = false;
- private DateTime? _lastSyncTime;
+public class SyncStatusTracker : ISyncStatusTracker
+{
+ private const int MaxErrorHistory = 50;
private readonly List _activePeers = new();
+ private readonly object _lock = new();
+ private readonly ILogger _logger;
private readonly Queue _recentErrors = new();
- private long _totalDocumentsSynced = 0;
- private long _totalBytesTransferred = 0;
- private const int MaxErrorHistory = 50;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Optional logger instance.
- public SyncStatusTracker(ILogger? logger = null)
- {
- _logger = logger ?? NullLogger.Instance;
- }
-
- ///
- /// Updates online status.
- ///
- /// Whether the node is currently online.
- public void SetOnlineStatus(bool isOnline)
- {
- lock (_lock)
- {
+
+ private bool _isOnline;
+ private DateTime? _lastSyncTime;
+ private long _totalBytesTransferred;
+ private long _totalDocumentsSynced;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Optional logger instance.
+ public SyncStatusTracker(ILogger? logger = null)
+ {
+ _logger = logger ?? NullLogger.Instance;
+ }
+
+ ///
+ /// Updates online status.
+ ///
+ /// Whether the node is currently online.
+ public void SetOnlineStatus(bool isOnline)
+ {
+ lock (_lock)
+ {
if (_isOnline != isOnline)
{
_isOnline = isOnline;
@@ -47,15 +47,15 @@ public class SyncStatusTracker : ISyncStatusTracker
}
}
- ///
- /// Records a successful sync operation.
- ///
- /// The number of documents synchronized.
- /// The number of bytes transferred.
- public void RecordSync(int documentCount, long bytesTransferred)
- {
- lock (_lock)
- {
+ ///
+ /// Records a successful sync operation.
+ ///
+ /// The number of documents synchronized.
+ /// The number of bytes transferred.
+ public void RecordSync(int documentCount, long bytesTransferred)
+ {
+ lock (_lock)
+ {
_lastSyncTime = DateTime.UtcNow;
_totalDocumentsSynced += documentCount;
_totalBytesTransferred += bytesTransferred;
@@ -64,16 +64,16 @@ public class SyncStatusTracker : ISyncStatusTracker
}
}
- ///
- /// Records a sync error.
- ///
- /// The error message.
- /// The related peer node identifier, if available.
- /// The error code, if available.
- public void RecordError(string message, string? peerNodeId = null, string? errorCode = null)
- {
- lock (_lock)
- {
+ ///
+ /// Records a sync error.
+ ///
+ /// The error message.
+ /// The related peer node identifier, if available.
+ /// The error code, if available.
+ public void RecordError(string message, string? peerNodeId = null, string? errorCode = null)
+ {
+ lock (_lock)
+ {
var error = new SyncError
{
Timestamp = DateTime.UtcNow,
@@ -84,25 +84,22 @@ public class SyncStatusTracker : ISyncStatusTracker
_recentErrors.Enqueue(error);
- while (_recentErrors.Count > MaxErrorHistory)
- {
- _recentErrors.Dequeue();
- }
+ while (_recentErrors.Count > MaxErrorHistory) _recentErrors.Dequeue();
_logger.LogWarning("Sync error recorded: {Message} (Peer: {Peer})", message, peerNodeId ?? "N/A");
}
}
- ///
- /// Updates peer information.
- ///
- /// The peer node identifier.
- /// The peer address.
- /// Whether the peer is currently connected.
- public void UpdatePeer(string nodeId, string address, bool isConnected)
- {
- lock (_lock)
- {
+ ///
+ /// Updates peer information.
+ ///
+ /// The peer node identifier.
+ /// The peer address.
+ /// Whether the peer is currently connected.
+ public void UpdatePeer(string nodeId, string address, bool isConnected)
+ {
+ lock (_lock)
+ {
var peer = _activePeers.FirstOrDefault(p => p.NodeId == nodeId);
if (peer == null)
@@ -126,40 +123,34 @@ public class SyncStatusTracker : ISyncStatusTracker
}
}
- ///
- /// Records successful sync with a peer.
- ///
- /// The peer node identifier.
- public void RecordPeerSuccess(string nodeId)
- {
- lock (_lock)
- {
+ ///
+ /// Records successful sync with a peer.
+ ///
+ /// The peer node identifier.
+ public void RecordPeerSuccess(string nodeId)
+ {
+ lock (_lock)
+ {
var peer = _activePeers.FirstOrDefault(p => p.NodeId == nodeId);
- if (peer != null)
- {
- peer.SuccessfulSyncs++;
- }
- }
- }
-
- ///
- /// Records failed sync with a peer.
- ///
- /// The peer node identifier.
- public void RecordPeerFailure(string nodeId)
- {
- lock (_lock)
- {
- var peer = _activePeers.FirstOrDefault(p => p.NodeId == nodeId);
- if (peer != null)
- {
- peer.FailedSyncs++;
- }
+ if (peer != null) peer.SuccessfulSyncs++;
}
}
///
- /// Gets current sync status.
+ /// Records failed sync with a peer.
+ ///
+ /// The peer node identifier.
+ public void RecordPeerFailure(string nodeId)
+ {
+ lock (_lock)
+ {
+ var peer = _activePeers.FirstOrDefault(p => p.NodeId == nodeId);
+ if (peer != null) peer.FailedSyncs++;
+ }
+ }
+
+ ///
+ /// Gets current sync status.
///
public SyncStatus GetStatus()
{
@@ -178,21 +169,18 @@ public class SyncStatusTracker : ISyncStatusTracker
}
}
- ///
- /// Cleans up inactive peers.
- ///
- /// The inactivity threshold used to remove peers.
- public void CleanupInactivePeers(TimeSpan inactiveThreshold)
- {
- lock (_lock)
- {
+ ///
+ /// Cleans up inactive peers.
+ ///
+ /// The inactivity threshold used to remove peers.
+ public void CleanupInactivePeers(TimeSpan inactiveThreshold)
+ {
+ lock (_lock)
+ {
var cutoff = DateTime.UtcNow - inactiveThreshold;
- var removed = _activePeers.RemoveAll(p => p.LastSeen < cutoff);
+ int removed = _activePeers.RemoveAll(p => p.LastSeen < cutoff);
- if (removed > 0)
- {
- _logger.LogInformation("Removed {Count} inactive peers", removed);
- }
+ if (removed > 0) _logger.LogInformation("Removed {Count} inactive peers", removed);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Document.cs b/src/ZB.MOM.WW.CBDDC.Core/Document.cs
index bae037e..6fd8ea9 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Document.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Document.cs
@@ -1,41 +1,15 @@
-using ZB.MOM.WW.CBDDC.Core.Sync;
-using System;
-using System.Text.Json;
-
+using System.Text.Json;
+using ZB.MOM.WW.CBDDC.Core.Sync;
+
namespace ZB.MOM.WW.CBDDC.Core;
///
-/// Represents a stored document and its synchronization metadata.
+/// Represents a stored document and its synchronization metadata.
///
public class Document
{
///
- /// Gets the collection that contains the document.
- ///
- public string Collection { get; private set; }
-
- ///
- /// Gets the document key.
- ///
- public string Key { get; private set; }
-
- ///
- /// Gets the document content.
- ///
- public JsonElement Content { get; private set; }
-
- ///
- /// Gets the timestamp of the latest applied update.
- ///
- public HlcTimestamp UpdatedAt { get; private set; }
-
- ///
- /// Gets a value indicating whether the document is deleted.
- ///
- public bool IsDeleted { get; private set; }
-
- ///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The collection that contains the document.
/// The document key.
@@ -52,32 +26,59 @@ public class Document
}
///
- /// Merges a remote operation into the current document using last-write-wins or a conflict resolver.
+ /// Gets the collection that contains the document.
+ ///
+ public string Collection { get; }
+
+ ///
+ /// Gets the document key.
+ ///
+ public string Key { get; }
+
+ ///
+ /// Gets the document content.
+ ///
+ public JsonElement Content { get; private set; }
+
+ ///
+ /// Gets the timestamp of the latest applied update.
+ ///
+ public HlcTimestamp UpdatedAt { get; private set; }
+
+ ///
+ /// Gets a value indicating whether the document is deleted.
+ ///
+ public bool IsDeleted { get; private set; }
+
+ ///
+ /// Merges a remote operation into the current document using last-write-wins or a conflict resolver.
///
/// The remote operation to merge.
/// An optional conflict resolver for custom merge behavior.
public void Merge(OplogEntry oplogEntry, IConflictResolver? resolver = null)
{
if (oplogEntry == null) return;
- if (Collection != oplogEntry.Collection) return;
- if (Key != oplogEntry.Key) return;
- if (resolver == null)
- {
- //last wins
- if (UpdatedAt <= oplogEntry.Timestamp)
- {
- Content = oplogEntry.Payload ?? default;
- UpdatedAt = oplogEntry.Timestamp;
- IsDeleted = oplogEntry.Operation == OperationType.Delete;
- }
- return;
- }
- var resolutionResult = resolver.Resolve(this, oplogEntry);
- if (resolutionResult.ShouldApply && resolutionResult.MergedDocument != null)
- {
- Content = resolutionResult.MergedDocument.Content;
- UpdatedAt = resolutionResult.MergedDocument.UpdatedAt;
- IsDeleted = resolutionResult.MergedDocument.IsDeleted;
- }
- }
-}
+ if (Collection != oplogEntry.Collection) return;
+ if (Key != oplogEntry.Key) return;
+ if (resolver == null)
+ {
+ //last wins
+ if (UpdatedAt <= oplogEntry.Timestamp)
+ {
+ Content = oplogEntry.Payload ?? default;
+ UpdatedAt = oplogEntry.Timestamp;
+ IsDeleted = oplogEntry.Operation == OperationType.Delete;
+ }
+
+ return;
+ }
+
+ var resolutionResult = resolver.Resolve(this, oplogEntry);
+ if (resolutionResult.ShouldApply && resolutionResult.MergedDocument != null)
+ {
+ Content = resolutionResult.MergedDocument.Content;
+ UpdatedAt = resolutionResult.MergedDocument.UpdatedAt;
+ IsDeleted = resolutionResult.MergedDocument.IsDeleted;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Exceptions/CBDDCExceptions.cs b/src/ZB.MOM.WW.CBDDC.Core/Exceptions/CBDDCExceptions.cs
index 50847e0..c249388 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Exceptions/CBDDCExceptions.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Exceptions/CBDDCExceptions.cs
@@ -1,19 +1,14 @@
-using System;
-
-namespace ZB.MOM.WW.CBDDC.Core.Exceptions;
-
-///
-/// Base exception for all CBDDC-related errors.
-///
-public class CBDDCException : Exception
-{
- ///
- /// Error code for programmatic error handling.
- ///
- public string ErrorCode { get; }
+using System;
+namespace ZB.MOM.WW.CBDDC.Core.Exceptions;
+
+///
+/// Base exception for all CBDDC-related errors.
+///
+public class CBDDCException : Exception
+{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The application-specific error code.
/// The exception message.
@@ -24,7 +19,7 @@ public class CBDDCException : Exception
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The application-specific error code.
/// The exception message.
@@ -34,137 +29,151 @@ public class CBDDCException : Exception
{
ErrorCode = errorCode;
}
-}
-
-///
-/// Exception thrown when network operations fail.
-///
+
+ ///
+ /// Error code for programmatic error handling.
+ ///
+ public string ErrorCode { get; }
+}
+
+///
+/// Exception thrown when network operations fail.
+///
public class NetworkException : CBDDCException
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
public NetworkException(string message)
- : base("NETWORK_ERROR", message) { }
+ : base("NETWORK_ERROR", message)
+ {
+ }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
/// The exception that caused the current exception.
public NetworkException(string message, Exception innerException)
- : base("NETWORK_ERROR", message, innerException) { }
+ : base("NETWORK_ERROR", message, innerException)
+ {
+ }
}
-
-///
-/// Exception thrown when persistence operations fail.
-///
+
+///
+/// Exception thrown when persistence operations fail.
+///
public class PersistenceException : CBDDCException
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
public PersistenceException(string message)
- : base("PERSISTENCE_ERROR", message) { }
+ : base("PERSISTENCE_ERROR", message)
+ {
+ }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
/// The exception that caused the current exception.
public PersistenceException(string message, Exception innerException)
- : base("PERSISTENCE_ERROR", message, innerException) { }
+ : base("PERSISTENCE_ERROR", message, innerException)
+ {
+ }
}
-
-///
-/// Exception thrown when synchronization operations fail.
-///
+
+///
+/// Exception thrown when synchronization operations fail.
+///
public class SyncException : CBDDCException
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
public SyncException(string message)
- : base("SYNC_ERROR", message) { }
+ : base("SYNC_ERROR", message)
+ {
+ }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
/// The exception that caused the current exception.
public SyncException(string message, Exception innerException)
- : base("SYNC_ERROR", message, innerException) { }
+ : base("SYNC_ERROR", message, innerException)
+ {
+ }
}
-
-///
-/// Exception thrown when configuration is invalid.
-///
+
+///
+/// Exception thrown when configuration is invalid.
+///
public class ConfigurationException : CBDDCException
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
public ConfigurationException(string message)
- : base("CONFIG_ERROR", message) { }
+ : base("CONFIG_ERROR", message)
+ {
+ }
}
-
-///
-/// Exception thrown when database corruption is detected.
-///
+
+///
+/// Exception thrown when database corruption is detected.
+///
public class DatabaseCorruptionException : PersistenceException
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
public DatabaseCorruptionException(string message)
- : base(message) { }
+ : base(message)
+ {
+ }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
/// The exception that caused the current exception.
public DatabaseCorruptionException(string message, Exception innerException)
- : base(message, innerException) { }
+ : base(message, innerException)
+ {
+ }
}
-
-///
-/// Exception thrown when a timeout occurs.
-///
+
+///
+/// Exception thrown when a timeout occurs.
+///
public class TimeoutException : CBDDCException
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The operation that timed out.
/// The timeout in milliseconds.
public TimeoutException(string operation, int timeoutMs)
- : base("TIMEOUT_ERROR", $"Operation '{operation}' timed out after {timeoutMs}ms") { }
+ : base("TIMEOUT_ERROR", $"Operation '{operation}' timed out after {timeoutMs}ms")
+ {
+ }
}
-
///
-/// Exception thrown when a document is not found in a collection.
+/// Exception thrown when a document is not found in a collection.
///
public class DocumentNotFoundException : PersistenceException
{
///
- /// Gets the document key that was not found.
- ///
- public string Key { get; }
-
- ///
- /// Gets the collection where the document was searched.
- ///
- public string Collection { get; }
-
- ///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The collection where the document was searched.
/// The document key that was not found.
@@ -174,16 +183,28 @@ public class DocumentNotFoundException : PersistenceException
Collection = collection;
Key = key;
}
+
+ ///
+ /// Gets the document key that was not found.
+ ///
+ public string Key { get; }
+
+ ///
+ /// Gets the collection where the document was searched.
+ ///
+ public string Collection { get; }
}
///
-/// Exception thrown when a concurrency conflict occurs during persistence operations.
+/// Exception thrown when a concurrency conflict occurs during persistence operations.
///
public class CBDDCConcurrencyException : PersistenceException
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
- public CBDDCConcurrencyException(string message) : base(message) { }
-}
+ public CBDDCConcurrencyException(string message) : base(message)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/HlcTimestamp.cs b/src/ZB.MOM.WW.CBDDC.Core/HlcTimestamp.cs
index eeb6163..245491a 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/HlcTimestamp.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/HlcTimestamp.cs
@@ -1,31 +1,31 @@
-using System;
-
-namespace ZB.MOM.WW.CBDDC.Core;
-
-///
-/// Represents a Hybrid Logical Clock timestamp.
-/// Provides a Total Ordering of events in a distributed system.
-/// Implements value semantics and comparable interfaces.
-///
+using System;
+
+namespace ZB.MOM.WW.CBDDC.Core;
+
+///
+/// Represents a Hybrid Logical Clock timestamp.
+/// Provides a Total Ordering of events in a distributed system.
+/// Implements value semantics and comparable interfaces.
+///
public readonly struct HlcTimestamp : IComparable, IComparable, IEquatable
{
///
- /// Gets the physical time component of the timestamp.
+ /// Gets the physical time component of the timestamp.
///
public long PhysicalTime { get; }
///
- /// Gets the logical counter component used to order events with equal physical time.
+ /// Gets the logical counter component used to order events with equal physical time.
///
public int LogicalCounter { get; }
///
- /// Gets the node identifier that produced this timestamp.
+ /// Gets the node identifier that produced this timestamp.
///
public string NodeId { get; }
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The physical time component.
/// The logical counter component.
@@ -35,36 +35,36 @@ public readonly struct HlcTimestamp : IComparable, IComparable, IE
PhysicalTime = physicalTime;
LogicalCounter = logicalCounter;
NodeId = nodeId ?? throw new ArgumentNullException(nameof(nodeId));
- }
-
+ }
+
///
- /// Compares two timestamps to establish a total order.
- /// Order: PhysicalTime -> LogicalCounter -> NodeId (lexicographical tie-breaker).
+ /// Compares two timestamps to establish a total order.
+ /// Order: PhysicalTime -> LogicalCounter -> NodeId (lexicographical tie-breaker).
///
/// The other timestamp to compare with this instance.
///
- /// A value less than zero if this instance is earlier than , zero if they are equal,
- /// or greater than zero if this instance is later than .
+ /// A value less than zero if this instance is earlier than , zero if they are equal,
+ /// or greater than zero if this instance is later than .
///
public int CompareTo(HlcTimestamp other)
{
int timeComparison = PhysicalTime.CompareTo(other.PhysicalTime);
if (timeComparison != 0) return timeComparison;
-
- int counterComparison = LogicalCounter.CompareTo(other.LogicalCounter);
- if (counterComparison != 0) return counterComparison;
-
+
+ int counterComparison = LogicalCounter.CompareTo(other.LogicalCounter);
+ if (counterComparison != 0) return counterComparison;
+
// Use Ordinal comparison for consistent tie-breaking across cultures/platforms
return string.Compare(NodeId, other.NodeId, StringComparison.Ordinal);
}
///
- /// Compares this instance with another object.
+ /// Compares this instance with another object.
///
/// The object to compare with this instance.
///
- /// A value less than zero if this instance is earlier than , zero if equal, or greater
- /// than zero if later.
+ /// A value less than zero if this instance is earlier than , zero if equal, or greater
+ /// than zero if later.
///
public int CompareTo(object? obj)
{
@@ -74,10 +74,10 @@ public readonly struct HlcTimestamp : IComparable, IComparable, IE
}
///
- /// Determines whether this instance and another timestamp are equal.
+ /// Determines whether this instance and another timestamp are equal.
///
/// The other timestamp to compare.
- /// if the timestamps are equal; otherwise, .
+ /// if the timestamps are equal; otherwise, .
public bool Equals(HlcTimestamp other)
{
return PhysicalTime == other.PhysicalTime &&
@@ -96,42 +96,68 @@ public readonly struct HlcTimestamp : IComparable, IComparable, IE
{
unchecked
{
- var hashCode = PhysicalTime.GetHashCode();
- hashCode = (hashCode * 397) ^ LogicalCounter;
- // Ensure HashCode uses the same comparison logic as Equals/CompareTo
- // Handle null NodeId gracefully (possible via default(HlcTimestamp))
- hashCode = (hashCode * 397) ^ (NodeId != null ? StringComparer.Ordinal.GetHashCode(NodeId) : 0);
- return hashCode;
- }
- }
-
- public static bool operator ==(HlcTimestamp left, HlcTimestamp right) => left.Equals(right);
- public static bool operator !=(HlcTimestamp left, HlcTimestamp right) => !left.Equals(right);
-
- // Standard comparison operators making usage in SyncOrchestrator cleaner (e.g., remote > local)
- public static bool operator <(HlcTimestamp left, HlcTimestamp right) => left.CompareTo(right) < 0;
- public static bool operator <=(HlcTimestamp left, HlcTimestamp right) => left.CompareTo(right) <= 0;
- public static bool operator >(HlcTimestamp left, HlcTimestamp right) => left.CompareTo(right) > 0;
- public static bool operator >=(HlcTimestamp left, HlcTimestamp right) => left.CompareTo(right) >= 0;
+ int hashCode = PhysicalTime.GetHashCode();
+ hashCode = (hashCode * 397) ^ LogicalCounter;
+ // Ensure HashCode uses the same comparison logic as Equals/CompareTo
+ // Handle null NodeId gracefully (possible via default(HlcTimestamp))
+ hashCode = (hashCode * 397) ^ (NodeId != null ? StringComparer.Ordinal.GetHashCode(NodeId) : 0);
+ return hashCode;
+ }
+ }
+
+ public static bool operator ==(HlcTimestamp left, HlcTimestamp right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(HlcTimestamp left, HlcTimestamp right)
+ {
+ return !left.Equals(right);
+ }
+
+ // Standard comparison operators making usage in SyncOrchestrator cleaner (e.g., remote > local)
+ public static bool operator <(HlcTimestamp left, HlcTimestamp right)
+ {
+ return left.CompareTo(right) < 0;
+ }
+
+ public static bool operator <=(HlcTimestamp left, HlcTimestamp right)
+ {
+ return left.CompareTo(right) <= 0;
+ }
+
+ public static bool operator >(HlcTimestamp left, HlcTimestamp right)
+ {
+ return left.CompareTo(right) > 0;
+ }
+
+ public static bool operator >=(HlcTimestamp left, HlcTimestamp right)
+ {
+ return left.CompareTo(right) >= 0;
+ }
///
- public override string ToString() => FormattableString.Invariant($"{PhysicalTime}:{LogicalCounter}:{NodeId}");
+ public override string ToString()
+ {
+ return FormattableString.Invariant($"{PhysicalTime}:{LogicalCounter}:{NodeId}");
+ }
///
- /// Parses a timestamp string.
+ /// Parses a timestamp string.
///
/// The string to parse, in the format "PhysicalTime:LogicalCounter:NodeId".
- /// The parsed .
+ /// The parsed .
public static HlcTimestamp Parse(string s)
{
if (string.IsNullOrEmpty(s)) throw new ArgumentNullException(nameof(s));
- var parts = s.Split(':');
- if (parts.Length != 3) throw new FormatException("Invalid HlcTimestamp format. Expected 'PhysicalTime:LogicalCounter:NodeId'.");
- if (!long.TryParse(parts[0], out var physicalTime))
- throw new FormatException("Invalid PhysicalTime component in HlcTimestamp.");
- if (!int.TryParse(parts[1], out var logicalCounter))
- throw new FormatException("Invalid LogicalCounter component in HlcTimestamp.");
- var nodeId = parts[2];
- return new HlcTimestamp(physicalTime, logicalCounter, nodeId);
- }
-}
+ string[] parts = s.Split(':');
+ if (parts.Length != 3)
+ throw new FormatException("Invalid HlcTimestamp format. Expected 'PhysicalTime:LogicalCounter:NodeId'.");
+ if (!long.TryParse(parts[0], out long physicalTime))
+ throw new FormatException("Invalid PhysicalTime component in HlcTimestamp.");
+ if (!int.TryParse(parts[1], out int logicalCounter))
+ throw new FormatException("Invalid LogicalCounter component in HlcTimestamp.");
+ string nodeId = parts[2];
+ return new HlcTimestamp(physicalTime, logicalCounter, nodeId);
+ }
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Management/IPeerManagementService.cs b/src/ZB.MOM.WW.CBDDC.Core/Management/IPeerManagementService.cs
index 87b0855..1d19d54 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Management/IPeerManagementService.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Management/IPeerManagementService.cs
@@ -6,13 +6,13 @@ using ZB.MOM.WW.CBDDC.Core.Network;
namespace ZB.MOM.WW.CBDDC.Core.Management;
///
-/// Service for managing remote peer configurations.
-/// Provides CRUD operations for adding, removing, enabling/disabling remote cloud nodes.
+/// Service for managing remote peer configurations.
+/// Provides CRUD operations for adding, removing, enabling/disabling remote cloud nodes.
///
public interface IPeerManagementService
{
///
- /// Adds a static remote peer with simple authentication.
+ /// Adds a static remote peer with simple authentication.
///
/// Unique identifier for the remote peer.
/// Network address (hostname:port) of the remote peer.
@@ -20,14 +20,14 @@ public interface IPeerManagementService
Task AddStaticPeerAsync(string nodeId, string address, CancellationToken cancellationToken = default);
///
- /// Removes a remote peer configuration.
+ /// Removes a remote peer configuration.
///
/// Unique identifier of the peer to remove.
/// Cancellation token.
Task RemoveRemotePeerAsync(string nodeId, CancellationToken cancellationToken = default);
///
- /// Removes confirmation tracking for a peer and optionally removes static remote configuration.
+ /// Removes confirmation tracking for a peer and optionally removes static remote configuration.
///
/// Unique identifier of the peer to untrack.
/// When true, also removes static remote peer configuration.
@@ -38,23 +38,23 @@ public interface IPeerManagementService
CancellationToken cancellationToken = default);
///
- /// Retrieves all configured remote peers.
+ /// Retrieves all configured remote peers.
///
/// Cancellation token.
/// Collection of remote peer configurations.
Task> GetAllRemotePeersAsync(CancellationToken cancellationToken = default);
///
- /// Enables synchronization with a remote peer.
+ /// Enables synchronization with a remote peer.
///
/// Unique identifier of the peer to enable.
/// Cancellation token.
Task EnablePeerAsync(string nodeId, CancellationToken cancellationToken = default);
///
- /// Disables synchronization with a remote peer (keeps configuration).
+ /// Disables synchronization with a remote peer (keeps configuration).
///
/// Unique identifier of the peer to disable.
/// Cancellation token.
Task DisablePeerAsync(string nodeId, CancellationToken cancellationToken = default);
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Management/PeerManagementService.cs b/src/ZB.MOM.WW.CBDDC.Core/Management/PeerManagementService.cs
index 979e264..57e957b 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Management/PeerManagementService.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Management/PeerManagementService.cs
@@ -2,29 +2,28 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
-using ZB.MOM.WW.CBDDC.Core.Network;
-using ZB.MOM.WW.CBDDC.Core.Storage;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
+using ZB.MOM.WW.CBDDC.Core.Network;
+using ZB.MOM.WW.CBDDC.Core.Storage;
namespace ZB.MOM.WW.CBDDC.Core.Management;
///
-/// Implementation of peer management service.
-/// Provides CRUD operations for managing remote peer configurations.
-///
-/// Remote peer configurations are stored in a synchronized collection and automatically
-/// replicated across all nodes in the cluster. Any change made on one node will be
-/// synchronized to all other nodes through the normal CBDDC sync process.
+/// Implementation of peer management service.
+/// Provides CRUD operations for managing remote peer configurations.
+/// Remote peer configurations are stored in a synchronized collection and automatically
+/// replicated across all nodes in the cluster. Any change made on one node will be
+/// synchronized to all other nodes through the normal CBDDC sync process.
///
public class PeerManagementService : IPeerManagementService
{
- private readonly IPeerConfigurationStore _store;
- private readonly IPeerOplogConfirmationStore _peerOplogConfirmationStore;
private readonly ILogger _logger;
+ private readonly IPeerOplogConfirmationStore _peerOplogConfirmationStore;
+ private readonly IPeerConfigurationStore _store;
///
- /// Initializes a new instance of the PeerManagementService class.
+ /// Initializes a new instance of the PeerManagementService class.
///
/// Database instance for accessing the synchronized collection.
/// Peer confirmation tracking store.
@@ -35,12 +34,13 @@ public class PeerManagementService : IPeerManagementService
ILogger? logger = null)
{
_store = store ?? throw new ArgumentNullException(nameof(store));
- _peerOplogConfirmationStore = peerOplogConfirmationStore ?? throw new ArgumentNullException(nameof(peerOplogConfirmationStore));
+ _peerOplogConfirmationStore = peerOplogConfirmationStore ??
+ throw new ArgumentNullException(nameof(peerOplogConfirmationStore));
_logger = logger ?? NullLogger.Instance;
}
///
- /// Adds or updates a static remote peer configuration.
+ /// Adds or updates a static remote peer configuration.
///
/// The unique node identifier of the peer.
/// The peer network address in host:port format.
@@ -60,22 +60,23 @@ public class PeerManagementService : IPeerManagementService
};
await _store.SaveRemotePeerAsync(config, cancellationToken);
- _logger.LogInformation("Added static remote peer: {NodeId} at {Address} (will sync to all cluster nodes)", nodeId, address);
+ _logger.LogInformation("Added static remote peer: {NodeId} at {Address} (will sync to all cluster nodes)",
+ nodeId, address);
}
///
- /// Removes a remote peer configuration.
+ /// Removes a remote peer configuration.
///
/// The unique node identifier of the peer to remove.
/// A token used to cancel the operation.
/// A task that represents the asynchronous operation.
public async Task RemoveRemotePeerAsync(string nodeId, CancellationToken cancellationToken = default)
{
- await RemovePeerTrackingAsync(nodeId, removeRemoteConfig: true, cancellationToken);
+ await RemovePeerTrackingAsync(nodeId, true, cancellationToken);
}
///
- /// Removes peer tracking and optionally removes remote peer configuration.
+ /// Removes peer tracking and optionally removes remote peer configuration.
///
/// The unique node identifier of the peer to untrack.
/// When true, also removes static remote peer configuration.
@@ -93,7 +94,8 @@ public class PeerManagementService : IPeerManagementService
if (removeRemoteConfig)
{
await _store.RemoveRemotePeerAsync(nodeId, cancellationToken);
- _logger.LogInformation("Removed remote peer and tracking: {NodeId} (will sync to all cluster nodes)", nodeId);
+ _logger.LogInformation("Removed remote peer and tracking: {NodeId} (will sync to all cluster nodes)",
+ nodeId);
return;
}
@@ -101,17 +103,18 @@ public class PeerManagementService : IPeerManagementService
}
///
- /// Gets all configured remote peers.
+ /// Gets all configured remote peers.
///
/// A token used to cancel the operation.
/// A task that represents the asynchronous operation. The task result contains remote peer configurations.
- public async Task> GetAllRemotePeersAsync(CancellationToken cancellationToken = default)
+ public async Task> GetAllRemotePeersAsync(
+ CancellationToken cancellationToken = default)
{
return await _store.GetRemotePeersAsync(cancellationToken);
}
///
- /// Enables a configured remote peer.
+ /// Enables a configured remote peer.
///
/// The unique node identifier of the peer to enable.
/// A token used to cancel the operation.
@@ -122,10 +125,7 @@ public class PeerManagementService : IPeerManagementService
var peer = await _store.GetRemotePeerAsync(nodeId, cancellationToken);
- if (peer == null)
- {
- return; // Peer not found, nothing to enable
- }
+ if (peer == null) return; // Peer not found, nothing to enable
if (!peer.IsEnabled)
{
@@ -136,7 +136,7 @@ public class PeerManagementService : IPeerManagementService
}
///
- /// Disables a configured remote peer.
+ /// Disables a configured remote peer.
///
/// The unique node identifier of the peer to disable.
/// A token used to cancel the operation.
@@ -147,10 +147,7 @@ public class PeerManagementService : IPeerManagementService
var peer = await _store.GetRemotePeerAsync(nodeId, cancellationToken);
- if (peer == null)
- {
- return; // Peer not found, nothing to disable
- }
+ if (peer == null) return; // Peer not found, nothing to disable
if (peer.IsEnabled)
{
@@ -163,23 +160,16 @@ public class PeerManagementService : IPeerManagementService
private static void ValidateNodeId(string nodeId)
{
if (string.IsNullOrWhiteSpace(nodeId))
- {
throw new ArgumentException("NodeId cannot be null or empty", nameof(nodeId));
- }
}
private static void ValidateAddress(string address)
{
if (string.IsNullOrWhiteSpace(address))
- {
throw new ArgumentException("Address cannot be null or empty", nameof(address));
- }
// Basic format validation (should contain host:port)
if (!address.Contains(':'))
- {
throw new ArgumentException("Address must be in format 'host:port'", nameof(address));
- }
}
-
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Network/IPeerNodeConfigurationProvider.cs b/src/ZB.MOM.WW.CBDDC.Core/Network/IPeerNodeConfigurationProvider.cs
index d92620b..79d09e9 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Network/IPeerNodeConfigurationProvider.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Network/IPeerNodeConfigurationProvider.cs
@@ -1,38 +1,40 @@
-using System;
using System.Threading.Tasks;
-namespace ZB.MOM.WW.CBDDC.Core.Network;
-
-///
-/// Represents a method that handles peer node configuration change notifications.
-///
-/// The source of the event.
-/// The updated peer node configuration.
-public delegate void PeerNodeConfigurationChangedEventHandler(object? sender, PeerNodeConfiguration newConfig);
+namespace ZB.MOM.WW.CBDDC.Core.Network;
///
-/// Defines a contract for retrieving and monitoring configuration settings for a peer node.
+/// Represents a method that handles peer node configuration change notifications.
///
-/// Implementations of this interface provide access to the current configuration and notify subscribers
-/// when configuration changes occur. This interface is typically used by components that require up-to-date
-/// configuration information for peer-to-peer networking scenarios.
+/// The source of the event.
+/// The updated peer node configuration.
+public delegate void PeerNodeConfigurationChangedEventHandler(object? sender, PeerNodeConfiguration newConfig);
+
+///
+/// Defines a contract for retrieving and monitoring configuration settings for a peer node.
+///
+///
+/// Implementations of this interface provide access to the current configuration and notify subscribers
+/// when configuration changes occur. This interface is typically used by components that require up-to-date
+/// configuration information for peer-to-peer networking scenarios.
+///
public interface IPeerNodeConfigurationProvider
{
- ///
- /// Asynchronously retrieves the current configuration settings for the peer node.
- ///
- ///
- /// A task that represents the asynchronous operation. The task result contains the current
- /// .
- ///
- public Task GetConfiguration();
+ ///
+ /// Asynchronously retrieves the current configuration settings for the peer node.
+ ///
+ ///
+ /// A task that represents the asynchronous operation. The task result contains the current
+ /// .
+ ///
+ public Task GetConfiguration();
///
- /// Occurs when the configuration of the peer node changes.
+ /// Occurs when the configuration of the peer node changes.
///
- /// Subscribe to this event to be notified when any configuration settings for the peer node are
- /// modified. Event handlers can use this notification to update dependent components or respond to configuration
- /// changes as needed.
-
+ ///
+ /// Subscribe to this event to be notified when any configuration settings for the peer node are
+ /// modified. Event handlers can use this notification to update dependent components or respond to configuration
+ /// changes as needed.
+ ///
public event PeerNodeConfigurationChangedEventHandler? ConfigurationChanged;
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Network/NodeRole.cs b/src/ZB.MOM.WW.CBDDC.Core/Network/NodeRole.cs
index 5864cdf..833ccc3 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Network/NodeRole.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Network/NodeRole.cs
@@ -1,20 +1,20 @@
namespace ZB.MOM.WW.CBDDC.Core.Network;
///
-/// Defines the role of a node in the distributed network cluster.
+/// Defines the role of a node in the distributed network cluster.
///
public enum NodeRole
{
///
- /// Standard member node that synchronizes only within the local area network.
- /// Does not connect to cloud remote nodes.
+ /// Standard member node that synchronizes only within the local area network.
+ /// Does not connect to cloud remote nodes.
///
Member = 0,
///
- /// Leader node that acts as a gateway to cloud remote nodes.
- /// Elected via the Bully algorithm (lexicographically smallest NodeId).
- /// Responsible for synchronizing local cluster changes with cloud nodes.
+ /// Leader node that acts as a gateway to cloud remote nodes.
+ /// Elected via the Bully algorithm (lexicographically smallest NodeId).
+ /// Responsible for synchronizing local cluster changes with cloud nodes.
///
CloudGateway = 1
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Network/PeerNode.cs b/src/ZB.MOM.WW.CBDDC.Core/Network/PeerNode.cs
index 0c019a4..6ee13d9 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Network/PeerNode.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Network/PeerNode.cs
@@ -1,53 +1,17 @@
using System;
using System.Collections.Generic;
-using System.Linq;
namespace ZB.MOM.WW.CBDDC.Core.Network;
///
-/// Represents a peer node in a distributed network, including its unique identifier, network address, and last seen
-/// timestamp.
+/// Represents a peer node in a distributed network, including its unique identifier, network address, and last seen
+/// timestamp.
///
public class PeerNode
{
///
- /// Gets the unique identifier for the node.
- ///
- public string NodeId { get; }
-
- ///
- /// Gets the address associated with the current instance.
- ///
- public string Address { get; }
-
- ///
- /// Gets the date and time when the entity was last observed or updated.
- ///
- public DateTimeOffset LastSeen { get; }
-
- ///
- /// Gets the configuration settings for the peer node.
- ///
- public PeerNodeConfiguration? Configuration { get; }
-
- ///
- /// Gets the type of the peer node (LanDiscovered, StaticRemote, or CloudRemote).
- ///
- public PeerType Type { get; }
-
- ///
- /// Gets the role assigned to this node within the cluster.
- ///
- public NodeRole Role { get; }
-
- ///
- /// Gets the list of collections this peer is interested in.
- ///
- public System.Collections.Generic.IReadOnlyList InterestingCollections { get; }
-
- ///
- /// Initializes a new instance of the PeerNode class with the specified node identifier, network address, and last
- /// seen timestamp.
+ /// Initializes a new instance of the PeerNode class with the specified node identifier, network address, and last
+ /// seen timestamp.
///
/// The unique identifier for the peer node. Cannot be null or empty.
/// The network address of the peer node. Cannot be null or empty.
@@ -57,10 +21,10 @@ public class PeerNode
/// The peer node configuration
/// The list of collections this peer is interested in.
public PeerNode(
- string nodeId,
- string address,
+ string nodeId,
+ string address,
DateTimeOffset lastSeen,
- PeerType type = PeerType.LanDiscovered,
+ PeerType type = PeerType.LanDiscovered,
NodeRole role = NodeRole.Member,
PeerNodeConfiguration? configuration = null,
IEnumerable? interestingCollections = null)
@@ -73,4 +37,39 @@ public class PeerNode
Configuration = configuration;
InterestingCollections = new List(interestingCollections ?? []).AsReadOnly();
}
-}
+
+ ///
+ /// Gets the unique identifier for the node.
+ ///
+ public string NodeId { get; }
+
+ ///
+ /// Gets the address associated with the current instance.
+ ///
+ public string Address { get; }
+
+ ///
+ /// Gets the date and time when the entity was last observed or updated.
+ ///
+ public DateTimeOffset LastSeen { get; }
+
+ ///
+ /// Gets the configuration settings for the peer node.
+ ///
+ public PeerNodeConfiguration? Configuration { get; }
+
+ ///
+ /// Gets the type of the peer node (LanDiscovered, StaticRemote, or CloudRemote).
+ ///
+ public PeerType Type { get; }
+
+ ///
+ /// Gets the role assigned to this node within the cluster.
+ ///
+ public NodeRole Role { get; }
+
+ ///
+ /// Gets the list of collections this peer is interested in.
+ ///
+ public IReadOnlyList InterestingCollections { get; }
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Network/PeerNodeConfiguration.cs b/src/ZB.MOM.WW.CBDDC.Core/Network/PeerNodeConfiguration.cs
index 7c00b3d..1d7a9be 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Network/PeerNodeConfiguration.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Network/PeerNodeConfiguration.cs
@@ -1,96 +1,101 @@
using System;
+using System.Collections.Generic;
namespace ZB.MOM.WW.CBDDC.Core.Network;
///
-/// Represents the configuration settings for a peer node in a distributed network.
+/// Represents the configuration settings for a peer node in a distributed network.
///
-/// Use this class to specify identification, network port, and authentication details required for a
-/// peer node to participate in a cluster or peer-to-peer environment. The property provides a
-/// basic configuration suitable for development or testing scenarios.
+///
+/// Use this class to specify identification, network port, and authentication details required for a
+/// peer node to participate in a cluster or peer-to-peer environment. The property provides a
+/// basic configuration suitable for development or testing scenarios.
+///
public class PeerNodeConfiguration
{
///
- /// Gets or sets the unique identifier for the node.
+ /// Gets or sets the unique identifier for the node.
///
- public string NodeId { get; set; } = string.Empty;
+ public string NodeId { get; set; } = string.Empty;
///
- /// Gets or sets the TCP port number used for network communication.
+ /// Gets or sets the TCP port number used for network communication.
///
public int TcpPort { get; set; }
///
- /// Gets or sets the authentication token used to authorize API requests.
+ /// Gets or sets the authentication token used to authorize API requests.
///
- public string AuthToken { get; set; } = string.Empty;
+ public string AuthToken { get; set; } = string.Empty;
///
- /// Maximum size of the document cache items. Default: 10.
+ /// Maximum size of the document cache items. Default: 10.
///
public int MaxDocumentCacheSize { get; set; } = 100;
///
- /// Maximum size of offline queue. Default: 1000.
+ /// Maximum size of offline queue. Default: 1000.
///
public int MaxQueueSize { get; set; } = 1000;
///
- /// Number of retry attempts for failed network operations. Default: 3.
+ /// Number of retry attempts for failed network operations. Default: 3.
///
public int RetryAttempts { get; set; } = 3;
///
- /// Delay between retry attempts in milliseconds. Default: 1000ms.
+ /// Delay between retry attempts in milliseconds. Default: 1000ms.
///
public int RetryDelayMs { get; set; } = 1000;
///
- /// Interval between periodic maintenance operations (Oplog pruning) in minutes. Default: 60 minutes.
+ /// Interval between periodic maintenance operations (Oplog pruning) in minutes. Default: 60 minutes.
///
public int MaintenanceIntervalMinutes { get; set; } = 60;
///
- /// Oplog retention period in hours. Entries older than this will be pruned. Default: 24 hours.
+ /// Oplog retention period in hours. Entries older than this will be pruned. Default: 24 hours.
///
public int OplogRetentionHours { get; set; } = 24;
///
- /// Gets or sets a list of known peers to connect to directly, bypassing discovery.
+ /// Gets or sets a list of known peers to connect to directly, bypassing discovery.
///
- public System.Collections.Generic.List KnownPeers { get; set; } = new();
+ public List KnownPeers { get; set; } = new();
///
- /// Gets the default configuration settings for a peer node.
+ /// Gets the default configuration settings for a peer node.
///
- /// Each access returns a new instance of the configuration with a unique node identifier. The
- /// default settings use TCP port 9000 and a generated authentication token. Modify the returned instance as needed
- /// before use.
- public static PeerNodeConfiguration Default => new PeerNodeConfiguration
- {
- NodeId = Guid.NewGuid().ToString(),
- TcpPort = 9000,
- AuthToken = Guid.NewGuid().ToString("N")
- };
+ ///
+ /// Each access returns a new instance of the configuration with a unique node identifier. The
+ /// default settings use TCP port 9000 and a generated authentication token. Modify the returned instance as needed
+ /// before use.
+ ///
+ public static PeerNodeConfiguration Default => new()
+ {
+ NodeId = Guid.NewGuid().ToString(),
+ TcpPort = 9000,
+ AuthToken = Guid.NewGuid().ToString("N")
+ };
}
///
-/// Configuration for a known peer node.
+/// Configuration for a known peer node.
///
public class KnownPeerConfiguration
{
///
- /// The unique identifier of the peer node.
+ /// The unique identifier of the peer node.
///
- public string NodeId { get; set; } = string.Empty;
+ public string NodeId { get; set; } = string.Empty;
///
- /// The hostname or IP address of the peer.
+ /// The hostname or IP address of the peer.
///
- public string Host { get; set; } = string.Empty;
+ public string Host { get; set; } = string.Empty;
///
- /// The TCP port of the peer.
+ /// The TCP port of the peer.
///
public int Port { get; set; }
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Network/PeerType.cs b/src/ZB.MOM.WW.CBDDC.Core/Network/PeerType.cs
index 9803d7f..29d8140 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Network/PeerType.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Network/PeerType.cs
@@ -1,26 +1,26 @@
namespace ZB.MOM.WW.CBDDC.Core.Network;
///
-/// Defines the type of peer node in the distributed network.
+/// Defines the type of peer node in the distributed network.
///
public enum PeerType
{
///
- /// Peer discovered via UDP broadcast on the local area network.
- /// These peers are ephemeral and removed after timeout when no longer broadcasting.
+ /// Peer discovered via UDP broadcast on the local area network.
+ /// These peers are ephemeral and removed after timeout when no longer broadcasting.
///
LanDiscovered = 0,
///
- /// Peer manually configured with a static address.
- /// These peers are persistent across restarts and stored in the database.
+ /// Peer manually configured with a static address.
+ /// These peers are persistent across restarts and stored in the database.
///
StaticRemote = 1,
///
- /// Cloud remote node.
- /// Always active if internet connectivity is available.
- /// Synchronized only by the elected leader node to reduce overhead.
+ /// Cloud remote node.
+ /// Always active if internet connectivity is available.
+ /// Synchronized only by the elected leader node to reduce overhead.
///
CloudRemote = 2
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Network/RemotePeerConfiguration.cs b/src/ZB.MOM.WW.CBDDC.Core/Network/RemotePeerConfiguration.cs
index 6f68754..811f57e 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Network/RemotePeerConfiguration.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Network/RemotePeerConfiguration.cs
@@ -1,38 +1,39 @@
+using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ZB.MOM.WW.CBDDC.Core.Network;
///
-/// Configuration for a remote peer node that is persistent across restarts.
-/// This collection is automatically synchronized across all nodes in the cluster.
+/// Configuration for a remote peer node that is persistent across restarts.
+/// This collection is automatically synchronized across all nodes in the cluster.
///
public class RemotePeerConfiguration
{
///
- /// Gets or sets the unique identifier for the remote peer node.
+ /// Gets or sets the unique identifier for the remote peer node.
///
[Key]
public string NodeId { get; set; } = "";
///
- /// Gets or sets the network address of the remote peer (hostname:port).
+ /// Gets or sets the network address of the remote peer (hostname:port).
///
public string Address { get; set; } = "";
///
- /// Gets or sets the type of the peer (StaticRemote or CloudRemote).
+ /// Gets or sets the type of the peer (StaticRemote or CloudRemote).
///
public PeerType Type { get; set; }
///
- /// Gets or sets whether this peer is enabled for synchronization.
- /// Disabled peers are stored but not used for sync.
+ /// Gets or sets whether this peer is enabled for synchronization.
+ /// Disabled peers are stored but not used for sync.
///
public bool IsEnabled { get; set; } = true;
///
- /// Gets or sets the list of collections this peer is interested in.
- /// If empty, the peer is interested in all collections.
+ /// Gets or sets the list of collections this peer is interested in.
+ /// If empty, the peer is interested in all collections.
///
- public System.Collections.Generic.List InterestingCollections { get; set; } = new();
-}
+ public List InterestingCollections { get; set; } = new();
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Network/StaticPeerNodeConfigurationProvider.cs b/src/ZB.MOM.WW.CBDDC.Core/Network/StaticPeerNodeConfigurationProvider.cs
index 2824b13..1d6d010 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Network/StaticPeerNodeConfigurationProvider.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Network/StaticPeerNodeConfigurationProvider.cs
@@ -1,32 +1,16 @@
-using System.Threading.Tasks;
-
+using System.Threading.Tasks;
+
namespace ZB.MOM.WW.CBDDC.Core.Network;
///
-/// Provides peer node configuration from an in-memory static source.
+/// Provides peer node configuration from an in-memory static source.
///
public class StaticPeerNodeConfigurationProvider : IPeerNodeConfigurationProvider
{
private PeerNodeConfiguration _configuration = new();
///
- /// Gets or sets the current peer node configuration.
- ///
- public PeerNodeConfiguration Configuration
- {
- get => _configuration;
- set
- {
- if (_configuration != value)
- {
- _configuration = value;
- OnConfigurationChanged(_configuration);
- }
- }
- }
-
- ///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The initial peer node configuration.
public StaticPeerNodeConfigurationProvider(PeerNodeConfiguration configuration)
@@ -35,12 +19,28 @@ public class StaticPeerNodeConfigurationProvider : IPeerNodeConfigurationProvide
}
///
- /// Occurs when the peer node configuration changes.
+ /// Gets or sets the current peer node configuration.
+ ///
+ public PeerNodeConfiguration Configuration
+ {
+ get => _configuration;
+ set
+ {
+ if (_configuration != value)
+ {
+ _configuration = value;
+ OnConfigurationChanged(_configuration);
+ }
+ }
+ }
+
+ ///
+ /// Occurs when the peer node configuration changes.
///
public event PeerNodeConfigurationChangedEventHandler? ConfigurationChanged;
///
- /// Gets the current peer node configuration.
+ /// Gets the current peer node configuration.
///
/// A task whose result is the current configuration.
public Task GetConfiguration()
@@ -49,11 +49,11 @@ public class StaticPeerNodeConfigurationProvider : IPeerNodeConfigurationProvide
}
///
- /// Raises the event.
+ /// Raises the event.
///
/// The new peer node configuration.
protected virtual void OnConfigurationChanged(PeerNodeConfiguration newConfig)
{
ConfigurationChanged?.Invoke(this, newConfig);
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/OplogEntry.cs b/src/ZB.MOM.WW.CBDDC.Core/OplogEntry.cs
index 0f0add9..dbac33a 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/OplogEntry.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/OplogEntry.cs
@@ -1,5 +1,7 @@
using System;
-using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
using System.Text.Json;
namespace ZB.MOM.WW.CBDDC.Core;
@@ -10,84 +12,56 @@ public enum OperationType
Delete
}
-public static class OplogEntryExtensions
-{
- ///
- /// Computes a deterministic hash for the specified oplog entry.
- ///
- /// The oplog entry to hash.
- /// The lowercase hexadecimal SHA-256 hash of the entry.
- public static string ComputeHash(this OplogEntry entry)
- {
- using var sha256 = System.Security.Cryptography.SHA256.Create();
- var sb = new System.Text.StringBuilder();
+public static class OplogEntryExtensions
+{
+ ///
+ /// Computes a deterministic hash for the specified oplog entry.
+ ///
+ /// The oplog entry to hash.
+ /// The lowercase hexadecimal SHA-256 hash of the entry.
+ public static string ComputeHash(this OplogEntry entry)
+ {
+ using var sha256 = SHA256.Create();
+ var sb = new StringBuilder();
sb.Append(entry.Collection);
sb.Append('|');
sb.Append(entry.Key);
sb.Append('|');
// Ensure stable string representation for Enum (integer value)
- sb.Append(((int)entry.Operation).ToString(System.Globalization.CultureInfo.InvariantCulture));
+ sb.Append(((int)entry.Operation).ToString(CultureInfo.InvariantCulture));
sb.Append('|');
// Payload excluded from hash to avoid serialization non-determinism
// sb.Append(entry.Payload...);
sb.Append('|');
// Timestamp.ToString() is now Invariant
- sb.Append(entry.Timestamp.ToString());
+ sb.Append(entry.Timestamp.ToString());
sb.Append('|');
sb.Append(entry.PreviousHash);
- var bytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
- var hashBytes = sha256.ComputeHash(bytes);
+ byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
+ byte[] hashBytes = sha256.ComputeHash(bytes);
// Convert to hex string
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
}
-public class OplogEntry
-{
- ///
- /// Gets the collection name associated with this entry.
- ///
- public string Collection { get; }
- ///
- /// Gets the document key associated with this entry.
- ///
- public string Key { get; }
- ///
- /// Gets the operation represented by this entry.
- ///
- public OperationType Operation { get; }
- ///
- /// Gets the serialized payload for the operation.
- ///
- public JsonElement? Payload { get; }
- ///
- /// Gets the logical timestamp for this entry.
- ///
- public HlcTimestamp Timestamp { get; }
- ///
- /// Gets the hash of this entry.
- ///
- public string Hash { get; }
- ///
- /// Gets the hash of the previous entry in the chain.
- ///
- public string PreviousHash { get; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The collection name.
- /// The document key.
- /// The operation type.
- /// The serialized payload.
- /// The logical timestamp.
- /// The previous entry hash.
- /// The current entry hash. If null, it is computed.
- public OplogEntry(string collection, string key, OperationType operation, JsonElement? payload, HlcTimestamp timestamp, string previousHash, string? hash = null)
- {
+public class OplogEntry
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The collection name.
+ /// The document key.
+ /// The operation type.
+ /// The serialized payload.
+ /// The logical timestamp.
+ /// The previous entry hash.
+ /// The current entry hash. If null, it is computed.
+ public OplogEntry(string collection, string key, OperationType operation, JsonElement? payload,
+ HlcTimestamp timestamp, string previousHash, string? hash = null)
+ {
Collection = collection;
Key = key;
Operation = operation;
@@ -98,10 +72,45 @@ public class OplogEntry
}
///
- /// Verifies if the stored Hash matches the content.
+ /// Gets the collection name associated with this entry.
+ ///
+ public string Collection { get; }
+
+ ///
+ /// Gets the document key associated with this entry.
+ ///
+ public string Key { get; }
+
+ ///
+ /// Gets the operation represented by this entry.
+ ///
+ public OperationType Operation { get; }
+
+ ///
+ /// Gets the serialized payload for the operation.
+ ///
+ public JsonElement? Payload { get; }
+
+ ///
+ /// Gets the logical timestamp for this entry.
+ ///
+ public HlcTimestamp Timestamp { get; }
+
+ ///
+ /// Gets the hash of this entry.
+ ///
+ public string Hash { get; }
+
+ ///
+ /// Gets the hash of the previous entry in the chain.
+ ///
+ public string PreviousHash { get; }
+
+ ///
+ /// Verifies if the stored Hash matches the content.
///
public bool IsValid()
{
return Hash == this.ComputeHash();
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/PeerOplogConfirmation.cs b/src/ZB.MOM.WW.CBDDC.Core/PeerOplogConfirmation.cs
index bae3dc0..89d74bd 100644
--- a/src/ZB.MOM.WW.CBDDC.Core/PeerOplogConfirmation.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/PeerOplogConfirmation.cs
@@ -3,42 +3,42 @@ using System;
namespace ZB.MOM.WW.CBDDC.Core;
///
-/// Represents a persisted confirmation watermark for a tracked peer and source node.
+/// Represents a persisted confirmation watermark for a tracked peer and source node.
///
public class PeerOplogConfirmation
{
///
- /// Gets or sets the tracked peer node identifier.
+ /// Gets or sets the tracked peer node identifier.
///
public string PeerNodeId { get; set; } = "";
///
- /// Gets or sets the source node identifier this confirmation applies to.
+ /// Gets or sets the source node identifier this confirmation applies to.
///
public string SourceNodeId { get; set; } = "";
///
- /// Gets or sets the physical wall-clock component of the confirmed HLC timestamp.
+ /// Gets or sets the physical wall-clock component of the confirmed HLC timestamp.
///
public long ConfirmedWall { get; set; }
///
- /// Gets or sets the logical counter component of the confirmed HLC timestamp.
+ /// Gets or sets the logical counter component of the confirmed HLC timestamp.
///
public int ConfirmedLogic { get; set; }
///
- /// Gets or sets the confirmed hash at the watermark.
+ /// Gets or sets the confirmed hash at the watermark.
///
public string ConfirmedHash { get; set; } = "";
///
- /// Gets or sets when this confirmation record was last updated in UTC.
+ /// Gets or sets when this confirmation record was last updated in UTC.
///
public DateTimeOffset LastConfirmedUtc { get; set; } = DateTimeOffset.UtcNow;
///
- /// Gets or sets whether this tracked peer is active for pruning/sync gating.
+ /// Gets or sets whether this tracked peer is active for pruning/sync gating.
///
public bool IsActive { get; set; } = true;
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/QueryNode.cs b/src/ZB.MOM.WW.CBDDC.Core/QueryNode.cs
index 91d835b..e526951 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/QueryNode.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/QueryNode.cs
@@ -1,225 +1,269 @@
-using System.Text.Json;
-
-namespace ZB.MOM.WW.CBDDC.Core;
-
-public abstract class QueryNode { }
-
+namespace ZB.MOM.WW.CBDDC.Core;
+
+public abstract class QueryNode
+{
+}
+
public class Eq : QueryNode
{
///
- /// Gets the field name to compare.
+ /// Initializes a new equality query node.
+ ///
+ /// The field name to compare.
+ /// The value to compare against.
+ public Eq(string field, object value)
+ {
+ Field = field;
+ Value = value;
+ }
+
+ ///
+ /// Gets the field name to compare.
///
public string Field { get; }
///
- /// Gets the value to compare against.
+ /// Gets the value to compare against.
///
public object Value { get; }
-
- ///
- /// Initializes a new equality query node.
- ///
- /// The field name to compare.
- /// The value to compare against.
- public Eq(string field, object value) { Field = field; Value = value; }
}
public class Gt : QueryNode
{
///
- /// Gets the field name to compare.
+ /// Initializes a new greater-than query node.
+ ///
+ /// The field name to compare.
+ /// The threshold value.
+ public Gt(string field, object value)
+ {
+ Field = field;
+ Value = value;
+ }
+
+ ///
+ /// Gets the field name to compare.
///
public string Field { get; }
///
- /// Gets the threshold value.
+ /// Gets the threshold value.
///
public object Value { get; }
-
- ///
- /// Initializes a new greater-than query node.
- ///
- /// The field name to compare.
- /// The threshold value.
- public Gt(string field, object value) { Field = field; Value = value; }
}
public class Lt : QueryNode
{
///
- /// Gets the field name to compare.
+ /// Initializes a new less-than query node.
+ ///
+ /// The field name to compare.
+ /// The threshold value.
+ public Lt(string field, object value)
+ {
+ Field = field;
+ Value = value;
+ }
+
+ ///
+ /// Gets the field name to compare.
///
public string Field { get; }
///
- /// Gets the threshold value.
+ /// Gets the threshold value.
///
public object Value { get; }
-
- ///
- /// Initializes a new less-than query node.
- ///
- /// The field name to compare.
- /// The threshold value.
- public Lt(string field, object value) { Field = field; Value = value; }
}
public class Gte : QueryNode
{
///
- /// Gets the field name to compare.
+ /// Initializes a new greater-than-or-equal query node.
+ ///
+ /// The field name to compare.
+ /// The threshold value.
+ public Gte(string field, object value)
+ {
+ Field = field;
+ Value = value;
+ }
+
+ ///
+ /// Gets the field name to compare.
///
public string Field { get; }
///
- /// Gets the threshold value.
+ /// Gets the threshold value.
///
public object Value { get; }
-
- ///
- /// Initializes a new greater-than-or-equal query node.
- ///
- /// The field name to compare.
- /// The threshold value.
- public Gte(string field, object value) { Field = field; Value = value; }
}
public class Lte : QueryNode
{
///
- /// Gets the field name to compare.
+ /// Initializes a new less-than-or-equal query node.
+ ///
+ /// The field name to compare.
+ /// The threshold value.
+ public Lte(string field, object value)
+ {
+ Field = field;
+ Value = value;
+ }
+
+ ///
+ /// Gets the field name to compare.
///
public string Field { get; }
///
- /// Gets the threshold value.
+ /// Gets the threshold value.
///
public object Value { get; }
-
- ///
- /// Initializes a new less-than-or-equal query node.
- ///
- /// The field name to compare.
- /// The threshold value.
- public Lte(string field, object value) { Field = field; Value = value; }
}
public class Neq : QueryNode
{
///
- /// Gets the field name to compare.
+ /// Initializes a new not-equal query node.
+ ///
+ /// The field name to compare.
+ /// The value to compare against.
+ public Neq(string field, object value)
+ {
+ Field = field;
+ Value = value;
+ }
+
+ ///
+ /// Gets the field name to compare.
///
public string Field { get; }
///
- /// Gets the value to compare against.
+ /// Gets the value to compare against.
///
public object Value { get; }
-
- ///
- /// Initializes a new not-equal query node.
- ///
- /// The field name to compare.
- /// The value to compare against.
- public Neq(string field, object value) { Field = field; Value = value; }
}
public class In : QueryNode
{
///
- /// Gets the field name to compare.
+ /// Initializes a new in-list query node.
+ ///
+ /// The field name to compare.
+ /// The set of values to compare against.
+ public In(string field, object[] values)
+ {
+ Field = field;
+ Values = values;
+ }
+
+ ///
+ /// Gets the field name to compare.
///
public string Field { get; }
///
- /// Gets the set of values to compare against.
+ /// Gets the set of values to compare against.
///
public object[] Values { get; }
-
- ///
- /// Initializes a new in-list query node.
- ///
- /// The field name to compare.
- /// The set of values to compare against.
- public In(string field, object[] values) { Field = field; Values = values; }
}
public class Contains : QueryNode
{
///
- /// Gets the field name to compare.
+ /// Initializes a new contains query node.
+ ///
+ /// The field name to compare.
+ /// The substring value to search for.
+ public Contains(string field, string value)
+ {
+ Field = field;
+ Value = value;
+ }
+
+ ///
+ /// Gets the field name to compare.
///
public string Field { get; }
///
- /// Gets the substring value to search for.
+ /// Gets the substring value to search for.
///
public string Value { get; }
-
- ///
- /// Initializes a new contains query node.
- ///
- /// The field name to compare.
- /// The substring value to search for.
- public Contains(string field, string value) { Field = field; Value = value; }
}
public class NotContains : QueryNode
{
///
- /// Gets the field name to compare.
+ /// Initializes a new not-contains query node.
+ ///
+ /// The field name to compare.
+ /// The substring value to exclude.
+ public NotContains(string field, string value)
+ {
+ Field = field;
+ Value = value;
+ }
+
+ ///
+ /// Gets the field name to compare.
///
public string Field { get; }
///
- /// Gets the substring value to exclude.
+ /// Gets the substring value to exclude.
///
public string Value { get; }
-
- ///
- /// Initializes a new not-contains query node.
- ///
- /// The field name to compare.
- /// The substring value to exclude.
- public NotContains(string field, string value) { Field = field; Value = value; }
}
public class And : QueryNode
{
///
- /// Gets the left side of the logical operation.
+ /// Initializes a new logical AND query node.
+ ///
+ /// The left query node.
+ /// The right query node.
+ public And(QueryNode left, QueryNode right)
+ {
+ Left = left;
+ Right = right;
+ }
+
+ ///
+ /// Gets the left side of the logical operation.
///
public QueryNode Left { get; }
///
- /// Gets the right side of the logical operation.
+ /// Gets the right side of the logical operation.
///
public QueryNode Right { get; }
-
- ///
- /// Initializes a new logical AND query node.
- ///
- /// The left query node.
- /// The right query node.
- public And(QueryNode left, QueryNode right) { Left = left; Right = right; }
}
public class Or : QueryNode
{
///
- /// Gets the left side of the logical operation.
+ /// Initializes a new logical OR query node.
+ ///
+ /// The left query node.
+ /// The right query node.
+ public Or(QueryNode left, QueryNode right)
+ {
+ Left = left;
+ Right = right;
+ }
+
+ ///
+ /// Gets the left side of the logical operation.
///
public QueryNode Left { get; }
///
- /// Gets the right side of the logical operation.
+ /// Gets the right side of the logical operation.
///
public QueryNode Right { get; }
-
- ///
- /// Initializes a new logical OR query node.
- ///
- /// The left query node.
- /// The right query node.
- public Or(QueryNode left, QueryNode right) { Left = left; Right = right; }
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/README.md b/src/ZB.MOM.WW.CBDDC.Core/README.md
index 32a898d..2c82dd4 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/README.md
+++ b/src/ZB.MOM.WW.CBDDC.Core/README.md
@@ -4,7 +4,9 @@ Core abstractions and logic for **CBDDC**, a peer-to-peer data synchronization m
## What Is CBDDC?
-CBDDC is **not** a database it's a sync layer that plugs into your existing data store (BLite) and enables automatic P2P replication across nodes in a mesh network. Your application reads and writes to its database as usual; CBDDC handles synchronization in the background.
+CBDDC is **not** a database οΏ½ it's a sync layer that plugs into your existing data store (BLite) and enables automatic
+P2P replication across nodes in a mesh network. Your application reads and writes to its database as usual; CBDDC
+handles synchronization in the background.
## What's In This Package
@@ -17,7 +19,7 @@ CBDDC is **not** a database
```bash
# Pick a persistence provider
-dotnet add package ZB.MOM.WW.CBDDC.Persistence # Embedded document DB
+dotnet add package ZB.MOM.WW.CBDDC.Persistence # Embedded document DB
# Add networking
dotnet add package ZB.MOM.WW.CBDDC.Network
@@ -65,12 +67,12 @@ builder.Services.AddCBDDCCore()
## Key Concepts
-| Concept | Description |
-|---------|-------------|
-| **CDC** | Change Data Capture watches collections registered via `WatchCollection()` |
-| **Oplog** | Append-only hash-chained journal of changes per node |
-| **VectorClock** | Tracks causal ordering across the mesh |
-| **DocumentStore** | Your bridge between entities and the sync engine |
+| Concept | Description |
+|-------------------|------------------------------------------------------------------------------|
+| **CDC** | Change Data Capture οΏ½ watches collections registered via `WatchCollection()` |
+| **Oplog** | Append-only hash-chained journal of changes per node |
+| **VectorClock** | Tracks causal ordering across the mesh |
+| **DocumentStore** | Your bridge between entities and the sync engine |
## Architecture
@@ -91,15 +93,16 @@ Your App ? DbContext.SaveChangesAsync()
## Related Packages
-- **ZB.MOM.WW.CBDDC.Persistence** οΏ½ BLite embedded provider (.NET 10+)
-- **ZB.MOM.WW.CBDDC.Network** P2P networking (UDP discovery, TCP sync, Gossip)
+- **ZB.MOM.WW.CBDDC.Persistence** οΏ½ BLite embedded provider (.NET 10+)
+- **ZB.MOM.WW.CBDDC.Network** οΏ½ P2P networking (UDP discovery, TCP sync, Gossip)
## Documentation
- **[Complete Documentation](https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net)**
-- **[Sample Application](https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net/tree/main/samples/ZB.MOM.WW.CBDDC.Sample.Console)**
+- **[Sample Application](https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net/tree/main/samples/ZB.MOM.WW.CBDDC.Sample.Console)
+ **
- **[Integration Guide](https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net#integrating-with-your-database)**
## License
-MIT see [LICENSE](https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net/blob/main/LICENSE)
+MIT οΏ½ see [LICENSE](https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net/blob/main/LICENSE)
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Resilience/IRetryPolicy.cs b/src/ZB.MOM.WW.CBDDC.Core/Resilience/IRetryPolicy.cs
index 22d19eb..ec752ff 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Resilience/IRetryPolicy.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Resilience/IRetryPolicy.cs
@@ -1,27 +1,28 @@
-ο»Ώusing System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace ZB.MOM.WW.CBDDC.Core.Resilience
+ο»Ώusing System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ZB.MOM.WW.CBDDC.Core.Resilience;
+
+public interface IRetryPolicy
{
- public interface IRetryPolicy
- {
- ///
- /// Executes an asynchronous operation with retry handling.
- ///
- /// The operation to execute.
- /// The operation name used for diagnostics.
- /// A token used to cancel the operation.
- /// A task that represents the asynchronous execution.
- Task ExecuteAsync(Func operation, string operationName, CancellationToken cancellationToken = default);
- ///
- /// Executes an asynchronous operation with retry handling and returns a result.
- ///
- /// The result type.
- /// The operation to execute.
- /// The operation name used for diagnostics.
- /// A token used to cancel the operation.
- /// A task that represents the asynchronous execution and yields the operation result.
- Task ExecuteAsync(Func> operation, string operationName, CancellationToken cancellationToken = default);
- }
-}
+ ///
+ /// Executes an asynchronous operation with retry handling.
+ ///
+ /// The operation to execute.
+ /// The operation name used for diagnostics.
+ /// A token used to cancel the operation.
+ /// A task that represents the asynchronous execution.
+ Task ExecuteAsync(Func operation, string operationName, CancellationToken cancellationToken = default);
+
+ ///
+ /// Executes an asynchronous operation with retry handling and returns a result.
+ ///
+ /// The result type.
+ /// The operation to execute.
+ /// The operation name used for diagnostics.
+ /// A token used to cancel the operation.
+ /// A task that represents the asynchronous execution and yields the operation result.
+ Task ExecuteAsync(Func> operation, string operationName,
+ CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Resilience/RetryPolicy.cs b/src/ZB.MOM.WW.CBDDC.Core/Resilience/RetryPolicy.cs
index 5889607..6198ed3 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Resilience/RetryPolicy.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Resilience/RetryPolicy.cs
@@ -1,50 +1,53 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using ZB.MOM.WW.CBDDC.Core.Exceptions;
-using ZB.MOM.WW.CBDDC.Core.Network;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using ZB.MOM.WW.CBDDC.Core.Exceptions;
+using ZB.MOM.WW.CBDDC.Core.Network;
+using TimeoutException = ZB.MOM.WW.CBDDC.Core.Exceptions.TimeoutException;
namespace ZB.MOM.WW.CBDDC.Core.Resilience;
///
-/// Provides retry logic for transient failures.
+/// Provides retry logic for transient failures.
///
-public class RetryPolicy : IRetryPolicy
-{
- private readonly IPeerNodeConfigurationProvider _peerNodeConfigurationProvider;
- private readonly ILogger _logger;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The provider for retry configuration values.
- /// The logger instance.
- public RetryPolicy(IPeerNodeConfigurationProvider peerNodeConfigurationProvider, ILogger? logger = null)
- {
- _logger = logger ?? NullLogger.Instance;
- _peerNodeConfigurationProvider = peerNodeConfigurationProvider
- ?? throw new ArgumentNullException(nameof(peerNodeConfigurationProvider));
- }
+public class RetryPolicy : IRetryPolicy
+{
+ private readonly ILogger _logger;
+ private readonly IPeerNodeConfigurationProvider _peerNodeConfigurationProvider;
- ///
- /// Executes an operation with retry logic.
- ///
- /// The result type returned by the operation.
- /// The asynchronous operation to execute.
- /// The operation name used for logging.
- /// A token used to cancel retry delays.
- public async Task ExecuteAsync(
- Func> operation,
- string operationName,
- CancellationToken cancellationToken = default)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The provider for retry configuration values.
+ /// The logger instance.
+ public RetryPolicy(IPeerNodeConfigurationProvider peerNodeConfigurationProvider,
+ ILogger? logger = null)
+ {
+ _logger = logger ?? NullLogger.Instance;
+ _peerNodeConfigurationProvider = peerNodeConfigurationProvider
+ ?? throw new ArgumentNullException(nameof(peerNodeConfigurationProvider));
+ }
+
+ ///
+ /// Executes an operation with retry logic.
+ ///
+ /// The result type returned by the operation.
+ /// The asynchronous operation to execute.
+ /// The operation name used for logging.
+ /// A token used to cancel retry delays.
+ public async Task ExecuteAsync(
+ Func> operation,
+ string operationName,
+ CancellationToken cancellationToken = default)
{
var config = await _peerNodeConfigurationProvider.GetConfiguration();
Exception? lastException = null;
- for (int attempt = 1; attempt <= config.RetryAttempts; attempt++)
- {
+ for (var attempt = 1; attempt <= config.RetryAttempts; attempt++)
try
{
_logger.LogDebug("Executing {Operation} (attempt {Attempt}/{Max})",
@@ -55,7 +58,7 @@ public class RetryPolicy : IRetryPolicy
catch (Exception ex) when (attempt < config.RetryAttempts && IsTransient(ex))
{
lastException = ex;
- var delay = config.RetryDelayMs * attempt; // Exponential backoff
+ int delay = config.RetryDelayMs * attempt; // Exponential backoff
_logger.LogWarning(ex,
"Operation {Operation} failed (attempt {Attempt}/{Max}). Retrying in {Delay}ms...",
@@ -63,36 +66,31 @@ public class RetryPolicy : IRetryPolicy
await Task.Delay(delay, cancellationToken);
}
- }
- if (lastException != null)
- {
- _logger.LogError(lastException,
- "Operation {Operation} failed after {Attempts} attempts",
- operationName, config.RetryAttempts);
- }
- else
- {
- _logger.LogError(
- "Operation {Operation} failed after {Attempts} attempts",
- operationName, config.RetryAttempts);
- }
+ if (lastException != null)
+ _logger.LogError(lastException,
+ "Operation {Operation} failed after {Attempts} attempts",
+ operationName, config.RetryAttempts);
+ else
+ _logger.LogError(
+ "Operation {Operation} failed after {Attempts} attempts",
+ operationName, config.RetryAttempts);
throw new CBDDCException("RETRY_EXHAUSTED",
$"Operation '{operationName}' failed after {config.RetryAttempts} attempts",
lastException!);
}
- ///
- /// Executes an operation with retry logic (void return).
- ///
- /// The asynchronous operation to execute.
- /// The operation name used for logging.
- /// A token used to cancel retry delays.
- public async Task ExecuteAsync(
- Func operation,
- string operationName,
- CancellationToken cancellationToken = default)
+ ///
+ /// Executes an operation with retry logic (void return).
+ ///
+ /// The asynchronous operation to execute.
+ /// The operation name used for logging.
+ /// A token used to cancel retry delays.
+ public async Task ExecuteAsync(
+ Func operation,
+ string operationName,
+ CancellationToken cancellationToken = default)
{
await ExecuteAsync(async () =>
{
@@ -104,13 +102,13 @@ public class RetryPolicy : IRetryPolicy
private static bool IsTransient(Exception ex)
{
// Network errors are typically transient
- if (ex is NetworkException or System.Net.Sockets.SocketException or System.IO.IOException)
+ if (ex is NetworkException or SocketException or IOException)
return true;
// Timeout errors are transient
- if (ex is Exceptions.TimeoutException or OperationCanceledException)
+ if (ex is TimeoutException or OperationCanceledException)
return true;
return false;
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/SnapshotMetadata.cs b/src/ZB.MOM.WW.CBDDC.Core/SnapshotMetadata.cs
index 385af8b..30736f8 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/SnapshotMetadata.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/SnapshotMetadata.cs
@@ -1,21 +1,24 @@
-namespace ZB.MOM.WW.CBDDC.Core;
-
+namespace ZB.MOM.WW.CBDDC.Core;
+
public class SnapshotMetadata
{
///
- /// Gets or sets the node identifier associated with the snapshot.
+ /// Gets or sets the node identifier associated with the snapshot.
///
public string NodeId { get; set; } = "";
+
///
- /// Gets or sets the physical time component of the snapshot timestamp.
+ /// Gets or sets the physical time component of the snapshot timestamp.
///
public long TimestampPhysicalTime { get; set; }
+
///
- /// Gets or sets the logical counter component of the snapshot timestamp.
+ /// Gets or sets the logical counter component of the snapshot timestamp.
///
public int TimestampLogicalCounter { get; set; }
+
///
- /// Gets or sets the snapshot hash.
+ /// Gets or sets the snapshot hash.
///
public string Hash { get; set; } = "";
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Storage/CorruptDatabaseException.cs b/src/ZB.MOM.WW.CBDDC.Core/Storage/CorruptDatabaseException.cs
index 4cad089..37a0e05 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Storage/CorruptDatabaseException.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Storage/CorruptDatabaseException.cs
@@ -1,16 +1,18 @@
-using System;
-
+using System;
+
namespace ZB.MOM.WW.CBDDC.Core.Storage;
///
-/// Represents an error that occurs when a database is found to be corrupt.
+/// Represents an error that occurs when a database is found to be corrupt.
///
public class CorruptDatabaseException : Exception
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The exception message.
/// The underlying exception that caused this error.
- public CorruptDatabaseException(string message, Exception innerException) : base(message, innerException) { }
-}
+ public CorruptDatabaseException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Storage/IDocumentMetadataStore.cs b/src/ZB.MOM.WW.CBDDC.Core/Storage/IDocumentMetadataStore.cs
index 81461e8..f8491f4 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Storage/IDocumentMetadataStore.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Storage/IDocumentMetadataStore.cs
@@ -1,108 +1,115 @@
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
namespace ZB.MOM.WW.CBDDC.Core.Storage;
///
-/// Defines the contract for storing and retrieving document metadata for sync tracking.
-/// Document metadata stores HLC timestamps and deleted state without modifying application entities.
+/// Defines the contract for storing and retrieving document metadata for sync tracking.
+/// Document metadata stores HLC timestamps and deleted state without modifying application entities.
///
public interface IDocumentMetadataStore : ISnapshotable
{
///
- /// Gets the metadata for a specific document.
+ /// Gets the metadata for a specific document.
///
/// The collection name.
/// The document key.
/// A cancellation token.
/// The document metadata if found; otherwise null.
- Task GetMetadataAsync(string collection, string key, CancellationToken cancellationToken = default);
+ Task GetMetadataAsync(string collection, string key,
+ CancellationToken cancellationToken = default);
///
- /// Gets metadata for all documents in a collection.
+ /// Gets metadata for all documents in a collection.
///
/// The collection name.
/// A cancellation token.
/// Enumerable of document metadata for the collection.
- Task> GetMetadataByCollectionAsync(string collection, CancellationToken cancellationToken = default);
+ Task> GetMetadataByCollectionAsync(string collection,
+ CancellationToken cancellationToken = default);
///
- /// Upserts (inserts or updates) metadata for a document.
+ /// Upserts (inserts or updates) metadata for a document.
///
/// The metadata to upsert.
/// A cancellation token.
Task UpsertMetadataAsync(DocumentMetadata metadata, CancellationToken cancellationToken = default);
///
- /// Upserts metadata for multiple documents in batch.
+ /// Upserts metadata for multiple documents in batch.
///
/// The metadata items to upsert.
/// A cancellation token.
- Task UpsertMetadataBatchAsync(IEnumerable metadatas, CancellationToken cancellationToken = default);
+ Task UpsertMetadataBatchAsync(IEnumerable metadatas,
+ CancellationToken cancellationToken = default);
///
- /// Marks a document as deleted by setting IsDeleted=true and updating the timestamp.
+ /// Marks a document as deleted by setting IsDeleted=true and updating the timestamp.
///
/// The collection name.
/// The document key.
/// The HLC timestamp of the deletion.
/// A cancellation token.
- Task MarkDeletedAsync(string collection, string key, HlcTimestamp timestamp, CancellationToken cancellationToken = default);
+ Task MarkDeletedAsync(string collection, string key, HlcTimestamp timestamp,
+ CancellationToken cancellationToken = default);
///
- /// Gets all document metadata with timestamps after the specified timestamp.
- /// Used for incremental sync to find documents modified since last sync.
+ /// Gets all document metadata with timestamps after the specified timestamp.
+ /// Used for incremental sync to find documents modified since last sync.
///
/// The timestamp to compare against.
/// Optional collection filter.
/// A cancellation token.
/// Documents modified after the specified timestamp.
- Task> GetMetadataAfterAsync(HlcTimestamp since, IEnumerable? collections = null, CancellationToken cancellationToken = default);
+ Task> GetMetadataAfterAsync(HlcTimestamp since,
+ IEnumerable? collections = null, CancellationToken cancellationToken = default);
}
///
-/// Represents metadata for a document used in sync tracking.
+/// Represents metadata for a document used in sync tracking.
///
public class DocumentMetadata
{
///
- /// Gets or sets the collection name.
+ /// Initializes a new instance of the class.
///
- public string Collection { get; set; } = "";
+ public DocumentMetadata()
+ {
+ }
///
- /// Gets or sets the document key.
+ /// Initializes a new instance of the class.
///
- public string Key { get; set; } = "";
-
- ///
- /// Gets or sets the HLC timestamp of the last modification.
- ///
- public HlcTimestamp UpdatedAt { get; set; }
-
- ///
- /// Gets or sets whether this document is marked as deleted (tombstone).
- ///
- public bool IsDeleted { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public DocumentMetadata() { }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The collection name.
- /// The document key.
- /// The last update timestamp.
- /// Whether the document is marked as deleted.
- public DocumentMetadata(string collection, string key, HlcTimestamp updatedAt, bool isDeleted = false)
- {
- Collection = collection;
+ /// The collection name.
+ /// The document key.
+ /// The last update timestamp.
+ /// Whether the document is marked as deleted.
+ public DocumentMetadata(string collection, string key, HlcTimestamp updatedAt, bool isDeleted = false)
+ {
+ Collection = collection;
Key = key;
UpdatedAt = updatedAt;
IsDeleted = isDeleted;
}
-}
+
+ ///
+ /// Gets or sets the collection name.
+ ///
+ public string Collection { get; set; } = "";
+
+ ///
+ /// Gets or sets the document key.
+ ///
+ public string Key { get; set; } = "";
+
+ ///
+ /// Gets or sets the HLC timestamp of the last modification.
+ ///
+ public HlcTimestamp UpdatedAt { get; set; }
+
+ ///
+ /// Gets or sets whether this document is marked as deleted (tombstone).
+ ///
+ public bool IsDeleted { get; set; }
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Storage/IDocumentStore.cs b/src/ZB.MOM.WW.CBDDC.Core/Storage/IDocumentStore.cs
index 789d92e..6f02b24 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Storage/IDocumentStore.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Storage/IDocumentStore.cs
@@ -1,91 +1,112 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
namespace ZB.MOM.WW.CBDDC.Core.Storage;
///
-/// Handles basic CRUD operations for documents.
+/// Handles basic CRUD operations for documents.
///
-public interface IDocumentStore : ISnapshotable
-{
- ///
- /// Gets the collections this store is interested in.
- ///
- IEnumerable InterestedCollection { get; }
+public interface IDocumentStore : ISnapshotable
+{
+ ///
+ /// Gets the collections this store is interested in.
+ ///
+ IEnumerable InterestedCollection { get; }
///
- /// Asynchronously retrieves a incoming from the specified collection by its key.
+ /// Asynchronously retrieves a incoming from the specified collection by its key.
///
/// The name of the collection containing the incoming to retrieve. Cannot be null or empty.
/// The unique key identifying the incoming within the collection. Cannot be null or empty.
/// A cancellation token that can be used to cancel the operation.
- /// A task that represents the asynchronous operation. The task result contains the incoming if found; otherwise, null.
+ ///
+ /// A task that represents the asynchronous operation. The task result contains the incoming if found; otherwise,
+ /// null.
+ ///
Task GetDocumentAsync(string collection, string key, CancellationToken cancellationToken = default);
///
- /// Asynchronously retrieves all documents belonging to the specified collection.
+ /// Asynchronously retrieves all documents belonging to the specified collection.
///
/// The name of the collection from which to retrieve documents. Cannot be null or empty.
/// A cancellation token that can be used to cancel the asynchronous operation.
- /// A task that represents the asynchronous operation. The task result contains an enumerable collection of
- /// documents in the specified collection. The collection is empty if no documents are found.
- Task> GetDocumentsByCollectionAsync(string collection, CancellationToken cancellationToken = default);
+ ///
+ /// A task that represents the asynchronous operation. The task result contains an enumerable collection of
+ /// documents in the specified collection. The collection is empty if no documents are found.
+ ///
+ Task> GetDocumentsByCollectionAsync(string collection,
+ CancellationToken cancellationToken = default);
///
- /// Asynchronously inserts a batch of documents into the data store.
+ /// Asynchronously inserts a batch of documents into the data store.
///
/// The collection of documents to insert. Cannot be null or contain null elements.
/// A cancellation token that can be used to cancel the operation.
- /// A task that represents the asynchronous operation. The task result is if all documents
- /// were inserted successfully; otherwise, .
- Task InsertBatchDocumentsAsync(IEnumerable documents, CancellationToken cancellationToken = default);
+ ///
+ /// A task that represents the asynchronous operation. The task result is if all documents
+ /// were inserted successfully; otherwise, .
+ ///
+ Task InsertBatchDocumentsAsync(IEnumerable documents,
+ CancellationToken cancellationToken = default);
///
- /// Asynchronously updates the specified incoming in the data store.
+ /// Asynchronously updates the specified incoming in the data store.
///
/// The incoming to update. Cannot be null.
/// A cancellation token that can be used to cancel the update operation.
- /// A task that represents the asynchronous operation. The task result is if the incoming was
- /// successfully updated; otherwise, .
+ ///
+ /// A task that represents the asynchronous operation. The task result is if the incoming was
+ /// successfully updated; otherwise, .
+ ///
Task PutDocumentAsync(Document document, CancellationToken cancellationToken = default);
///
- /// Asynchronously updates a batch of documents in the data store.
+ /// Asynchronously updates a batch of documents in the data store.
///
/// The collection of documents to update. Cannot be null or contain null elements.
/// A cancellation token that can be used to cancel the operation.
- /// A task that represents the asynchronous operation. The task result is if all documents
- /// were updated successfully; otherwise, .
- Task UpdateBatchDocumentsAsync(IEnumerable documents, CancellationToken cancellationToken = default);
+ ///
+ /// A task that represents the asynchronous operation. The task result is if all documents
+ /// were updated successfully; otherwise, .
+ ///
+ Task UpdateBatchDocumentsAsync(IEnumerable documents,
+ CancellationToken cancellationToken = default);
///
- /// Asynchronously deletes a incoming identified by the specified key from the given collection.
+ /// Asynchronously deletes a incoming identified by the specified key from the given collection.
///
/// The name of the collection containing the incoming to delete. Cannot be null or empty.
/// The unique key identifying the incoming to delete. Cannot be null or empty.
/// A cancellation token that can be used to cancel the delete operation.
- /// A task that represents the asynchronous delete operation. The task result is if the
- /// incoming was successfully deleted; otherwise, .
+ ///
+ /// A task that represents the asynchronous delete operation. The task result is if the
+ /// incoming was successfully deleted; otherwise, .
+ ///
Task DeleteDocumentAsync(string collection, string key, CancellationToken cancellationToken = default);
- ///
- /// Asynchronously deletes a batch of documents identified by their keys.
- ///
- ///
- /// If any of the specified documents cannot be deleted, the method returns but does not
- /// throw an exception. The operation is performed asynchronously and may complete partially if cancellation is requested.
- ///
- /// A collection of incoming keys that specify the documents to delete. Cannot be null or contain null or empty
- /// values.
+ ///
+ /// Asynchronously deletes a batch of documents identified by their keys.
+ ///
+ ///
+ /// If any of the specified documents cannot be deleted, the method returns but does not
+ /// throw an exception. The operation is performed asynchronously and may complete partially if cancellation is
+ /// requested.
+ ///
+ ///
+ /// A collection of incoming keys that specify the documents to delete. Cannot be null or contain null or empty
+ /// values.
+ ///
/// A cancellation token that can be used to cancel the delete operation.
- /// A task that represents the asynchronous delete operation. The task result is if all
- /// specified documents were successfully deleted; otherwise, .
- Task DeleteBatchDocumentsAsync(IEnumerable documentKeys, CancellationToken cancellationToken = default);
+ ///
+ /// A task that represents the asynchronous delete operation. The task result is if all
+ /// specified documents were successfully deleted; otherwise, .
+ ///
+ Task DeleteBatchDocumentsAsync(IEnumerable documentKeys,
+ CancellationToken cancellationToken = default);
///
- /// Asynchronously merges the specified incoming with existing data and returns the updated incoming.
+ /// Asynchronously merges the specified incoming with existing data and returns the updated incoming.
///
/// The incoming to merge. Cannot be null.
/// A cancellation token that can be used to cancel the merge operation.
@@ -93,11 +114,14 @@ public interface IDocumentStore : ISnapshotable
Task MergeAsync(Document incoming, CancellationToken cancellationToken = default);
///
- /// Asynchronously retrieves documents identified by the specified collection and key pairs.
+ /// Asynchronously retrieves documents identified by the specified collection and key pairs.
///
- /// A list of tuples, each containing the collection name and the document key that uniquely identify the documents
- /// to retrieve. Cannot be null or empty.
+ ///
+ /// A list of tuples, each containing the collection name and the document key that uniquely identify the documents
+ /// to retrieve. Cannot be null or empty.
+ ///
/// A cancellation token that can be used to cancel the asynchronous operation.
/// A task that represents the asynchronous retrieval operation.
- Task> GetDocumentsAsync(List<(string Collection, string Key)> documentKeys, CancellationToken cancellationToken);
-}
+ Task> GetDocumentsAsync(List<(string Collection, string Key)> documentKeys,
+ CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Storage/IOplogStore.cs b/src/ZB.MOM.WW.CBDDC.Core/Storage/IOplogStore.cs
index 0edb9d6..b6414bc 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Storage/IOplogStore.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Storage/IOplogStore.cs
@@ -1,5 +1,4 @@
using System;
-using System.Buffers;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -7,17 +6,17 @@ using System.Threading.Tasks;
namespace ZB.MOM.WW.CBDDC.Core.Storage;
///
-/// Handles operations related to the Operation Log (Oplog), synchronization, and logical clocks.
+/// Handles operations related to the Operation Log (Oplog), synchronization, and logical clocks.
///
public interface IOplogStore : ISnapshotable
{
///
- /// Occurs when changes are applied to the store from external sources (sync).
+ /// Occurs when changes are applied to the store from external sources (sync).
///
event EventHandler ChangesApplied;
///
- /// Appends a new entry to the operation log asynchronously.
+ /// Appends a new entry to the operation log asynchronously.
///
/// The operation log entry to append. Cannot be null.
/// A cancellation token that can be used to cancel the append operation.
@@ -25,57 +24,64 @@ public interface IOplogStore : ISnapshotable
Task AppendOplogEntryAsync(OplogEntry entry, CancellationToken cancellationToken = default);
///
- /// Asynchronously retrieves all oplog entries that occurred after the specified timestamp.
+ /// Asynchronously retrieves all oplog entries that occurred after the specified timestamp.
///
/// The timestamp after which oplog entries should be returned.
/// An optional collection of collection names to filter the results.
/// A cancellation token that can be used to cancel the asynchronous operation.
/// A task that represents the asynchronous operation containing matching oplog entries.
- Task> GetOplogAfterAsync(HlcTimestamp timestamp, IEnumerable? collections = null, CancellationToken cancellationToken = default);
+ Task> GetOplogAfterAsync(HlcTimestamp timestamp, IEnumerable? collections = null,
+ CancellationToken cancellationToken = default);
///
- /// Asynchronously retrieves the latest observed hybrid logical clock (HLC) timestamp.
+ /// Asynchronously retrieves the latest observed hybrid logical clock (HLC) timestamp.
///
/// A cancellation token that can be used to cancel the operation.
/// A task that represents the asynchronous operation containing the latest HLC timestamp.
Task GetLatestTimestampAsync(CancellationToken cancellationToken = default);
///
- /// Asynchronously retrieves the current vector clock representing the state of distributed events.
+ /// Asynchronously retrieves the current vector clock representing the state of distributed events.
///
/// A cancellation token that can be used to cancel the asynchronous operation.
/// A task that represents the asynchronous operation containing the current vector clock.
Task GetVectorClockAsync(CancellationToken cancellationToken = default);
///
- /// Retrieves a collection of oplog entries for the specified node that occurred after the given timestamp.
+ /// Retrieves a collection of oplog entries for the specified node that occurred after the given timestamp.
///
/// The unique identifier of the node for which to retrieve oplog entries. Cannot be null or empty.
/// The timestamp after which oplog entries should be returned.
/// An optional collection of collection names to filter the oplog entries.
/// A cancellation token that can be used to cancel the asynchronous operation.
/// A task that represents the asynchronous operation containing oplog entries for the specified node.
- Task> GetOplogForNodeAfterAsync(string nodeId, HlcTimestamp since, IEnumerable? collections = null, CancellationToken cancellationToken = default);
+ Task> GetOplogForNodeAfterAsync(string nodeId, HlcTimestamp since,
+ IEnumerable? collections = null, CancellationToken cancellationToken = default);
///
- /// Asynchronously retrieves the hash of the most recent entry for the specified node.
+ /// Asynchronously retrieves the hash of the most recent entry for the specified node.
///
- /// The unique identifier of the node for which to retrieve the last entry hash. Cannot be null or empty.
+ ///
+ /// The unique identifier of the node for which to retrieve the last entry hash. Cannot be null or
+ /// empty.
+ ///
/// A cancellation token that can be used to cancel the operation.
/// A task that represents the asynchronous operation containing the hash string of the last entry or null.
Task GetLastEntryHashAsync(string nodeId, CancellationToken cancellationToken = default);
///
- /// Asynchronously retrieves a sequence of oplog entries representing the chain between the specified start and end hashes.
+ /// Asynchronously retrieves a sequence of oplog entries representing the chain between the specified start and end
+ /// hashes.
///
/// The hash of the first entry in the chain range. Cannot be null or empty.
/// The hash of the last entry in the chain range. Cannot be null or empty.
/// A cancellation token that can be used to cancel the asynchronous operation.
/// A task that represents the asynchronous operation containing OplogEntry objects in chain order.
- Task> GetChainRangeAsync(string startHash, string endHash, CancellationToken cancellationToken = default);
+ Task> GetChainRangeAsync(string startHash, string endHash,
+ CancellationToken cancellationToken = default);
///
- /// Asynchronously retrieves the oplog entry associated with the specified hash value.
+ /// Asynchronously retrieves the oplog entry associated with the specified hash value.
///
/// The hash string identifying the oplog entry to retrieve. Cannot be null or empty.
/// A cancellation token that can be used to cancel the asynchronous operation.
@@ -83,7 +89,7 @@ public interface IOplogStore : ISnapshotable
Task GetEntryByHashAsync(string hash, CancellationToken cancellationToken = default);
///
- /// Applies a batch of oplog entries asynchronously to the target data store.
+ /// Applies a batch of oplog entries asynchronously to the target data store.
///
/// A collection of OplogEntry objects representing the operations to apply. Cannot be null.
/// A cancellation token that can be used to cancel the batch operation.
@@ -91,11 +97,10 @@ public interface IOplogStore : ISnapshotable
Task ApplyBatchAsync(IEnumerable oplogEntries, CancellationToken cancellationToken = default);
///
- /// Asynchronously removes entries from the oplog that are older than the specified cutoff timestamp.
+ /// Asynchronously removes entries from the oplog that are older than the specified cutoff timestamp.
///
/// The timestamp that defines the upper bound for entries to be pruned.
/// A cancellation token that can be used to cancel the prune operation.
/// A task that represents the asynchronous prune operation.
Task PruneOplogAsync(HlcTimestamp cutoff, CancellationToken cancellationToken = default);
-
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Storage/IPeerConfigurationStore.cs b/src/ZB.MOM.WW.CBDDC.Core/Storage/IPeerConfigurationStore.cs
index 47f3fe4..48921db 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Storage/IPeerConfigurationStore.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Storage/IPeerConfigurationStore.cs
@@ -6,26 +6,26 @@ using ZB.MOM.WW.CBDDC.Core.Network;
namespace ZB.MOM.WW.CBDDC.Core.Storage;
///
-/// Handles storage and retrieval of remote peer configurations.
+/// Handles storage and retrieval of remote peer configurations.
///
public interface IPeerConfigurationStore : ISnapshotable
{
///
- /// Saves or updates a remote peer configuration in the persistent store.
+ /// Saves or updates a remote peer configuration in the persistent store.
///
/// The remote peer configuration to save.
/// Cancellation token.
Task SaveRemotePeerAsync(RemotePeerConfiguration peer, CancellationToken cancellationToken = default);
///
- /// Retrieves all remote peer configurations from the persistent store.
+ /// Retrieves all remote peer configurations from the persistent store.
///
/// Cancellation token.
/// Collection of remote peer configurations.
Task> GetRemotePeersAsync(CancellationToken cancellationToken = default);
///
- /// Asynchronously retrieves the configuration for a remote peer identified by the specified node ID.
+ /// Asynchronously retrieves the configuration for a remote peer identified by the specified node ID.
///
/// The unique identifier of the remote peer whose configuration is to be retrieved.
/// A cancellation token that can be used to cancel the asynchronous operation.
@@ -33,9 +33,9 @@ public interface IPeerConfigurationStore : ISnapshotable GetRemotePeerAsync(string nodeId, CancellationToken cancellationToken);
///
- /// Removes a remote peer configuration from the persistent store.
+ /// Removes a remote peer configuration from the persistent store.
///
/// The unique identifier of the peer to remove.
/// Cancellation token.
Task RemoveRemotePeerAsync(string nodeId, CancellationToken cancellationToken = default);
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Storage/IPeerOplogConfirmationStore.cs b/src/ZB.MOM.WW.CBDDC.Core/Storage/IPeerOplogConfirmationStore.cs
index 5d6b7d3..8c27597 100644
--- a/src/ZB.MOM.WW.CBDDC.Core/Storage/IPeerOplogConfirmationStore.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Storage/IPeerOplogConfirmationStore.cs
@@ -6,12 +6,12 @@ using ZB.MOM.WW.CBDDC.Core.Network;
namespace ZB.MOM.WW.CBDDC.Core.Storage;
///
-/// Defines persistence operations for peer oplog confirmation tracking.
+/// Defines persistence operations for peer oplog confirmation tracking.
///
public interface IPeerOplogConfirmationStore : ISnapshotable
{
///
- /// Ensures the specified peer is tracked for confirmation-based pruning.
+ /// Ensures the specified peer is tracked for confirmation-based pruning.
///
/// The peer node identifier.
/// The peer network address.
@@ -24,7 +24,7 @@ public interface IPeerOplogConfirmationStore : ISnapshotable
- /// Updates the confirmation watermark for a tracked peer and source node.
+ /// Updates the confirmation watermark for a tracked peer and source node.
///
/// The tracked peer node identifier.
/// The source node identifier of the confirmed oplog stream.
@@ -39,14 +39,14 @@ public interface IPeerOplogConfirmationStore : ISnapshotable
- /// Gets all persisted peer confirmations.
+ /// Gets all persisted peer confirmations.
///
/// A cancellation token.
/// All peer confirmations.
Task> GetConfirmationsAsync(CancellationToken cancellationToken = default);
///
- /// Gets persisted confirmations for a specific tracked peer.
+ /// Gets persisted confirmations for a specific tracked peer.
///
/// The peer node identifier.
/// A cancellation token.
@@ -56,16 +56,16 @@ public interface IPeerOplogConfirmationStore : ISnapshotable
- /// Deactivates tracking for the specified peer.
+ /// Deactivates tracking for the specified peer.
///
/// The peer node identifier.
/// A cancellation token.
Task RemovePeerTrackingAsync(string peerNodeId, CancellationToken cancellationToken = default);
///
- /// Gets all active tracked peer identifiers.
+ /// Gets all active tracked peer identifiers.
///
/// A cancellation token.
/// Distinct active tracked peer identifiers.
Task> GetActiveTrackedPeersAsync(CancellationToken cancellationToken = default);
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotMetadataStore.cs b/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotMetadataStore.cs
index 5a2b564..ed836d4 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotMetadataStore.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotMetadataStore.cs
@@ -7,16 +7,21 @@ namespace ZB.MOM.WW.CBDDC.Core.Storage;
public interface ISnapshotMetadataStore : ISnapshotable
{
///
- /// Asynchronously retrieves the snapshot metadata associated with the specified node identifier.
+ /// Asynchronously retrieves the snapshot metadata associated with the specified node identifier.
///
- /// The unique identifier of the node for which to retrieve snapshot metadata. Cannot be null or empty.
+ ///
+ /// The unique identifier of the node for which to retrieve snapshot metadata. Cannot be null or
+ /// empty.
+ ///
/// A token to monitor for cancellation requests.
- /// A task that represents the asynchronous operation. The task result contains the
- /// for the specified node if found; otherwise, .
+ ///
+ /// A task that represents the asynchronous operation. The task result contains the
+ /// for the specified node if found; otherwise, .
+ ///
Task GetSnapshotMetadataAsync(string nodeId, CancellationToken cancellationToken = default);
///
- /// Asynchronously inserts the specified snapshot metadata into the data store.
+ /// Asynchronously inserts the specified snapshot metadata into the data store.
///
/// The snapshot metadata to insert. Cannot be null.
/// A cancellation token that can be used to cancel the asynchronous operation.
@@ -24,7 +29,7 @@ public interface ISnapshotMetadataStore : ISnapshotable
Task InsertSnapshotMetadataAsync(SnapshotMetadata metadata, CancellationToken cancellationToken = default);
///
- /// Asynchronously updates the metadata for an existing snapshot.
+ /// Asynchronously updates the metadata for an existing snapshot.
///
/// The metadata object representing the snapshot to update. Cannot be null.
/// A cancellation token that can be used to cancel the asynchronous operation.
@@ -32,7 +37,7 @@ public interface ISnapshotMetadataStore : ISnapshotable
Task UpdateSnapshotMetadataAsync(SnapshotMetadata existingMeta, CancellationToken cancellationToken = default);
///
- /// Asynchronously retrieves the hash of the current snapshot for the specified node.
+ /// Asynchronously retrieves the hash of the current snapshot for the specified node.
///
/// The unique identifier of the node for which to obtain the snapshot hash.
/// A cancellation token that can be used to cancel the operation.
@@ -40,9 +45,9 @@ public interface ISnapshotMetadataStore : ISnapshotable
Task GetSnapshotHashAsync(string nodeId, CancellationToken cancellationToken = default);
///
- /// Gets all snapshot metadata entries. Used for initializing VectorClock cache.
+ /// Gets all snapshot metadata entries. Used for initializing VectorClock cache.
///
/// A cancellation token.
/// All snapshot metadata entries.
Task> GetAllSnapshotMetadataAsync(CancellationToken cancellationToken = default);
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotService.cs b/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotService.cs
index 31c8e63..cf391ce 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotService.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotService.cs
@@ -5,12 +5,12 @@ using System.Threading.Tasks;
namespace ZB.MOM.WW.CBDDC.Core.Storage;
///
-/// Handles full database lifecycle operations such as snapshots, replacement, and clearing data.
+/// Handles full database lifecycle operations such as snapshots, replacement, and clearing data.
///
public interface ISnapshotService
{
///
- /// Asynchronously creates a snapshot of the current state and writes it to the specified destination stream.
+ /// Asynchronously creates a snapshot of the current state and writes it to the specified destination stream.
///
/// The stream to which the snapshot data will be written.
/// A cancellation token that can be used to cancel the snapshot creation operation.
@@ -18,7 +18,7 @@ public interface ISnapshotService
Task CreateSnapshotAsync(Stream destination, CancellationToken cancellationToken = default);
///
- /// Replaces the existing database with the contents provided in the specified stream asynchronously.
+ /// Replaces the existing database with the contents provided in the specified stream asynchronously.
///
/// A stream containing the new database data to be used for replacement.
/// A cancellation token that can be used to cancel the operation.
@@ -26,10 +26,10 @@ public interface ISnapshotService
Task ReplaceDatabaseAsync(Stream databaseStream, CancellationToken cancellationToken = default);
///
- /// Merges the provided snapshot stream into the current data store asynchronously.
+ /// Merges the provided snapshot stream into the current data store asynchronously.
///
/// A stream containing the snapshot data to be merged.
/// A cancellation token that can be used to cancel the merge operation.
/// A task that represents the asynchronous merge operation.
Task MergeSnapshotAsync(Stream snapshotStream, CancellationToken cancellationToken = default);
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotable.cs b/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotable.cs
index 7737ae2..9c12ca7 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotable.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Storage/ISnapshotable.cs
@@ -7,24 +7,28 @@ namespace ZB.MOM.WW.CBDDC.Core.Storage;
public interface ISnapshotable
{
///
- /// Asynchronously deletes the underlying data store and all of its contents.
+ /// Asynchronously deletes the underlying data store and all of its contents.
///
/// A cancellation token that can be used to cancel the drop operation.
- /// After calling this method, the data store and all stored data will be permanently removed.
- /// This operation cannot be undone. Any further operations on the data store may result in errors.
+ ///
+ /// After calling this method, the data store and all stored data will be permanently removed.
+ /// This operation cannot be undone. Any further operations on the data store may result in errors.
+ ///
/// A task that represents the asynchronous drop operation.
Task DropAsync(CancellationToken cancellationToken = default);
///
- /// Asynchronously exports a collection of items of type T.
+ /// Asynchronously exports a collection of items of type T.
///
/// A cancellation token that can be used to cancel the export operation.
- /// A task that represents the asynchronous export operation. The task result contains an enumerable collection of
- /// exported items of type T.
+ ///
+ /// A task that represents the asynchronous export operation. The task result contains an enumerable collection of
+ /// exported items of type T.
+ ///
Task> ExportAsync(CancellationToken cancellationToken = default);
///
- /// Imports the specified collection of items asynchronously.
+ /// Imports the specified collection of items asynchronously.
///
/// The collection of items to import. Cannot be null. Each item will be processed in sequence.
/// A cancellation token that can be used to cancel the import operation.
@@ -32,13 +36,15 @@ public interface ISnapshotable
Task ImportAsync(IEnumerable items, CancellationToken cancellationToken = default);
///
- /// Merges the specified collection of items into the target data store asynchronously.
+ /// Merges the specified collection of items into the target data store asynchronously.
///
- /// If the operation is canceled via the provided cancellation token, the returned task will be
- /// in a canceled state. The merge operation may update existing items or add new items, depending on the
- /// implementation.
+ ///
+ /// If the operation is canceled via the provided cancellation token, the returned task will be
+ /// in a canceled state. The merge operation may update existing items or add new items, depending on the
+ /// implementation.
+ ///
/// The collection of items to merge into the data store. Cannot be null.
/// A cancellation token that can be used to cancel the merge operation.
/// A task that represents the asynchronous merge operation.
Task MergeAsync(IEnumerable items, CancellationToken cancellationToken = default);
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Storage/IVectorClockService.cs b/src/ZB.MOM.WW.CBDDC.Core/Storage/IVectorClockService.cs
index 6bbed68..5db170f 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Storage/IVectorClockService.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Storage/IVectorClockService.cs
@@ -1,49 +1,49 @@
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace ZB.MOM.WW.CBDDC.Core.Storage;
-
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ZB.MOM.WW.CBDDC.Core.Storage;
+
///
-/// Manages Vector Clock state for the local node.
-/// Tracks the latest timestamp and hash per node for sync coordination.
+/// Manages Vector Clock state for the local node.
+/// Tracks the latest timestamp and hash per node for sync coordination.
///
public interface IVectorClockService
{
///
- /// Indicates whether the cache has been populated with initial data.
- /// Reset to false by .
+ /// Indicates whether the cache has been populated with initial data.
+ /// Reset to false by .
///
bool IsInitialized { get; set; }
///
- /// Updates the cache with a new OplogEntry's timestamp and hash.
- /// Called by both DocumentStore (local CDC) and OplogStore (remote sync).
+ /// Updates the cache with a new OplogEntry's timestamp and hash.
+ /// Called by both DocumentStore (local CDC) and OplogStore (remote sync).
///
/// The oplog entry containing timestamp and hash data.
void Update(OplogEntry entry);
///
- /// Returns the current Vector Clock built from cached node timestamps.
+ /// Returns the current Vector Clock built from cached node timestamps.
///
/// A token used to cancel the operation.
Task GetVectorClockAsync(CancellationToken cancellationToken = default);
///
- /// Returns the latest known timestamp across all nodes.
+ /// Returns the latest known timestamp across all nodes.
///
/// A token used to cancel the operation.
Task GetLatestTimestampAsync(CancellationToken cancellationToken = default);
///
- /// Returns the last known hash for the specified node.
- /// Returns null if the node is unknown.
+ /// Returns the last known hash for the specified node.
+ /// Returns null if the node is unknown.
///
/// The node identifier.
string? GetLastHash(string nodeId);
///
- /// Updates the cache with a specific node's timestamp and hash.
- /// Used for snapshot metadata fallback.
+ /// Updates the cache with a specific node's timestamp and hash.
+ /// Used for snapshot metadata fallback.
///
/// The node identifier.
/// The timestamp to store for the node.
@@ -51,8 +51,8 @@ public interface IVectorClockService
void UpdateNode(string nodeId, HlcTimestamp timestamp, string hash);
///
- /// Clears the cache and resets to false,
- /// forcing re-initialization on next access.
+ /// Clears the cache and resets to false,
+ /// forcing re-initialization on next access.
///
void Invalidate();
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Sync/IConflictResolver.cs b/src/ZB.MOM.WW.CBDDC.Core/Sync/IConflictResolver.cs
index 432f45b..10eb508 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Sync/IConflictResolver.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Sync/IConflictResolver.cs
@@ -1,21 +1,9 @@
-using System.Text.Json;
-using ZB.MOM.WW.CBDDC.Core;
-
-namespace ZB.MOM.WW.CBDDC.Core.Sync;
-
+namespace ZB.MOM.WW.CBDDC.Core.Sync;
+
public class ConflictResolutionResult
{
///
- /// Gets a value indicating whether the remote change should be applied.
- ///
- public bool ShouldApply { get; }
- ///
- /// Gets the merged document to apply when conflict resolution produced one.
- ///
- public Document? MergedDocument { get; }
-
- ///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// Indicates whether the change should be applied.
/// The merged document produced by resolution, if any.
@@ -26,25 +14,42 @@ public class ConflictResolutionResult
}
///
- /// Creates a result indicating that the resolved document should be applied.
+ /// Gets a value indicating whether the remote change should be applied.
+ ///
+ public bool ShouldApply { get; }
+
+ ///
+ /// Gets the merged document to apply when conflict resolution produced one.
+ ///
+ public Document? MergedDocument { get; }
+
+ ///
+ /// Creates a result indicating that the resolved document should be applied.
///
/// The merged document to apply.
/// A resolution result that applies the provided document.
- public static ConflictResolutionResult Apply(Document document) => new(true, document);
+ public static ConflictResolutionResult Apply(Document document)
+ {
+ return new ConflictResolutionResult(true, document);
+ }
+
///
- /// Creates a result indicating that the remote change should be ignored.
+ /// Creates a result indicating that the remote change should be ignored.
///
/// A resolution result that skips applying the remote change.
- public static ConflictResolutionResult Ignore() => new(false, null);
+ public static ConflictResolutionResult Ignore()
+ {
+ return new ConflictResolutionResult(false, null);
+ }
}
public interface IConflictResolver
{
///
- /// Resolves a conflict between local state and a remote oplog entry.
+ /// Resolves a conflict between local state and a remote oplog entry.
///
/// The local document state, if present.
/// The incoming remote oplog entry.
/// The resolution outcome indicating whether and how to apply changes.
ConflictResolutionResult Resolve(Document? local, OplogEntry remote);
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Sync/IOfflineQueue.cs b/src/ZB.MOM.WW.CBDDC.Core/Sync/IOfflineQueue.cs
index 2928136..ec1a284 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Sync/IOfflineQueue.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Sync/IOfflineQueue.cs
@@ -1,40 +1,40 @@
-ο»Ώusing System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace ZB.MOM.WW.CBDDC.Core.Sync
+ο»Ώusing System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ZB.MOM.WW.CBDDC.Core.Sync;
+
+///
+/// Represents a queue for operations that should be executed when connectivity is restored.
+///
+public interface IOfflineQueue
{
///
- /// Represents a queue for operations that should be executed when connectivity is restored.
+ /// Gets the number of pending operations in the queue.
///
- public interface IOfflineQueue
- {
- ///
- /// Gets the number of pending operations in the queue.
- ///
- int Count { get; }
+ int Count { get; }
- ///
- /// Clears all pending operations from the queue.
- ///
- /// A task that represents the asynchronous operation.
- Task Clear();
+ ///
+ /// Clears all pending operations from the queue.
+ ///
+ /// A task that represents the asynchronous operation.
+ Task Clear();
- ///
- /// Enqueues a pending operation.
- ///
- /// The operation to enqueue.
- /// A task that represents the asynchronous operation.
- Task Enqueue(PendingOperation operation);
+ ///
+ /// Enqueues a pending operation.
+ ///
+ /// The operation to enqueue.
+ /// A task that represents the asynchronous operation.
+ Task Enqueue(PendingOperation operation);
- ///
- /// Flushes the queue by executing each pending operation.
- ///
- /// The delegate used to execute each operation.
- /// A token used to cancel the flush operation.
- ///
- /// A task that returns a tuple containing the number of successful and failed operations.
- ///
- Task<(int Successful, int Failed)> FlushAsync(Func executor, CancellationToken cancellationToken = default);
- }
-}
+ ///
+ /// Flushes the queue by executing each pending operation.
+ ///
+ /// The delegate used to execute each operation.
+ /// A token used to cancel the flush operation.
+ ///
+ /// A task that returns a tuple containing the number of successful and failed operations.
+ ///
+ Task<(int Successful, int Failed)> FlushAsync(Func executor,
+ CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Sync/LastWriteWinsConflictResolver.cs b/src/ZB.MOM.WW.CBDDC.Core/Sync/LastWriteWinsConflictResolver.cs
index 9192b12..2f6bef4 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Sync/LastWriteWinsConflictResolver.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Sync/LastWriteWinsConflictResolver.cs
@@ -1,24 +1,22 @@
-using System.Text.Json;
-using ZB.MOM.WW.CBDDC.Core;
-
namespace ZB.MOM.WW.CBDDC.Core.Sync;
-public class LastWriteWinsConflictResolver : IConflictResolver
-{
- ///
- /// Resolves document conflicts by preferring the entry with the latest timestamp.
- ///
- /// The local document, if available.
- /// The incoming remote oplog entry.
- /// The conflict resolution result indicating whether to apply or ignore the remote change.
- public ConflictResolutionResult Resolve(Document? local, OplogEntry remote)
- {
+public class LastWriteWinsConflictResolver : IConflictResolver
+{
+ ///
+ /// Resolves document conflicts by preferring the entry with the latest timestamp.
+ ///
+ /// The local document, if available.
+ /// The incoming remote oplog entry.
+ /// The conflict resolution result indicating whether to apply or ignore the remote change.
+ public ConflictResolutionResult Resolve(Document? local, OplogEntry remote)
+ {
// If no local document exists, always apply remote change
if (local == null)
{
// Construct new document from oplog entry
var content = remote.Payload ?? default;
- var newDoc = new Document(remote.Collection, remote.Key, content, remote.Timestamp, remote.Operation == OperationType.Delete);
+ var newDoc = new Document(remote.Collection, remote.Key, content, remote.Timestamp,
+ remote.Operation == OperationType.Delete);
return ConflictResolutionResult.Apply(newDoc);
}
@@ -27,11 +25,12 @@ public class LastWriteWinsConflictResolver : IConflictResolver
{
// Remote is newer, apply it
var content = remote.Payload ?? default;
- var newDoc = new Document(remote.Collection, remote.Key, content, remote.Timestamp, remote.Operation == OperationType.Delete);
+ var newDoc = new Document(remote.Collection, remote.Key, content, remote.Timestamp,
+ remote.Operation == OperationType.Delete);
return ConflictResolutionResult.Apply(newDoc);
}
// Local is newer or equal, ignore remote
return ConflictResolutionResult.Ignore();
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Sync/OfflineQueue.cs b/src/ZB.MOM.WW.CBDDC.Core/Sync/OfflineQueue.cs
index b324d48..74aa81d 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Sync/OfflineQueue.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Sync/OfflineQueue.cs
@@ -1,37 +1,38 @@
-using ZB.MOM.WW.CBDDC.Core.Network;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
-using System;
-using System.Collections.Generic;
-using System.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using ZB.MOM.WW.CBDDC.Core.Network;
namespace ZB.MOM.WW.CBDDC.Core.Sync;
///
-/// Queue for operations performed while offline.
+/// Queue for operations performed while offline.
///
-public class OfflineQueue : IOfflineQueue
-{
- private readonly IPeerNodeConfigurationProvider _peerNodeConfigurationProvider;
- private readonly Queue _queue = new();
- private readonly ILogger _logger;
- private readonly object _lock = new();
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The configuration provider used for queue limits.
- /// The logger instance.
- public OfflineQueue(IPeerNodeConfigurationProvider peerNodeConfigurationProvider, ILogger? logger = null)
- {
- _peerNodeConfigurationProvider = peerNodeConfigurationProvider;
- _logger = logger ?? NullLogger.Instance;
- }
+public class OfflineQueue : IOfflineQueue
+{
+ private readonly object _lock = new();
+ private readonly ILogger _logger;
+ private readonly IPeerNodeConfigurationProvider _peerNodeConfigurationProvider;
+ private readonly Queue _queue = new();
///
- /// Gets the number of pending operations.
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration provider used for queue limits.
+ /// The logger instance.
+ public OfflineQueue(IPeerNodeConfigurationProvider peerNodeConfigurationProvider,
+ ILogger? logger = null)
+ {
+ _peerNodeConfigurationProvider = peerNodeConfigurationProvider;
+ _logger = logger ?? NullLogger.Instance;
+ }
+
+ ///
+ /// Gets the number of pending operations.
///
public int Count
{
@@ -44,15 +45,15 @@ public class OfflineQueue : IOfflineQueue
}
}
- ///
- /// Enqueues an operation for later execution.
- ///
- /// The pending operation to enqueue.
- /// A task that represents the asynchronous enqueue operation.
- public async Task Enqueue(PendingOperation operation)
- {
- var config = await _peerNodeConfigurationProvider.GetConfiguration();
- lock (_lock)
+ ///
+ /// Enqueues an operation for later execution.
+ ///
+ /// The pending operation to enqueue.
+ /// A task that represents the asynchronous enqueue operation.
+ public async Task Enqueue(PendingOperation operation)
+ {
+ var config = await _peerNodeConfigurationProvider.GetConfiguration();
+ lock (_lock)
{
if (_queue.Count >= config.MaxQueueSize)
{
@@ -67,15 +68,16 @@ public class OfflineQueue : IOfflineQueue
}
}
- ///
- /// Flushes all pending operations.
- ///
- /// The delegate that executes each pending operation.
- /// A token used to cancel the operation.
- /// A task whose result contains the number of successful and failed operations.
- public async Task<(int Successful, int Failed)> FlushAsync(Func executor, CancellationToken cancellationToken = default)
- {
- List operations;
+ ///
+ /// Flushes all pending operations.
+ ///
+ /// The delegate that executes each pending operation.
+ /// A token used to cancel the operation.
+ /// A task whose result contains the number of successful and failed operations.
+ public async Task<(int Successful, int Failed)> FlushAsync(Func executor,
+ CancellationToken cancellationToken = default)
+ {
+ List operations;
lock (_lock)
{
@@ -91,11 +93,10 @@ public class OfflineQueue : IOfflineQueue
_logger.LogInformation("Flushing {Count} pending operations", operations.Count);
- int successful = 0;
- int failed = 0;
+ var successful = 0;
+ var failed = 0;
foreach (var op in operations)
- {
try
{
await executor(op);
@@ -107,7 +108,6 @@ public class OfflineQueue : IOfflineQueue
_logger.LogError(ex, "Failed to execute pending {Type} operation for {Collection}:{Key}",
op.Type, op.Collection, op.Key);
}
- }
_logger.LogInformation("Flush completed: {Successful} successful, {Failed} failed",
successful, failed);
@@ -116,15 +116,15 @@ public class OfflineQueue : IOfflineQueue
}
///
- /// Clears all pending operations.
+ /// Clears all pending operations.
///
public async Task Clear()
{
lock (_lock)
{
- var count = _queue.Count;
+ int count = _queue.Count;
_queue.Clear();
_logger.LogInformation("Cleared {Count} pending operations", count);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Sync/PendingOperation.cs b/src/ZB.MOM.WW.CBDDC.Core/Sync/PendingOperation.cs
index 9d67ef6..4931ee9 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Sync/PendingOperation.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Sync/PendingOperation.cs
@@ -1,32 +1,34 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace ZB.MOM.WW.CBDDC.Core.Sync;
-
-///
-/// Represents a pending operation to be executed when connection is restored.
-///
+using System;
+
+namespace ZB.MOM.WW.CBDDC.Core.Sync;
+
+///
+/// Represents a pending operation to be executed when connection is restored.
+///
public class PendingOperation
{
///
- /// Gets or sets the operation type.
+ /// Gets or sets the operation type.
///
public string Type { get; set; } = "";
+
///
- /// Gets or sets the collection targeted by the operation.
+ /// Gets or sets the collection targeted by the operation.
///
public string Collection { get; set; } = "";
+
///
- /// Gets or sets the document key targeted by the operation.
+ /// Gets or sets the document key targeted by the operation.
///
public string Key { get; set; } = "";
+
///
- /// Gets or sets the payload associated with the operation.
+ /// Gets or sets the payload associated with the operation.
///
public object? Data { get; set; }
+
///
- /// Gets or sets the UTC time when the operation was queued.
+ /// Gets or sets the UTC time when the operation was queued.
///
public DateTime QueuedAt { get; set; }
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/Sync/RecursiveNodeMergeConflictResolver.cs b/src/ZB.MOM.WW.CBDDC.Core/Sync/RecursiveNodeMergeConflictResolver.cs
index fe7c2ff..1fb5134 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/Sync/RecursiveNodeMergeConflictResolver.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/Sync/RecursiveNodeMergeConflictResolver.cs
@@ -1,28 +1,27 @@
-using System;
using System.Buffers;
using System.Collections.Generic;
-using System.IO;
using System.Text.Json;
-namespace ZB.MOM.WW.CBDDC.Core.Sync;
-
-///
-/// Resolves merge conflicts by recursively merging object and array nodes.
-///
-public class RecursiveNodeMergeConflictResolver : IConflictResolver
-{
- ///
- /// Resolves a conflict between a local document and a remote operation.
- ///
- /// The local document, or if none exists.
- /// The remote operation to apply.
- /// The conflict resolution result indicating whether and what to apply.
- public ConflictResolutionResult Resolve(Document? local, OplogEntry remote)
- {
- if (local == null)
- {
+namespace ZB.MOM.WW.CBDDC.Core.Sync;
+
+///
+/// Resolves merge conflicts by recursively merging object and array nodes.
+///
+public class RecursiveNodeMergeConflictResolver : IConflictResolver
+{
+ ///
+ /// Resolves a conflict between a local document and a remote operation.
+ ///
+ /// The local document, or if none exists.
+ /// The remote operation to apply.
+ /// The conflict resolution result indicating whether and what to apply.
+ public ConflictResolutionResult Resolve(Document? local, OplogEntry remote)
+ {
+ if (local == null)
+ {
var content = remote.Payload ?? default;
- var newDoc = new Document(remote.Collection, remote.Key, content, remote.Timestamp, remote.Operation == OperationType.Delete);
+ var newDoc = new Document(remote.Collection, remote.Key, content, remote.Timestamp,
+ remote.Operation == OperationType.Delete);
return ConflictResolutionResult.Apply(newDoc);
}
@@ -33,6 +32,7 @@ public class RecursiveNodeMergeConflictResolver : IConflictResolver
var newDoc = new Document(remote.Collection, remote.Key, default, remote.Timestamp, true);
return ConflictResolutionResult.Apply(newDoc);
}
+
return ConflictResolutionResult.Ignore();
}
@@ -41,12 +41,14 @@ public class RecursiveNodeMergeConflictResolver : IConflictResolver
var localTs = local.UpdatedAt;
var remoteTs = remote.Timestamp;
- if (localJson.ValueKind == JsonValueKind.Undefined) return ConflictResolutionResult.Apply(new Document(remote.Collection, remote.Key, remoteJson, remoteTs, false));
- if (remoteJson.ValueKind == JsonValueKind.Undefined) return ConflictResolutionResult.Ignore();
-
- // Optimization: Use ArrayBufferWriter (Net6.0) or MemoryStream (NS2.0)
- // Utf8JsonWriter works with both, but ArrayBufferWriter is more efficient for high throughput.
-
+ if (localJson.ValueKind == JsonValueKind.Undefined)
+ return ConflictResolutionResult.Apply(new Document(remote.Collection, remote.Key, remoteJson, remoteTs,
+ false));
+ if (remoteJson.ValueKind == JsonValueKind.Undefined) return ConflictResolutionResult.Ignore();
+
+ // Optimization: Use ArrayBufferWriter (Net6.0) or MemoryStream (NS2.0)
+ // Utf8JsonWriter works with both, but ArrayBufferWriter is more efficient for high throughput.
+
JsonElement mergedDocJson;
#if NET6_0_OR_GREATER
@@ -55,7 +57,8 @@ public class RecursiveNodeMergeConflictResolver : IConflictResolver
{
MergeJson(writer, localJson, localTs, remoteJson, remoteTs);
}
- mergedDocJson = JsonDocument.Parse(bufferWriter.WrittenMemory).RootElement;
+
+ mergedDocJson = JsonDocument.Parse(bufferWriter.WrittenMemory).RootElement;
#else
using (var ms = new MemoryStream())
{
@@ -67,13 +70,14 @@ public class RecursiveNodeMergeConflictResolver : IConflictResolver
mergedDocJson = JsonDocument.Parse(ms.ToArray()).RootElement;
}
#endif
-
+
var maxTimestamp = remoteTs.CompareTo(localTs) > 0 ? remoteTs : localTs;
var mergedDoc = new Document(remote.Collection, remote.Key, mergedDocJson, maxTimestamp, false);
return ConflictResolutionResult.Apply(mergedDoc);
}
- private void MergeJson(Utf8JsonWriter writer, JsonElement local, HlcTimestamp localTs, JsonElement remote, HlcTimestamp remoteTs)
+ private void MergeJson(Utf8JsonWriter writer, JsonElement local, HlcTimestamp localTs, JsonElement remote,
+ HlcTimestamp remoteTs)
{
if (local.ValueKind != remote.ValueKind)
{
@@ -93,7 +97,7 @@ public class RecursiveNodeMergeConflictResolver : IConflictResolver
break;
default:
// Primitives
- if (local.GetRawText() == remote.GetRawText())
+ if (local.GetRawText() == remote.GetRawText())
{
local.WriteTo(writer);
}
@@ -102,54 +106,51 @@ public class RecursiveNodeMergeConflictResolver : IConflictResolver
if (remoteTs.CompareTo(localTs) > 0) remote.WriteTo(writer);
else local.WriteTo(writer);
}
+
break;
}
}
- private void MergeObjects(Utf8JsonWriter writer, JsonElement local, HlcTimestamp localTs, JsonElement remote, HlcTimestamp remoteTs)
+ private void MergeObjects(Utf8JsonWriter writer, JsonElement local, HlcTimestamp localTs, JsonElement remote,
+ HlcTimestamp remoteTs)
{
- writer.WriteStartObject();
-
- // We need to iterate keys. To avoid double iteration efficiently, we can use a dictionary for the UNION of keys.
- // But populating a dictionary is effectively what we did before.
- // Can we do better?
- // Yes: Iterate Local, write merged/local. Track handled keys. Then iterate Remote, write remaining.
-
+ writer.WriteStartObject();
+
+ // We need to iterate keys. To avoid double iteration efficiently, we can use a dictionary for the UNION of keys.
+ // But populating a dictionary is effectively what we did before.
+ // Can we do better?
+ // Yes: Iterate Local, write merged/local. Track handled keys. Then iterate Remote, write remaining.
+
var processedKeys = new HashSet();
foreach (var prop in local.EnumerateObject())
{
- var key = prop.Name;
+ string key = prop.Name;
processedKeys.Add(key); // Mark as processed
writer.WritePropertyName(key);
if (remote.TryGetProperty(key, out var remoteVal))
- {
// Collision -> Merge
MergeJson(writer, prop.Value, localTs, remoteVal, remoteTs);
- }
else
- {
// Only local
prop.Value.WriteTo(writer);
- }
}
foreach (var prop in remote.EnumerateObject())
- {
if (!processedKeys.Contains(prop.Name))
{
// New from remote
writer.WritePropertyName(prop.Name);
prop.Value.WriteTo(writer);
}
- }
writer.WriteEndObject();
}
- private void MergeArrays(Utf8JsonWriter writer, JsonElement local, HlcTimestamp localTs, JsonElement remote, HlcTimestamp remoteTs)
+ private void MergeArrays(Utf8JsonWriter writer, JsonElement local, HlcTimestamp localTs, JsonElement remote,
+ HlcTimestamp remoteTs)
{
// Heuristic check
bool localIsObj = HasObjects(local);
@@ -164,10 +165,10 @@ public class RecursiveNodeMergeConflictResolver : IConflictResolver
}
if (localIsObj != remoteIsObj)
- {
- // Mixed mistmatch LWW
- if (remoteTs.CompareTo(localTs) > 0) remote.WriteTo(writer);
- else local.WriteTo(writer);
+ {
+ // Mixed mistmatch LWW
+ if (remoteTs.CompareTo(localTs) > 0) remote.WriteTo(writer);
+ else local.WriteTo(writer);
return;
}
@@ -184,44 +185,36 @@ public class RecursiveNodeMergeConflictResolver : IConflictResolver
return;
}
- writer.WriteStartArray();
-
- // We want to write Union of items by ID.
- // To preserve some semblance of order (or just determinism), we can iterate local IDs first, then remote new IDs.
- // Or just use the dictionary values.
-
- // NOTE: We cannot simply write to writer inside the map loop if we are creating a merged map.
- // Let's iterate the union of keys similar to Objects.
-
+ writer.WriteStartArray();
+
+ // We want to write Union of items by ID.
+ // To preserve some semblance of order (or just determinism), we can iterate local IDs first, then remote new IDs.
+ // Or just use the dictionary values.
+
+ // NOTE: We cannot simply write to writer inside the map loop if we are creating a merged map.
+ // Let's iterate the union of keys similar to Objects.
+
var processedIds = new HashSet();
// 1. Process Local Items (Merge or Write)
foreach (var kvp in localMap)
{
- var id = kvp.Key;
+ string id = kvp.Key;
var localItem = kvp.Value;
processedIds.Add(id);
if (remoteMap.TryGetValue(id, out var remoteItem))
- {
// Merge recursively
MergeJson(writer, localItem, localTs, remoteItem, remoteTs);
- }
else
- {
// Keep local item
localItem.WriteTo(writer);
- }
}
// 2. Process New Remote Items
foreach (var kvp in remoteMap)
- {
if (!processedIds.Contains(kvp.Key))
- {
kvp.Value.WriteTo(writer);
- }
- }
writer.WriteEndArray();
}
@@ -249,6 +242,7 @@ public class RecursiveNodeMergeConflictResolver : IConflictResolver
map[id] = item;
}
+
return map;
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/VectorClock.cs b/src/ZB.MOM.WW.CBDDC.Core/VectorClock.cs
index f9281c1..12a6ada 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/VectorClock.cs
+++ b/src/ZB.MOM.WW.CBDDC.Core/VectorClock.cs
@@ -5,86 +5,84 @@ using System.Linq;
namespace ZB.MOM.WW.CBDDC.Core;
///
-/// Represents a Vector Clock for tracking causality in a distributed system.
-/// Maps NodeId -> HlcTimestamp to track the latest known state of each node.
+/// Represents a Vector Clock for tracking causality in a distributed system.
+/// Maps NodeId -> HlcTimestamp to track the latest known state of each node.
///
-public class VectorClock
-{
- private readonly Dictionary _clock;
-
- ///
- /// Initializes a new empty vector clock.
- ///
- public VectorClock()
- {
- _clock = new Dictionary(StringComparer.Ordinal);
- }
-
- ///
- /// Initializes a new vector clock from an existing clock state.
- ///
- /// The clock state to copy.
- public VectorClock(Dictionary clock)
- {
- _clock = new Dictionary(clock, StringComparer.Ordinal);
- }
+public class VectorClock
+{
+ private readonly Dictionary _clock;
///
- /// Gets all node IDs in this vector clock.
+ /// Initializes a new empty vector clock.
+ ///
+ public VectorClock()
+ {
+ _clock = new Dictionary(StringComparer.Ordinal);
+ }
+
+ ///
+ /// Initializes a new vector clock from an existing clock state.
+ ///
+ /// The clock state to copy.
+ public VectorClock(Dictionary clock)
+ {
+ _clock = new Dictionary(clock, StringComparer.Ordinal);
+ }
+
+ ///
+ /// Gets all node IDs in this vector clock.
///
public IEnumerable NodeIds => _clock.Keys;
- ///
- /// Gets the timestamp for a specific node, or default if not present.
- ///
- /// The node identifier.
- public HlcTimestamp GetTimestamp(string nodeId)
- {
- return _clock.TryGetValue(nodeId, out var ts) ? ts : default;
- }
+ ///
+ /// Gets the timestamp for a specific node, or default if not present.
+ ///
+ /// The node identifier.
+ public HlcTimestamp GetTimestamp(string nodeId)
+ {
+ return _clock.TryGetValue(nodeId, out var ts) ? ts : default;
+ }
- ///
- /// Sets or updates the timestamp for a specific node.
- ///
- /// The node identifier.
- /// The timestamp to set.
- public void SetTimestamp(string nodeId, HlcTimestamp timestamp)
- {
- _clock[nodeId] = timestamp;
- }
+ ///
+ /// Sets or updates the timestamp for a specific node.
+ ///
+ /// The node identifier.
+ /// The timestamp to set.
+ public void SetTimestamp(string nodeId, HlcTimestamp timestamp)
+ {
+ _clock[nodeId] = timestamp;
+ }
- ///
- /// Merges another vector clock into this one, taking the maximum timestamp for each node.
- ///
- /// The vector clock to merge from.
- public void Merge(VectorClock other)
- {
- foreach (var nodeId in other.NodeIds)
- {
+ ///
+ /// Merges another vector clock into this one, taking the maximum timestamp for each node.
+ ///
+ /// The vector clock to merge from.
+ public void Merge(VectorClock other)
+ {
+ foreach (string nodeId in other.NodeIds)
+ {
var otherTs = other.GetTimestamp(nodeId);
if (!_clock.TryGetValue(nodeId, out var currentTs) || otherTs.CompareTo(currentTs) > 0)
- {
_clock[nodeId] = otherTs;
- }
}
}
///
- /// Compares this vector clock with another to determine causality.
- /// Returns:
- /// - Positive: This is strictly ahead (dominates other)
- /// - Negative: Other is strictly ahead (other dominates this)
- /// - Zero: Concurrent (neither dominates)
- ///
- /// The vector clock to compare with.
- public CausalityRelation CompareTo(VectorClock other)
- {
- bool thisAhead = false;
- bool otherAhead = false;
+ /// Compares this vector clock with another to determine causality.
+ /// Returns:
+ /// - Positive: This is strictly ahead (dominates other)
+ /// - Negative: Other is strictly ahead (other dominates this)
+ /// - Zero: Concurrent (neither dominates)
+ ///
+ /// The vector clock to compare with.
+ public CausalityRelation CompareTo(VectorClock other)
+ {
+ var thisAhead = false;
+ var otherAhead = false;
var allNodes = new HashSet(_clock.Keys.Union(other._clock.Keys), StringComparer.Ordinal);
- foreach (var nodeId in allNodes)
+ foreach (string nodeId in allNodes)
{
var thisTs = GetTimestamp(nodeId);
var otherTs = other.GetTimestamp(nodeId);
@@ -92,19 +90,11 @@ public class VectorClock
int cmp = thisTs.CompareTo(otherTs);
if (cmp > 0)
- {
thisAhead = true;
- }
- else if (cmp < 0)
- {
- otherAhead = true;
- }
+ else if (cmp < 0) otherAhead = true;
// Early exit if concurrent
- if (thisAhead && otherAhead)
- {
- return CausalityRelation.Concurrent;
- }
+ if (thisAhead && otherAhead) return CausalityRelation.Concurrent;
}
if (thisAhead && !otherAhead)
@@ -115,65 +105,56 @@ public class VectorClock
return CausalityRelation.Equal;
}
- ///
- /// Determines which nodes have updates that this vector clock doesn't have.
- /// Returns node IDs where the other vector clock is ahead.
- ///
- /// The vector clock to compare against.
- public IEnumerable GetNodesWithUpdates(VectorClock other)
- {
- var allNodes = new HashSet(_clock.Keys, StringComparer.Ordinal);
- foreach (var nodeId in other._clock.Keys)
- {
- allNodes.Add(nodeId);
- }
+ ///
+ /// Determines which nodes have updates that this vector clock doesn't have.
+ /// Returns node IDs where the other vector clock is ahead.
+ ///
+ /// The vector clock to compare against.
+ public IEnumerable GetNodesWithUpdates(VectorClock other)
+ {
+ var allNodes = new HashSet(_clock.Keys, StringComparer.Ordinal);
+ foreach (string nodeId in other._clock.Keys) allNodes.Add(nodeId);
- foreach (var nodeId in allNodes)
+ foreach (string nodeId in allNodes)
{
var thisTs = GetTimestamp(nodeId);
var otherTs = other.GetTimestamp(nodeId);
- if (otherTs.CompareTo(thisTs) > 0)
- {
- yield return nodeId;
- }
- }
- }
-
- ///
- /// Determines which nodes have updates that the other vector clock doesn't have.
- /// Returns node IDs where this vector clock is ahead.
- ///
- /// The vector clock to compare against.
- public IEnumerable GetNodesToPush(VectorClock other)
- {
- var allNodes = new HashSet(_clock.Keys.Union(other._clock.Keys), StringComparer.Ordinal);
-
- foreach (var nodeId in allNodes)
- {
- var thisTs = GetTimestamp(nodeId);
- var otherTs = other.GetTimestamp(nodeId);
-
- if (thisTs.CompareTo(otherTs) > 0)
- {
- yield return nodeId;
- }
+ if (otherTs.CompareTo(thisTs) > 0) yield return nodeId;
}
}
///
- /// Creates a copy of this vector clock.
+ /// Determines which nodes have updates that the other vector clock doesn't have.
+ /// Returns node IDs where this vector clock is ahead.
+ ///
+ /// The vector clock to compare against.
+ public IEnumerable GetNodesToPush(VectorClock other)
+ {
+ var allNodes = new HashSet(_clock.Keys.Union(other._clock.Keys), StringComparer.Ordinal);
+
+ foreach (string nodeId in allNodes)
+ {
+ var thisTs = GetTimestamp(nodeId);
+ var otherTs = other.GetTimestamp(nodeId);
+
+ if (thisTs.CompareTo(otherTs) > 0) yield return nodeId;
+ }
+ }
+
+ ///
+ /// Creates a copy of this vector clock.
///
public VectorClock Clone()
{
return new VectorClock(new Dictionary(_clock, StringComparer.Ordinal));
}
- ///
- public override string ToString()
- {
- if (_clock.Count == 0)
- return "{}";
+ ///
+ public override string ToString()
+ {
+ if (_clock.Count == 0)
+ return "{}";
var entries = _clock.Select(kvp => $"{kvp.Key}:{kvp.Value}");
return "{" + string.Join(", ", entries) + "}";
@@ -181,16 +162,19 @@ public class VectorClock
}
///
-/// Represents the causality relationship between two vector clocks.
+/// Represents the causality relationship between two vector clocks.
///
public enum CausalityRelation
{
/// Both vector clocks are equal.
Equal,
+
/// This vector clock is strictly ahead (dominates).
StrictlyAhead,
+
/// This vector clock is strictly behind (dominated).
StrictlyBehind,
+
/// Vector clocks are concurrent (neither dominates).
Concurrent
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Core/ZB.MOM.WW.CBDDC.Core.csproj b/src/ZB.MOM.WW.CBDDC.Core/ZB.MOM.WW.CBDDC.Core.csproj
index 668fb7b..bf088e0 100755
--- a/src/ZB.MOM.WW.CBDDC.Core/ZB.MOM.WW.CBDDC.Core.csproj
+++ b/src/ZB.MOM.WW.CBDDC.Core/ZB.MOM.WW.CBDDC.Core.csproj
@@ -1,33 +1,33 @@
-
- ZB.MOM.WW.CBDDC.Core
- ZB.MOM.WW.CBDDC.Core
- ZB.MOM.WW.CBDDC.Core
- net10.0
- latest
- enable
- 1.0.3
- MrDevRobot
- Core abstractions and logic for CBDDC, a lightweight P2P mesh database.
- MIT
- p2p;mesh;database;gossip;cbddc;lan;offline-first;distributed
- https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net
- https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net
- git
- README.md
-
+
+ ZB.MOM.WW.CBDDC.Core
+ ZB.MOM.WW.CBDDC.Core
+ ZB.MOM.WW.CBDDC.Core
+ net10.0
+ latest
+ enable
+ 1.0.3
+ MrDevRobot
+ Core abstractions and logic for CBDDC, a lightweight P2P mesh database.
+ MIT
+ p2p;mesh;database;gossip;cbddc;lan;offline-first;distributed
+ https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net
+ https://github.com/CBDDC/ZB.MOM.WW.CBDDC.Net
+ git
+ README.md
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ZB.MOM.WW.CBDDC.Hosting/CBDDCHostingExtensions.cs b/src/ZB.MOM.WW.CBDDC.Hosting/CBDDCHostingExtensions.cs
index 346b791..d4baceb 100755
--- a/src/ZB.MOM.WW.CBDDC.Hosting/CBDDCHostingExtensions.cs
+++ b/src/ZB.MOM.WW.CBDDC.Hosting/CBDDCHostingExtensions.cs
@@ -1,22 +1,22 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Hosting;
using ZB.MOM.WW.CBDDC.Hosting.Configuration;
using ZB.MOM.WW.CBDDC.Hosting.HealthChecks;
using ZB.MOM.WW.CBDDC.Hosting.HostedServices;
using ZB.MOM.WW.CBDDC.Hosting.Services;
using ZB.MOM.WW.CBDDC.Network;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.Diagnostics.HealthChecks;
-using Microsoft.Extensions.Hosting;
namespace ZB.MOM.WW.CBDDC.Hosting;
///
-/// Extension methods for configuring CBDDC in ASP.NET Core applications.
+/// Extension methods for configuring CBDDC in ASP.NET Core applications.
///
public static class CBDDCHostingExtensions
{
///
- /// Adds CBDDC ASP.NET integration with the specified configuration.
+ /// Adds CBDDC ASP.NET integration with the specified configuration.
///
/// The service collection.
/// Action to configure CBDDC options.
@@ -43,7 +43,7 @@ public static class CBDDCHostingExtensions
}
///
- /// Adds CBDDC ASP.NET integration for single-cluster mode.
+ /// Adds CBDDC ASP.NET integration for single-cluster mode.
///
/// The service collection.
/// Action to configure single-cluster options.
@@ -51,10 +51,7 @@ public static class CBDDCHostingExtensions
this IServiceCollection services,
Action? configure = null)
{
- return services.AddCBDDCHosting(options =>
- {
- configure?.Invoke(options.Cluster);
- });
+ return services.AddCBDDCHosting(options => { configure?.Invoke(options.Cluster); });
}
private static void RegisterSingleClusterServices(
@@ -81,12 +78,10 @@ public static class CBDDCHostingExtensions
{
// Health checks
if (options.EnableHealthChecks)
- {
services.AddHealthChecks()
.AddCheck(
"cbddc",
- failureStatus: HealthStatus.Unhealthy,
- tags: new[] { "db", "ready" });
- }
+ HealthStatus.Unhealthy,
+ new[] { "db", "ready" });
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Hosting/Configuration/CBDDCHostingOptions.cs b/src/ZB.MOM.WW.CBDDC.Hosting/Configuration/CBDDCHostingOptions.cs
index f3d35ae..23770ef 100755
--- a/src/ZB.MOM.WW.CBDDC.Hosting/Configuration/CBDDCHostingOptions.cs
+++ b/src/ZB.MOM.WW.CBDDC.Hosting/Configuration/CBDDCHostingOptions.cs
@@ -1,18 +1,18 @@
namespace ZB.MOM.WW.CBDDC.Hosting.Configuration;
///
-/// Configuration options for CBDDC ASP.NET integration.
+/// Configuration options for CBDDC ASP.NET integration.
///
public class CBDDCHostingOptions
{
///
- /// Gets or sets the cluster configuration.
+ /// Gets or sets the cluster configuration.
///
public ClusterOptions Cluster { get; set; } = new();
///
- /// Gets or sets whether to enable health checks.
- /// Default: true
+ /// Gets or sets whether to enable health checks.
+ /// Default: true
///
public bool EnableHealthChecks { get; set; } = true;
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Hosting/Configuration/ClusterOptions.cs b/src/ZB.MOM.WW.CBDDC.Hosting/Configuration/ClusterOptions.cs
index b7e59ec..2f3ca0d 100755
--- a/src/ZB.MOM.WW.CBDDC.Hosting/Configuration/ClusterOptions.cs
+++ b/src/ZB.MOM.WW.CBDDC.Hosting/Configuration/ClusterOptions.cs
@@ -1,40 +1,39 @@
-using System;
-
namespace ZB.MOM.WW.CBDDC.Hosting.Configuration;
///
-/// Configuration options for cluster mode.
+/// Configuration options for cluster mode.
///
public class ClusterOptions
{
///
- /// Gets or sets the node identifier for this instance.
+ /// Gets or sets the node identifier for this instance.
///
public string NodeId { get; set; } = Environment.MachineName;
///
- /// Gets or sets the TCP port for sync operations.
- /// Default: 5001
+ /// Gets or sets the TCP port for sync operations.
+ /// Default: 5001
///
public int TcpPort { get; set; } = 5001;
///
- /// Gets or sets whether to enable UDP discovery.
- /// Default: false (disabled in server mode)
+ /// Gets or sets whether to enable UDP discovery.
+ /// Default: false (disabled in server mode)
///
public bool EnableUdpDiscovery { get; set; } = false;
///
- /// Gets or sets the lag threshold (in milliseconds) used to determine when a tracked peer is considered lagging.
- /// Peers above this threshold degrade health status.
- /// Default: 30,000 ms.
+ /// Gets or sets the lag threshold (in milliseconds) used to determine when a tracked peer is considered lagging.
+ /// Peers above this threshold degrade health status.
+ /// Default: 30,000 ms.
///
public long PeerConfirmationLagThresholdMs { get; set; } = 30_000;
///
- /// Gets or sets the critical lag threshold (in milliseconds) used to determine when a tracked peer causes unhealthy status.
- /// Peers above this threshold mark health as unhealthy.
- /// Default: 120,000 ms.
+ /// Gets or sets the critical lag threshold (in milliseconds) used to determine when a tracked peer causes unhealthy
+ /// status.
+ /// Peers above this threshold mark health as unhealthy.
+ /// Default: 120,000 ms.
///
public long PeerConfirmationCriticalLagThresholdMs { get; set; } = 120_000;
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Hosting/HealthChecks/CBDDCHealthCheck.cs b/src/ZB.MOM.WW.CBDDC.Hosting/HealthChecks/CBDDCHealthCheck.cs
index da916ed..1f3d3bc 100755
--- a/src/ZB.MOM.WW.CBDDC.Hosting/HealthChecks/CBDDCHealthCheck.cs
+++ b/src/ZB.MOM.WW.CBDDC.Hosting/HealthChecks/CBDDCHealthCheck.cs
@@ -1,8 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using ZB.MOM.WW.CBDDC.Core.Storage;
using ZB.MOM.WW.CBDDC.Hosting.Configuration;
@@ -10,17 +5,17 @@ using ZB.MOM.WW.CBDDC.Hosting.Configuration;
namespace ZB.MOM.WW.CBDDC.Hosting.HealthChecks;
///
-/// Health check for CBDDC persistence layer.
-/// Verifies that the database connection is healthy.
+/// Health check for CBDDC persistence layer.
+/// Verifies that the database connection is healthy.
///
public class CBDDCHealthCheck : IHealthCheck
{
private readonly IOplogStore _oplogStore;
- private readonly IPeerOplogConfirmationStore _peerOplogConfirmationStore;
private readonly CBDDCHostingOptions _options;
+ private readonly IPeerOplogConfirmationStore _peerOplogConfirmationStore;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The oplog store used to verify persistence health.
/// The peer confirmation store used for confirmation lag health checks.
@@ -31,16 +26,17 @@ public class CBDDCHealthCheck : IHealthCheck
CBDDCHostingOptions options)
{
_oplogStore = oplogStore ?? throw new ArgumentNullException(nameof(oplogStore));
- _peerOplogConfirmationStore = peerOplogConfirmationStore ?? throw new ArgumentNullException(nameof(peerOplogConfirmationStore));
+ _peerOplogConfirmationStore = peerOplogConfirmationStore ??
+ throw new ArgumentNullException(nameof(peerOplogConfirmationStore));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
///
- /// Performs a health check against the CBDDC persistence layer.
+ /// Performs a health check against the CBDDC persistence layer.
///
/// The health check execution context.
/// A token used to cancel the health check.
- /// A describing the health status.
+ /// A describing the health status.
public async Task CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
@@ -58,15 +54,18 @@ public class CBDDCHealthCheck : IHealthCheck
var peersWithNoConfirmation = new List();
var laggingPeers = new List();
var criticalLaggingPeers = new List();
- var lastSuccessfulConfirmationUpdateByPeer = new Dictionary(StringComparer.Ordinal);
+ var lastSuccessfulConfirmationUpdateByPeer =
+ new Dictionary(StringComparer.Ordinal);
var maxLagMs = 0L;
- var lagThresholdMs = Math.Max(0, _options.Cluster.PeerConfirmationLagThresholdMs);
- var criticalLagThresholdMs = Math.Max(lagThresholdMs, _options.Cluster.PeerConfirmationCriticalLagThresholdMs);
+ long lagThresholdMs = Math.Max(0, _options.Cluster.PeerConfirmationLagThresholdMs);
+ long criticalLagThresholdMs =
+ Math.Max(lagThresholdMs, _options.Cluster.PeerConfirmationCriticalLagThresholdMs);
- foreach (var peerNodeId in trackedPeers)
+ foreach (string peerNodeId in trackedPeers)
{
- var confirmations = (await _peerOplogConfirmationStore.GetConfirmationsForPeerAsync(peerNodeId, cancellationToken))
+ var confirmations =
+ (await _peerOplogConfirmationStore.GetConfirmationsForPeerAsync(peerNodeId, cancellationToken))
.Where(confirmation => confirmation.IsActive)
.ToList();
@@ -83,19 +82,14 @@ public class CBDDCHealthCheck : IHealthCheck
.ThenBy(confirmation => confirmation.ConfirmedLogic)
.First();
- var lagMs = Math.Max(0, localHead.PhysicalTime - oldestConfirmation.ConfirmedWall);
+ long lagMs = Math.Max(0, localHead.PhysicalTime - oldestConfirmation.ConfirmedWall);
maxLagMs = Math.Max(maxLagMs, lagMs);
- lastSuccessfulConfirmationUpdateByPeer[peerNodeId] = confirmations.Max(confirmation => confirmation.LastConfirmedUtc);
+ lastSuccessfulConfirmationUpdateByPeer[peerNodeId] =
+ confirmations.Max(confirmation => confirmation.LastConfirmedUtc);
- if (lagMs > lagThresholdMs)
- {
- laggingPeers.Add(peerNodeId);
- }
+ if (lagMs > lagThresholdMs) laggingPeers.Add(peerNodeId);
- if (lagMs > criticalLagThresholdMs)
- {
- criticalLaggingPeers.Add(peerNodeId);
- }
+ if (lagMs > criticalLagThresholdMs) criticalLaggingPeers.Add(peerNodeId);
}
var payload = new Dictionary
@@ -108,18 +102,14 @@ public class CBDDCHealthCheck : IHealthCheck
};
if (criticalLaggingPeers.Count > 0)
- {
return HealthCheckResult.Unhealthy(
$"CBDDC is unhealthy. Critical lag detected for {criticalLaggingPeers.Count} tracked peer(s).",
data: payload);
- }
if (peersWithNoConfirmation.Count > 0 || laggingPeers.Count > 0)
- {
return HealthCheckResult.Degraded(
$"CBDDC is degraded. Lagging peers: {laggingPeers.Count}, unconfirmed peers: {peersWithNoConfirmation.Count}.",
data: payload);
- }
return HealthCheckResult.Healthy(
$"CBDDC is healthy. Latest timestamp: {localHead.PhysicalTime}.",
@@ -129,7 +119,7 @@ public class CBDDCHealthCheck : IHealthCheck
{
return HealthCheckResult.Unhealthy(
"CBDDC persistence layer is unavailable",
- exception: ex);
+ ex);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Hosting/HostedServices/DiscoveryServiceHostedService.cs b/src/ZB.MOM.WW.CBDDC.Hosting/HostedServices/DiscoveryServiceHostedService.cs
index 2abe7f0..18180f6 100755
--- a/src/ZB.MOM.WW.CBDDC.Hosting/HostedServices/DiscoveryServiceHostedService.cs
+++ b/src/ZB.MOM.WW.CBDDC.Hosting/HostedServices/DiscoveryServiceHostedService.cs
@@ -1,5 +1,3 @@
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog.Context;
@@ -8,7 +6,7 @@ using ZB.MOM.WW.CBDDC.Network;
namespace ZB.MOM.WW.CBDDC.Hosting.HostedServices;
///
-/// Hosted service that manages the lifecycle of the discovery service.
+/// Hosted service that manages the lifecycle of the discovery service.
///
public class DiscoveryServiceHostedService : IHostedService
{
@@ -16,7 +14,7 @@ public class DiscoveryServiceHostedService : IHostedService
private readonly ILogger _logger;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The discovery service to manage.
/// The logger used for service lifecycle events.
@@ -29,7 +27,7 @@ public class DiscoveryServiceHostedService : IHostedService
}
///
- /// Starts the discovery service.
+ /// Starts the discovery service.
///
/// A token used to cancel the startup operation.
/// A task that represents the asynchronous start operation.
@@ -45,7 +43,7 @@ public class DiscoveryServiceHostedService : IHostedService
}
///
- /// Stops the discovery service.
+ /// Stops the discovery service.
///
/// A token used to cancel the shutdown operation.
/// A task that represents the asynchronous stop operation.
@@ -59,4 +57,4 @@ public class DiscoveryServiceHostedService : IHostedService
await _discoveryService.Stop();
_logger.LogInformation("Discovery Service stopped");
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Hosting/HostedServices/TcpSyncServerHostedService.cs b/src/ZB.MOM.WW.CBDDC.Hosting/HostedServices/TcpSyncServerHostedService.cs
index bf2b691..cc188eb 100755
--- a/src/ZB.MOM.WW.CBDDC.Hosting/HostedServices/TcpSyncServerHostedService.cs
+++ b/src/ZB.MOM.WW.CBDDC.Hosting/HostedServices/TcpSyncServerHostedService.cs
@@ -1,5 +1,3 @@
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog.Context;
@@ -8,15 +6,15 @@ using ZB.MOM.WW.CBDDC.Network;
namespace ZB.MOM.WW.CBDDC.Hosting.HostedServices;
///
-/// Hosted service that manages the lifecycle of the TCP sync server.
+/// Hosted service that manages the lifecycle of the TCP sync server.
///
public class TcpSyncServerHostedService : IHostedService
{
- private readonly ISyncServer _syncServer;
private readonly ILogger _logger;
+ private readonly ISyncServer _syncServer;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The sync server to start and stop.
/// The logger instance.
@@ -29,7 +27,7 @@ public class TcpSyncServerHostedService : IHostedService
}
///
- /// Starts the TCP sync server.
+ /// Starts the TCP sync server.
///
/// A token used to cancel startup.
public async Task StartAsync(CancellationToken cancellationToken)
@@ -44,7 +42,7 @@ public class TcpSyncServerHostedService : IHostedService
}
///
- /// Stops the TCP sync server.
+ /// Stops the TCP sync server.
///
/// A token used to cancel shutdown.
public async Task StopAsync(CancellationToken cancellationToken)
@@ -57,4 +55,4 @@ public class TcpSyncServerHostedService : IHostedService
await _syncServer.Stop();
_logger.LogInformation("TCP Sync Server stopped");
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Hosting/README.md b/src/ZB.MOM.WW.CBDDC.Hosting/README.md
index 672fc71..330af2d 100755
--- a/src/ZB.MOM.WW.CBDDC.Hosting/README.md
+++ b/src/ZB.MOM.WW.CBDDC.Hosting/README.md
@@ -41,6 +41,7 @@ app.Run();
## Health Checks
CBDDC registers health checks that verify:
+
- Database connectivity
- Latest timestamp retrieval
@@ -53,6 +54,7 @@ curl http://localhost:5000/health
### Cluster
Best for:
+
- Dedicated database servers
- Simple deployments
- Development/testing environments
@@ -60,6 +62,7 @@ Best for:
## Server Behavior
CBDDC servers operate in respond-only mode:
+
- Accept incoming sync connections
- Respond to sync requests
- Do not initiate outbound sync
@@ -69,11 +72,11 @@ CBDDC servers operate in respond-only mode:
### ClusterOptions
-| Property | Type | Default | Description |
-|----------|------|---------|-------------|
-| NodeId | string | MachineName | Unique node identifier |
-| TcpPort | int | 5001 | TCP port for sync |
-| EnableUdpDiscovery | bool | false | Enable UDP discovery |
+| Property | Type | Default | Description |
+|--------------------|--------|-------------|------------------------|
+| NodeId | string | MachineName | Unique node identifier |
+| TcpPort | int | 5001 | TCP port for sync |
+| EnableUdpDiscovery | bool | false | Enable UDP discovery |
## Production Checklist
diff --git a/src/ZB.MOM.WW.CBDDC.Hosting/Services/NoOpDiscoveryService.cs b/src/ZB.MOM.WW.CBDDC.Hosting/Services/NoOpDiscoveryService.cs
index 127ea79..ffc5b2a 100755
--- a/src/ZB.MOM.WW.CBDDC.Hosting/Services/NoOpDiscoveryService.cs
+++ b/src/ZB.MOM.WW.CBDDC.Hosting/Services/NoOpDiscoveryService.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Serilog.Context;
@@ -9,24 +7,24 @@ using ZB.MOM.WW.CBDDC.Network;
namespace ZB.MOM.WW.CBDDC.Hosting.Services;
///
-/// No-op implementation of IDiscoveryService for server scenarios.
-/// Does not perform UDP broadcast discovery - relies on explicit peer configuration.
+/// No-op implementation of IDiscoveryService for server scenarios.
+/// Does not perform UDP broadcast discovery - relies on explicit peer configuration.
///
public class NoOpDiscoveryService : IDiscoveryService
{
private readonly ILogger _logger;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// The logger instance to use, or to use a no-op logger.
+ /// The logger instance to use, or to use a no-op logger.
public NoOpDiscoveryService(ILogger? logger = null)
{
_logger = logger ?? NullLogger.Instance;
}
///
- /// Gets the currently active peers.
+ /// Gets the currently active peers.
///
/// An empty sequence because discovery is disabled.
public IEnumerable GetActivePeers()
@@ -35,7 +33,7 @@ public class NoOpDiscoveryService : IDiscoveryService
}
///
- /// Starts the discovery service.
+ /// Starts the discovery service.
///
/// A completed task.
public Task Start()
@@ -49,7 +47,7 @@ public class NoOpDiscoveryService : IDiscoveryService
}
///
- /// Stops the discovery service.
+ /// Stops the discovery service.
///
/// A completed task.
public Task Stop()
@@ -63,10 +61,10 @@ public class NoOpDiscoveryService : IDiscoveryService
}
///
- /// Releases resources used by this instance.
+ /// Releases resources used by this instance.
///
public void Dispose()
{
_logger.LogDebug("NoOpDiscoveryService disposed");
}
-}
+}
\ No newline at end of file
diff --git a/src/ZB.MOM.WW.CBDDC.Hosting/Services/NoOpSyncOrchestrator.cs b/src/ZB.MOM.WW.CBDDC.Hosting/Services/NoOpSyncOrchestrator.cs
index 8ef5bf2..6d4383c 100755
--- a/src/ZB.MOM.WW.CBDDC.Hosting/Services/NoOpSyncOrchestrator.cs
+++ b/src/ZB.MOM.WW.CBDDC.Hosting/Services/NoOpSyncOrchestrator.cs
@@ -1,4 +1,3 @@
-using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Serilog.Context;
@@ -7,24 +6,24 @@ using ZB.MOM.WW.CBDDC.Network;
namespace ZB.MOM.WW.CBDDC.Hosting.Services;
///
-/// No-op implementation of ISyncOrchestrator for server scenarios.
-/// Does not initiate outbound sync - only responds to incoming sync requests.
+/// No-op implementation of ISyncOrchestrator for server scenarios.
+/// Does not initiate outbound sync - only responds to incoming sync requests.
///
public class NoOpSyncOrchestrator : ISyncOrchestrator
{
private readonly ILogger _logger;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// The logger instance to use, or for a no-op logger.
+ /// The logger instance to use, or for a no-op logger.
public NoOpSyncOrchestrator(ILogger? logger = null)
{
_logger = logger ?? NullLogger.Instance;
}
///