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.Surreal;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
///
/// Surreal-backed document store for the sample app.
///
public class SampleDocumentStore : SurrealDocumentStore
{
private const string UsersCollection = "Users";
private const string TodoListsCollection = "TodoLists";
private const string LogsCollection = "Logs";
private const string TimeseriesCollection = "Timeseries";
///
/// Initializes a new instance of the class.
///
/// Sample database context.
/// Peer configuration provider.
/// Vector clock service.
/// Optional logger.
public SampleDocumentStore(
SampleDbContext context,
IPeerNodeConfigurationProvider configProvider,
IVectorClockService vectorClockService,
ILogger? logger = null)
: base(
context,
context.SurrealEmbeddedClient,
context.SchemaInitializer,
configProvider,
vectorClockService,
new LastWriteWinsConflictResolver(),
null,
null,
logger)
{
WatchCollection(UsersCollection, context.Users, u => u.Id);
WatchCollection(TodoListsCollection, context.TodoLists, t => t.Id);
WatchCollection(LogsCollection, context.Logs, entry => entry.Id);
WatchCollection(TimeseriesCollection, context.Timeseries, point => point.Id);
}
///
protected override async Task ApplyContentToEntityAsync(
string collection,
string key,
JsonElement content,
CancellationToken cancellationToken)
{
await UpsertEntityAsync(collection, key, content, cancellationToken);
}
///
protected override async Task ApplyContentToEntitiesBatchAsync(
IEnumerable<(string Collection, string Key, JsonElement Content)> documents,
CancellationToken cancellationToken)
{
foreach ((string collection, string key, var content) in documents)
await UpsertEntityAsync(collection, key, content, cancellationToken);
}
///
protected override async Task GetEntityAsJsonAsync(
string collection,
string key,
CancellationToken cancellationToken)
{
return collection switch
{
UsersCollection => SerializeEntity(await _context.Users.FindByIdAsync(key, cancellationToken)),
TodoListsCollection => SerializeEntity(await _context.TodoLists.FindByIdAsync(key, cancellationToken)),
LogsCollection => SerializeEntity(await _context.Logs.FindByIdAsync(key, cancellationToken)),
TimeseriesCollection => SerializeEntity(await _context.Timeseries.FindByIdAsync(key, cancellationToken)),
_ => null
};
}
///
protected override async Task RemoveEntityAsync(
string collection,
string key,
CancellationToken cancellationToken)
{
await DeleteEntityAsync(collection, key, cancellationToken);
}
///
protected override async Task RemoveEntitiesBatchAsync(
IEnumerable<(string Collection, string Key)> documents,
CancellationToken cancellationToken)
{
foreach ((string collection, string key) in documents)
await DeleteEntityAsync(collection, key, cancellationToken);
}
///
protected override async Task> GetAllEntitiesAsJsonAsync(
string collection,
CancellationToken cancellationToken)
{
return collection switch
{
UsersCollection => (await _context.Users.FindAllAsync(cancellationToken))
.Select(u => (u.Id, SerializeEntity(u)!.Value))
.ToList(),
TodoListsCollection => (await _context.TodoLists.FindAllAsync(cancellationToken))
.Select(t => (t.Id, SerializeEntity(t)!.Value))
.ToList(),
LogsCollection => (await _context.Logs.FindAllAsync(cancellationToken))
.Select(entry => (entry.Id, SerializeEntity(entry)!.Value))
.ToList(),
TimeseriesCollection => (await _context.Timeseries.FindAllAsync(cancellationToken))
.Select(point => (point.Id, SerializeEntity(point)!.Value))
.ToList(),
_ => []
};
}
private async Task UpsertEntityAsync(
string collection,
string key,
JsonElement content,
CancellationToken cancellationToken)
{
switch (collection)
{
case UsersCollection:
var user = content.Deserialize() ?? throw new InvalidOperationException("Failed to deserialize user.");
user.Id = key;
if (await _context.Users.FindByIdAsync(key, cancellationToken) == null)
await _context.Users.InsertAsync(user, cancellationToken);
else
await _context.Users.UpdateAsync(user, cancellationToken);
break;
case TodoListsCollection:
var todo = content.Deserialize() ??
throw new InvalidOperationException("Failed to deserialize todo list.");
todo.Id = key;
if (await _context.TodoLists.FindByIdAsync(key, cancellationToken) == null)
await _context.TodoLists.InsertAsync(todo, cancellationToken);
else
await _context.TodoLists.UpdateAsync(todo, cancellationToken);
break;
case LogsCollection:
var logEntry = content.Deserialize() ??
throw new InvalidOperationException("Failed to deserialize telemetry log.");
logEntry.Id = key;
if (await _context.Logs.FindByIdAsync(key, cancellationToken) == null)
await _context.Logs.InsertAsync(logEntry, cancellationToken);
else
await _context.Logs.UpdateAsync(logEntry, cancellationToken);
break;
case TimeseriesCollection:
var point = content.Deserialize() ??
throw new InvalidOperationException("Failed to deserialize timeseries point.");
point.Id = key;
if (await _context.Timeseries.FindByIdAsync(key, cancellationToken) == null)
await _context.Timeseries.InsertAsync(point, cancellationToken);
else
await _context.Timeseries.UpdateAsync(point, cancellationToken);
break;
default:
throw new NotSupportedException($"Collection '{collection}' is not supported for sync.");
}
}
private async Task DeleteEntityAsync(string collection, string key, CancellationToken cancellationToken)
{
switch (collection)
{
case UsersCollection:
await _context.Users.DeleteAsync(key, cancellationToken);
break;
case TodoListsCollection:
await _context.TodoLists.DeleteAsync(key, cancellationToken);
break;
case LogsCollection:
await _context.Logs.DeleteAsync(key, cancellationToken);
break;
case TimeseriesCollection:
await _context.Timeseries.DeleteAsync(key, cancellationToken);
break;
default:
_logger.LogWarning("Attempted to remove entity from unsupported collection: {Collection}", collection);
break;
}
}
private static JsonElement? SerializeEntity(T? entity) where T : class
{
return entity == null ? null : JsonSerializer.SerializeToElement(entity);
}
}