124 lines
5.2 KiB
C#
124 lines
5.2 KiB
C#
using System.Collections.Concurrent;
|
|
using Operations.Service.Application.State;
|
|
|
|
namespace Operations.Service.Application.Ports;
|
|
|
|
public sealed class InMemoryRestaurantLifecycleStorePort : IRestaurantLifecycleStorePort
|
|
{
|
|
private readonly ConcurrentDictionary<string, PersistedRestaurantLifecycleRecord> orders = new();
|
|
private readonly ConcurrentDictionary<string, ConcurrentQueue<PersistedRestaurantLifecycleEvent>> events = new();
|
|
|
|
public InMemoryRestaurantLifecycleStorePort()
|
|
{
|
|
foreach (var record in BuildSeedOrders())
|
|
{
|
|
orders[BuildKey(record.ContextId, record.OrderId)] = record;
|
|
}
|
|
|
|
foreach (var record in BuildSeedEvents())
|
|
{
|
|
var queue = events.GetOrAdd(BuildKey(record.ContextId, record.OrderId), static _ => new ConcurrentQueue<PersistedRestaurantLifecycleEvent>());
|
|
queue.Enqueue(record);
|
|
}
|
|
}
|
|
|
|
public Task<PersistedRestaurantLifecycleRecord?> GetOrderAsync(string contextId, string orderId, CancellationToken cancellationToken)
|
|
{
|
|
orders.TryGetValue(BuildKey(contextId, orderId), out var record);
|
|
return Task.FromResult(record);
|
|
}
|
|
|
|
public Task<IReadOnlyCollection<PersistedRestaurantLifecycleRecord>> ListOrdersAsync(string contextId, CancellationToken cancellationToken)
|
|
{
|
|
var records = orders.Values
|
|
.Where(record => string.Equals(record.ContextId, contextId, StringComparison.Ordinal))
|
|
.OrderByDescending(record => record.UpdatedAtUtc)
|
|
.ToArray();
|
|
|
|
return Task.FromResult<IReadOnlyCollection<PersistedRestaurantLifecycleRecord>>(records);
|
|
}
|
|
|
|
public Task<IReadOnlyCollection<PersistedRestaurantLifecycleRecord>> ListPayableOrdersAsync(string contextId, CancellationToken cancellationToken)
|
|
{
|
|
var records = orders.Values
|
|
.Where(record => string.Equals(record.ContextId, contextId, StringComparison.Ordinal))
|
|
.Where(record => string.Equals(record.OrderState, "Served", StringComparison.Ordinal))
|
|
.Where(record => string.Equals(record.CheckState, "Open", StringComparison.Ordinal) || string.Equals(record.CheckState, "AwaitingPayment", StringComparison.Ordinal))
|
|
.Where(record => record.OutstandingBalance > 0m)
|
|
.OrderByDescending(record => record.UpdatedAtUtc)
|
|
.ToArray();
|
|
|
|
return Task.FromResult<IReadOnlyCollection<PersistedRestaurantLifecycleRecord>>(records);
|
|
}
|
|
|
|
public Task UpsertOrderAsync(PersistedRestaurantLifecycleRecord record, CancellationToken cancellationToken)
|
|
{
|
|
orders[BuildKey(record.ContextId, record.OrderId)] = record;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task<IReadOnlyCollection<PersistedRestaurantLifecycleEvent>> ListEventsAsync(string contextId, string orderId, CancellationToken cancellationToken)
|
|
{
|
|
if (!events.TryGetValue(BuildKey(contextId, orderId), out var queue))
|
|
{
|
|
return Task.FromResult<IReadOnlyCollection<PersistedRestaurantLifecycleEvent>>(Array.Empty<PersistedRestaurantLifecycleEvent>());
|
|
}
|
|
|
|
var records = queue.OrderByDescending(record => record.OccurredAtUtc).ToArray();
|
|
return Task.FromResult<IReadOnlyCollection<PersistedRestaurantLifecycleEvent>>(records);
|
|
}
|
|
|
|
public Task AppendEventAsync(PersistedRestaurantLifecycleEvent record, CancellationToken cancellationToken)
|
|
{
|
|
var queue = events.GetOrAdd(BuildKey(record.ContextId, record.OrderId), static _ => new ConcurrentQueue<PersistedRestaurantLifecycleEvent>());
|
|
queue.Enqueue(record);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private static IReadOnlyCollection<PersistedRestaurantLifecycleRecord> BuildSeedOrders()
|
|
{
|
|
return new[]
|
|
{
|
|
new PersistedRestaurantLifecycleRecord(
|
|
"demo-context",
|
|
"ORD-1001",
|
|
"CHK-1001",
|
|
"T-08",
|
|
"Preparing",
|
|
"Open",
|
|
2,
|
|
true,
|
|
24.00m,
|
|
"USD",
|
|
"customer-orders",
|
|
new[] { "ITEM-101", "ITEM-202" },
|
|
DateTime.UtcNow.AddMinutes(-9)),
|
|
new PersistedRestaurantLifecycleRecord(
|
|
"demo-context",
|
|
"ORD-1002",
|
|
"CHK-1002",
|
|
"T-12",
|
|
"Served",
|
|
"Open",
|
|
4,
|
|
true,
|
|
37.50m,
|
|
"USD",
|
|
"waiter-floor",
|
|
new[] { "ITEM-301", "ITEM-404", "ITEM-405" },
|
|
DateTime.UtcNow.AddMinutes(-4))
|
|
};
|
|
}
|
|
|
|
private static IReadOnlyCollection<PersistedRestaurantLifecycleEvent> BuildSeedEvents()
|
|
{
|
|
return new[]
|
|
{
|
|
new PersistedRestaurantLifecycleEvent("demo-context", "ORD-1001", "EVT-1001", "Preparing", "Order ORD-1001 is being prepared at the hot-line station.", DateTime.UtcNow.AddMinutes(-8)),
|
|
new PersistedRestaurantLifecycleEvent("demo-context", "ORD-1002", "EVT-1002", "Served", "Order ORD-1002 was served and is ready for payment.", DateTime.UtcNow.AddMinutes(-3))
|
|
};
|
|
}
|
|
|
|
private static string BuildKey(string contextId, string orderId) => $"{contextId}::{orderId}";
|
|
}
|