Mi opinión sobre el caché en memoria

· 4 min de lectura

He estado trabajando en algunas cosas que manejaban una gran cantidad de datos. Mientras lo hacía, me di cuenta de que una gran parte nunca cambia, o al menos durante un tiempo determinado, no cambia.

Entonces pensé que sería útil crear un repositorio de caché personal, por supuesto, esto no es nuevo, hace unas semanas leí sobre esto en la publicación de StackOverflow escrita por Nick Craver sobre cómo administran el caché de la aplicación.

Además, siempre quise trabajar con caché, cómo funciona, la lógica y cómo podría hacer que funcione, así que… ¡por qué no!

Flujo

Aquí hay un flujo rápido de cómo se comportará la clase que tiene el control del caché cuando el usuario solicite un valor.

Imagen de Gyazo

Implementación

Antes de comenzar

Lo sé, lo sé. Hay System.Runtime.Caching que maneja la memoria caché. Pero decidí construirlo yo mismo. Si quieres usar esa clase, consulta aquí para obtener instrucciones.

Elemento de caché

El primer paso es tener una clase que almacene el valor de un objeto y la fecha de vencimiento. Probablemente haya una mejor manera de hacerlo, pero esto es lo que pensé, así que ahí lo tienes:

public class CacheItem
{
    public string Identifier { get; set; }
    public object Value { get; set; }
    public DateTime ValidUntil { get; set; }

    public CacheItem(string identifier, object value, TimeSpan valid)
    {
        Identifier = identifier;
        Value = value;
        ValidUntil = DateTime.UtcNow.Add(valid);
    }
}

Repositorio de caché

Luego necesitamos una clase que maneje los objetos y los guarde en algún lugar (como CacheItem). Me gusta manejar todos los datos/modelos en clases que tienen el sufijo Repository, pero no es necesario, así que creemos uno para el almacenamiento en caché.

public class CacheRepository
{
    private static Dictionary<string, string> Cache = new Dictionary<string, string>();
    private static T Set<T>(string key, Func<T> lookup, TimeSpan durationMinutes)
    {
        var item = new Models.Item(key, lookup(), durationMinutes);
        return Save<T>(key, item);
    }
    private static T Save<T>(string key, Item item)
    {
        Cache[key] = JsonConvert.SerializeObject(item);
        return (T)item.Value;
    }
    private static T Get<T>(string key)
    {
        var cached = Cache.FirstOrDefault(x => x.Key == key).Value;
        var item = string.IsNullOrEmpty(cached) ? null : JsonConvert.DeserializeObject<Item>(cached);
        return (item == null) 
        ? default : (item.ValidUntil > DateTime.UtcNow) 
        ? JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(item.Value)) : default;
    }
    public static T GetOrSet<T>(string key, Func<T> lookup, TimeSpan durationMinutes)
    {
        var cache = Get<T>(key);
        return cache == null ? Set(key, lookup, durationMinutes) : cache;
    }
}

Hasta ahora tenemos un diccionario estático llamado Cache donde se almacenarán todos los elementos. Recuerde que esto solo durará mientras la aplicación se esté ejecutando, por eso este título de tutorial tiene almacenamiento en memoria caché.

Tenga en cuenta que el elemento Cache se inicializará una vez que se cargue la clase CacheRepository.

El único método disponible al invocar la clase CacheRepository es GetOrSet(string key, Func<T> lookup, TimeSpan durationMinutes) que necesita tres parámetros:

  1. key: el identificador del objeto a guardar.
  2. lookup: la función de devolución de llamada en caso de que el caché caduque o sea nulo.
  3. durationMinutes: la duración en minutos que se agregará a la hora actual (en UTC).

Hora de almacenar en caché

Ahora, use nuestro repositorio de almacenamiento en caché para obtener algunos datos de algún lugar.

Para que todo esto tenga sentido, creemos un objeto de ejemplo con algunas propiedades y luego un repositorio para buscar y completar una lista de ese objeto.

public class User 
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}
public class UserRepository 
{
    public List<User> Get()
    {
        // Code to get users
    }
}

Ya que tenemos el método para llenar una lista de User, usemos la clase CacheRepository.

private UserRepository _userRepository = new UserRepository();
private List<User> _user;
public List<User> User
{
    get
    {
        _user = CacheRepository.GetOrSet($"users", usersRepo.Get, TimeSpan.FromMinutes(10));
        return _user;
    }
}

Y así, cada vez que accedas a la variable User, te pedirá a CacheRepository el valor de un objeto que tiene la clave users.

Si esa clave existe, verificará la fecha de vencimiento. Si cualquiera de estas condiciones es falsa, utilizará la devolución de llamada para establecer el valor (con usersRepo.Get) del objeto, lo guardará en la caché con la fecha de vencimiento establecida en DateTime.UtcNow + TimeSpan.FromMinutes(10) y lo devolverá.