using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.GroupedObservations;
using MongoDB.Bson;
using System.Collections.Concurrent;
namespace adas_core.Application.Services.Caching
{
///
/// Implementación de caché en memoria con soporte de locking seguro
/// mediante LockManagerService + InMemoryLockProvider.
/// Compatible con la interfaz ICacheService incluyendo GetOrSet.
///
public class CacheService(LockManagerService lockManager) : ICacheService
{
private readonly ConcurrentDictionary _mem = new();
// HELPERS
private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
=> $"GroupedObs:{patientId}:{gf.Name}";
// GET OR SET (string key)
public async Task GetOrSetObjectAsync(
string key,
Func> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
// FAST PATH
if (_mem.TryGetValue(key, out var existing))
return (T)existing;
// LOCKED PATH
return await lockManager.WithLockAsync(
$"getorset:{key}",
TimeSpan.FromSeconds(5),
async () =>
{
if (_mem.TryGetValue(key, out var again))
return (T)again;
var created = await factory();
if (created != null!)
_mem[key] = created;
return created!;
},
cancellationToken);
}
public async Task GetOrSetValueAsync(
string key,
Func> loader,
TimeSpan? ttlOverride = null)
{
var result = await GetOrSetObjectAsync(key, loader, ttlOverride);
return (string?)result;
}
// GET OR SET (GroupedField + patientId)
public async Task GetOrSetObjectAsync(
GroupedField groupedField,
ObjectId patientId,
Func> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
var key = BuildGroupedKey(groupedField, patientId);
if (_mem.TryGetValue(key, out var existing))
return (T)existing;
return await lockManager.WithLockAsync(
$"getorset:{key}",
TimeSpan.FromSeconds(5),
async () =>
{
if (_mem.TryGetValue(key, out var again))
return (T)again;
var created = await factory();
if (created != null!)
_mem[key] = created;
return created!;
},
cancellationToken);
}
// GET / SET
public void SetValue(string key, string value)
=> _mem[key] = value;
public string? GetValue(string key)
=> _mem.TryGetValue(key, out var v) ? v.ToString() : null;
public Task GetObjectAsync(string key, bool updateExpiration = true)
{
return Task.FromResult(
_mem.TryGetValue(key, out var v) ? (T?)v : default
);
}
public Task SetObjectAsync(string key, T obj, bool updateExpiration = true)
{
_mem[key] = obj!;
return Task.CompletedTask;
}
public Task GetObjectAsync(string key, TimeSpan? ttlOverride, bool upd)
=> GetObjectAsync(key, upd);
public Task SetObjectAsync(string key, T obj, TimeSpan? ttlOverride, bool upd)
=> SetObjectAsync(key, obj, upd);
// DELETE / CLEAN
public Task DeleteObjectAsync(string key)
{
_mem.TryRemove(key, out _);
return Task.CompletedTask;
}
public Task DeleteByPatternAsync(string pattern)
{
var p = pattern.Replace("*", "");
var keys = _mem.Keys.Where(k => k.Contains(p)).ToList();
long removed = 0;
foreach (var k in keys)
if (_mem.TryRemove(k, out _))
removed++;
return Task.FromResult(removed);
}
public void CleanCache() => _mem.Clear();
}
}