operations-service/src/Operations.Service.Application/Ports/InMemoryRestaurantLifecycleStorePort.cs
2026-03-31 18:38:30 -06:00

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}";
}