Redeon.SuperSiteEngineCore.Web.Eltheon.Core.Features.InMemory
Overview
This feature provides the in-memory persistence primitives used across the Eltheon stack. It supplies scoped and global key/value stores, queue abstractions, factories and middleware that allow features such as Email, Search and AdminDashboard to exchange data without an external broker.
Components
IInMemoryDatabase,IGlobalInMemoryDatabase,ISessionInMemoryDatabasemodel the different lifetimes for cached data.InMemoryDatabaseFactoryandInMemoryQueueFactorydeliver the correct implementation based on the requested scope (global vs. per-request).- Queue-related contracts (
IInMemoryQueue,IQueueItem,QueueInfo) support async enqueue/dequeue, durable leasing, completion, retry/fail/dead-letter status, persistence viaSaveQueuesAsyncand inspection through diagnostic APIs.DequeueAsyncis a blocking wait that resumes when an item arrives or the cancellation token is canceled. - Manager classes expose higher-level operations for consumers that do not need to manipulate the factories directly.
Usage
builder.Services.Configure<InMemoryOptions>(configuration.GetSection("InMemory"));
builder.Services.AddSingleton<IInMemoryDatabaseProvider, InProcessInMemoryProvider>();
builder.Services.AddSingleton<InMemoryDatabaseFactory>();
builder.Services.AddSingleton<IInMemoryQueue, InMemoryQueue>();
builder.Services.AddSingleton<InMemoryQueueFactory>();
Once registered, any feature can accept an InMemoryQueueFactory or InMemoryDatabaseFactory in its constructor and obtain the storage surface it needs by calling GetDatabase(isGlobal) or GetQueue(). To plug in Redis (or a different backing store) implement IInMemoryDatabaseProvider, register it, and set InMemory:Provider in configuration to point to your provider.
"InMemory": {
"Provider": "InProcess",
"ConnectionString": ""
}
Set Provider to Redis or Memcached once you've registered the respective provider implementations. The built-in Redis and Memcached providers act as placeholders and will throw a descriptive exception until you supply your own backing implementation.
Persistence
Queues and databases can be persisted to disk via the SaveQueuesAsync/LoadQueuesAsync APIs. Ensure GlobalConfig paths are initialized so persistence targets exist.
DequeueAsync is not a polling API. Consumers should treat it as a wait-for-work primitive and cancel it when the host is shutting down.
Queue persistence writes through a temporary file and replaces/moves the final JSON file afterward. If a queue JSON file is corrupt during load, the file is quarantined with a .corrupt.*.bak suffix and the queue starts empty instead of silently overwriting the damaged file.
Durable Queue Workflow
The classic DequeueAsync API removes work from the queue as before. Durable consumers such as the EventBus outbox should use the additive lease/status APIs:
var item = await queue.TryLeaseAsync<MyQueueItem>("Eltheon.EventOutbox", cancellationToken);
if (item == null)
{
return;
}
try
{
await ProcessAsync(item, cancellationToken);
await queue.CompleteAsync("Eltheon.EventOutbox", item.QueueInfo.Id);
}
catch (Exception ex)
{
await queue.RetryAsync("Eltheon.EventOutbox", item.QueueInfo.Id, ex.Message);
}
TryLeaseAsync marks pending/retry items as running without removing them. CompleteAsync removes successfully processed items. RetryAsync, FailAsync, and DeadLetterAsync preserve the item and append queue history. RecoverRunningAsync moves running items from a previous process back to retry state so they are not lost after restart.
Diagnostics
Because the implementation is in-memory, restarts clear volatile queues unless SaveQueuesAsync is called. Durable queue items that were saved to disk are loaded from Queues/queues.json. Use the factory abstractions to hook custom instrumentation (e.g., wrapping IInMemoryQueue with your own decorator) when you need to track queue depth or latency.