using adas_core.Application.Services.Interfaces; using adas_core.Domain.Enums; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.GroupedObservations; using adas_core.Domain.Utils; using MongoDB.Bson; namespace adas_core.Application.Services.Caching { /// /// Orquestador de caché. Selecciona el backend (Redis, InMemory, None) /// según CacheSettings y la entidad del key. /// Implementa ICacheService y delega en el backend elegido. /// /// public class CacheDispatcher( RedisService redis, CacheService memory, NoCacheService noop, CacheSettings cacheSettings) : ICacheService { // Selección de backend /// /// Selects the appropriate cache backend (Redis, in-memory, or no-op) for the given key by classifying the key into an entity type and resolving its configured cache mode. /// Unknown entity types default to in-memory caching, and unrecognized modes fall back to the no-op cache service. /// /// The cache key used to determine the entity type and the corresponding cache backend. /// The instance that should handle caching for the supplied key. /// private ICacheService SelectBackend(string key) { var entity = CacheKeyClassifier.Classify(key); var mode = entity switch { CacheEnum.EntityType.Patients => cacheSettings.Patients, CacheEnum.EntityType.Displays => cacheSettings.Displays, CacheEnum.EntityType.PumpObservations => cacheSettings.PumpObservations, CacheEnum.EntityType.Appointments => cacheSettings.Appointments, CacheEnum.EntityType.GroupedObservations => cacheSettings.GroupedObservations, _ => CacheEnum.Mode.Cache }; return mode switch { CacheEnum.Mode.Redis => redis, CacheEnum.Mode.Cache => memory, _ => noop }; } // Para GroupedObservations generamos la misma clave compuesta que el resto de servicios, // de modo que el clasificador y la política de TTL funcionen igual. /// /// Builds a composite key used to identify grouped observations for a specific patient. /// /// The grouped field whose name contributes to the key. /// The identifier of the patient associated with the grouped observation. /// A formatted key string in the form GroupedObs:{patientId}:{gf.Name}. /// private static string BuildGroupedKey(GroupedField gf, ObjectId patientId) => $"GroupedObs:{patientId}:{gf.Name}"; /// /// Selects the appropriate cache backend for the given grouped field and patient identifier by building a grouped key and resolving the backend through the key-based overload. /// /// The grouped field used to derive the cache key. /// The patient identifier used to derive the cache key. /// The backend associated with the built grouped key. /// private ICacheService SelectBackend(GroupedField groupedField, ObjectId patientId) => SelectBackend(BuildGroupedKey(groupedField, patientId)); // GetOrSet (KEY string) /// /// Asynchronously retrieves the object associated with the specified key from the selected backend, or sets it using the provided factory if it is not already cached. /// /// The cache key used to identify the object and to select the appropriate backend. /// A delegate that asynchronously produces the value to store when the key is not present in the selected backend. /// An optional time-to-live duration for the cached object. If null, the backend's default expiration is applied. /// A token to observe while waiting for the operation to complete. /// A task that represents the asynchronous operation, containing the retrieved or newly created object of type . /// public Task GetOrSetObjectAsync( string key, Func> factory, TimeSpan? ttl = null, CancellationToken cancellationToken = default) => SelectBackend(key).GetOrSetObjectAsync(key, factory, ttl, cancellationToken); /// /// Asynchronously retrieves the value associated with the specified key from the backend selected for that key, /// or loads and stores it using the provided loader function if it is not already present. /// Supports an optional time-to-live (TTL) for the cached entry, and the returned value may be null. /// /// The key used to identify the cached value and to select the appropriate backend. /// An asynchronous function that produces the value to cache when no existing entry is found. /// An optional time-to-live duration after which the cached entry expires. If null, the backend's default TTL is used. /// A task that represents the asynchronous operation, containing the cached or loaded string value, or null if no value could be obtained. /// public Task GetOrSetValueAsync( string key, Func> loader, TimeSpan? ttl = null) => SelectBackend(key).GetOrSetValueAsync(key, loader, ttl); // GetOrSet (GroupedField + PatientId) /// /// Asynchronously retrieves the object associated with the specified grouped field and patient, or creates and stores it using the provided factory if it does not exist. /// The appropriate backend is selected based on the grouped field and patient identifier before the underlying get-or-set operation is performed. /// /// The type of the object to retrieve or create. /// The grouped field that determines the target backend and identifies the cached object. /// The identifier of the patient whose object is being retrieved or created. /// The asynchronous factory used to create the object when no cached value is available. /// An optional time-to-live applied to the cached object. When null, the backend's default expiration is used. /// The token to observe for canceling the asynchronous operation. /// A task that represents the asynchronous get-or-set operation, containing the retrieved or newly created object. /// public Task GetOrSetObjectAsync( GroupedField groupedField, ObjectId patientId, Func> factory, TimeSpan? ttl = null, CancellationToken cancellationToken = default) => SelectBackend(groupedField, patientId) .GetOrSetObjectAsync(groupedField, patientId, factory, ttl, cancellationToken); // Set/Get básicos /// /// Sets the value associated with the specified key by selecting the appropriate backend for that key and delegating the assignment to it. /// /// The key used to select the backend and identify the value to set. /// The value to associate with the specified key. /// public void SetValue(string key, string value) => SelectBackend(key).SetValue(key, value); /// /// Retrieves the value associated with the specified key by delegating the lookup to a backend selected for that key. /// /// The key used to select the backend and retrieve the associated value. /// The value associated with the key, or null if the selected backend returns no value. /// public string? GetValue(string key) => SelectBackend(key).GetValue(key); /// /// Retrieves an object of type from the backend selected by the given key, with an option to trigger an update. /// /// The identifier used to select the appropriate backend and to look up the object. /// Indicates whether the underlying backend should perform an update during retrieval. Defaults to true. /// A task that represents the asynchronous retrieval operation, containing the object of type or null if not found. /// public Task GetObjectAsync(string key, bool upd = true) => SelectBackend(key).GetObjectAsync(key, upd); /// /// Asynchronously stores an object in the backend selected by the specified key. /// /// The type of the object to store. /// The key used to select the backend and identify the stored object. /// The object to store in the selected backend. /// Indicates whether an update operation should be performed. Defaults to true. /// A task that represents the asynchronous store operation. /// public Task SetObjectAsync(string key, T obj, bool upd = true) => SelectBackend(key).SetObjectAsync(key, obj, upd); /// /// Retrieves an object of the specified type from the backend selected by the given key, optionally applying a time-to-live and update behavior. /// /// The identifier used to select the backend and locate the stored object. /// An optional time-to-live applied to the object; if null, the backend's default is used. /// A flag indicating whether the retrieval should update the object's state (e.g., refresh expiration). /// A task containing the deserialized object, or null if the object is not found in the selected backend. /// public Task GetObjectAsync(string key, TimeSpan? ttl, bool upd) => SelectBackend(key).GetObjectAsync(key, ttl, upd); /// /// Asynchronously stores an object in the backend selected for the specified key, with an optional time-to-live and update flag. /// /// The key used to select the target backend and identify the object. /// The object to store. /// The optional time-to-live duration for the stored object. /// Indicates whether to update an existing entry or create a new one. /// A task that represents the asynchronous set operation. /// public Task SetObjectAsync(string key, T obj, TimeSpan? ttl, bool upd) => SelectBackend(key).SetObjectAsync(key, obj, ttl, upd); /// /// Asynchronously deletes the object identified by the specified key by delegating the operation to the backend selected for that key. /// /// The identifier of the object to delete, also used to resolve the responsible backend. /// A task that represents the asynchronous delete operation. /// public Task DeleteObjectAsync(string key) => SelectBackend(key).DeleteObjectAsync(key); /// /// Deletes entries matching the specified pattern from both Redis and in-memory storage, returning the total count of deleted entries. /// /// The pattern used to match entries for deletion in both storage backends. /// The combined total number of entries deleted from Redis and in-memory storage. /// public async Task DeleteByPatternAsync(string pattern) => await redis.DeleteByPatternAsync(pattern) + await memory.DeleteByPatternAsync(pattern); /// /// Clears all cached data from both the in-memory cache and the Redis cache, ensuring that stale entries are removed across all configured cache providers. /// /// public void CleanCache() { memory.CleanCache(); redis.CleanCache(); } } }