Creado apartir del commit b888de6c92056de294456f581a56e25e734d4ab7 de develop

This commit is contained in:
jrojas
2026-06-26 09:52:35 +02:00
commit 319fd3dfb0
746 changed files with 106610 additions and 0 deletions
@@ -0,0 +1,275 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Utils;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.GroupedObservations;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using MongoDB.Bson;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using StackExchange.Redis;
namespace adas_core.Application.Services.Caching
{
public class RedisService : ICacheService
{
private readonly ILogger<RedisService> _logger;
private readonly CacheSettings _cacheSettings;
private readonly LockManagerService _lockManager;
private ConnectionMultiplexer? _connection;
private IDatabase? _database;
private IServer? _server;
public IDatabase? Database => _database;
private bool _isRedisAvailable;
public RedisService(
IOptions<CacheSettings> options,
ILogger<RedisService> logger,
LockManagerService lockManager)
{
_cacheSettings = options.Value;
_logger = logger;
_lockManager = lockManager;
if (!string.IsNullOrEmpty(_cacheSettings.Redis.ConnectionString))
_ = InitializeRedisConnectionAsync();
}
// GET OR SET (string key)
public async Task<T> GetOrSetObjectAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
if (!_isRedisAvailable)
return await factory();
var direct = await GetObjectAsync<T>(key);
if (direct is not null)
return direct;
return await _lockManager.WithLockAsync(
$"getorset:{key}",
TimeSpan.FromSeconds(5),
async () =>
{
var again = await GetObjectAsync<T>(key);
if (again is not null)
return again;
var created = await factory();
if (created != null)
await SetObjectAsync(key, created, ttl, true);
return created!;
},
cancellationToken);
}
public async Task<string?> GetOrSetValueAsync(
string key,
Func<Task<string>> loader,
TimeSpan? ttl = null)
{
if (!_isRedisAvailable)
return await loader();
var direct = GetValue(key);
if (direct is not null)
return direct;
return await _lockManager.WithLockAsync(
$"getorset:{key}",
TimeSpan.FromSeconds(5),
async () =>
{
var again = GetValue(key);
if (again is not null)
return again;
var created = await loader();
SetValue(key, created);
return created;
});
}
// GET OR SET (GroupedField + patientId)
private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
=> $"GroupedObs:{patientId}:{gf.Name}";
public async Task<T> GetOrSetObjectAsync<T>(
GroupedField groupedField,
ObjectId patientId,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
var key = BuildGroupedKey(groupedField, patientId);
if (!_isRedisAvailable)
return await factory();
var direct = await GetObjectAsync<T>(key);
if (direct is not null)
return direct;
return await _lockManager.WithLockAsync(
$"getorset:{key}",
TimeSpan.FromSeconds(5),
async () =>
{
var again = await GetObjectAsync<T>(key);
if (again is not null)
return again;
var created = await factory();
if (created != null)
await SetObjectAsync(key, created, ttl, true);
return created!;
},
cancellationToken);
}
// BASIC OPERATIONS
public void SetValue(string key, string value)
=> _database?.StringSet(key, value, GetEntityTtl(key), true);
public string? GetValue(string key)
{
var val = _database?.StringGet(key);
if (val.HasValue && ShouldRenewTtl(key))
_database?.KeyExpire(key, GetEntityTtl(key));
return val;
}
public async Task<T?> GetObjectAsync<T>(string key, bool updateExpiration = true)
{
if (!_isRedisAvailable)
return default;
var json = await _database!.StringGetAsync(key);
if (json.IsNullOrEmpty)
return default;
if (updateExpiration)
_database!.KeyExpire(key, GetEntityTtl(key));
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter> { new StringEnumConverter(), new ObjectIdConverter() }
};
try
{
return JsonConvert.DeserializeObject<T>(json!, settings);
}
catch (Exception e)
{
_logger.LogError("Error deserializing object in RedisService {e}",e.ToString());
throw new Exception($"Error deserializing object in RedisService {e}", e);
}
}
public async Task SetObjectAsync<T>(
string key,
T obj,
bool updateExpiration = true)
=> await SetObjectAsync(key, obj, null, updateExpiration);
public async Task SetObjectAsync<T>(
string key,
T obj,
TimeSpan? ttlOverride,
bool updateExpiration)
{
if (!_isRedisAvailable)
return;
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter> { new StringEnumConverter(), new ObjectIdConverter() }
};
var json = JsonConvert.SerializeObject(obj, settings);
await _database!.StringSetAsync(key, json, ttlOverride ?? GetEntityTtl(key), true);
}
public async Task DeleteObjectAsync(string key)
{
if (_isRedisAvailable)
await _database!.KeyDeleteAsync(key);
}
public async Task<long> DeleteByPatternAsync(string pattern)
{
if (!_isRedisAvailable || _server == null)
return 0;
var keys = _server.Keys(pattern: pattern).ToArray();
foreach (var key in keys)
await _database!.KeyDeleteAsync(key);
return keys.Length;
}
public void CleanCache()
=> _server?.FlushDatabase();
// TTL
private TimeSpan? GetEntityTtl(string key)
{
var entity = CacheKeyClassifier.Classify(key);
var ttl = _cacheSettings.Redis.Ttl;
int? seconds = entity switch
{
CacheEnum.EntityType.Patients => ttl.PatientsSeconds,
CacheEnum.EntityType.Displays => ttl.DisplaysSeconds,
_ => ttl.GlobalSeconds
};
return seconds > 0 ? TimeSpan.FromSeconds(seconds.Value) : null;
}
private bool ShouldRenewTtl(string key)
=> GetEntityTtl(key) != null;
// INITIALIZATION
private async Task InitializeRedisConnectionAsync()
{
_isRedisAvailable = false;
try
{
if (_cacheSettings.Redis.ConnectionString == null)
return;
_connection = await ConnectionMultiplexer.ConnectAsync(_cacheSettings.Redis.ConnectionString);
_database = _connection.GetDatabase();
_server = _connection.GetServer(_cacheSettings.Redis.ConnectionString);
_isRedisAvailable = true;
}
catch(Exception ex)
{
_logger.LogError( "Error Initializing Redis Connection. Exception:{ex}", ex.Message);
}
}
public Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttlOverride, bool updateExpiration)
=> GetObjectAsync<T>(key, updateExpiration);
}
}