Mejorar el rendimiento de mis aplicaciones .Net 6 con REDIS
Tags: spanish, redis, csharp, latam
Published: 2022-10-14 16:24:46

En este post nos centraremos en el caso de uso de caching para la optimización de la respuesta de algún servicio en especifico.

Para ello explicare el siguiente codigo que esta en mi repositorio de github [CacheServicesApp](https://github.com/airarrazabald/CacheServicesApp) donde esta implementado en una API .Net 6 con redis

## Pre requisitos

- Lo primero es tener instalado redis, para ello tengo un post en el cual explico como montar redis en docker **[Redis en Docker Container](https://dev.to/airarrazabald/redis-en-docker-container-4o6p)**
- Tener instalado Visual studio 2022

## El problema

El siguiente problema es hipotético el cual puede pasar en un ambiente productivo real:

Tenemos una api que esta montada en la nube en EE.UU la cual debe comunicarse con otro servicio externo el cual esta en Chile. El problema es que tiene un latencia muy alta debido a la diferencia geográfica entre ambos servicios y la alta demanda de solicitudes lo que afecta aumentando el tiempo de respuesta.

## Solución

Una posible solución es montar un servidor de cache distribuida (en este caso **Redis**) que este en la misma localización que este nuestro servicio.

Por que cache distribuida y no en la cache de la memoria: Esto dependerá si los datos que necesitamos extraer también lo consume otro cliente en ese caso seria una buena opción utilizar cache distribuida.

## Construyendo el servicio

En esta ocasión consumiremos un endpoint desde nuestro nuevo servicio construido Net Core 6, el cual en una primera petición obtendrá los datos, los almacenara en Redis y los devuelve al front. En una segunda petición traera los datos directo de Redis optimizando los tiempos de respuesta.

### Crear la solución

- En Visual Studio 2022 creamos un proyecto con la plantilla **ASP NET. Core Web API**.
    
- Luego nos solicitará el nombre del proyecto el cual sera **CacheServiceApp**
    
- En el siguiente paso seleccionamos
    
    - Framework: *.NET 6.0*
    - Marcar check de *Configurar para HTTPS*
    - Marcar check de *Usar Controladores*
    - Marcar check de *Habilitar compatibilidad con OpenAPI* (para implementar swagger)
- Con esto pasos ya tenemos nuestro proyecto y solución lista

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oe1iifi2u0bwnohp6h8s.png)

### Crear los proyectos

ahora debemos agregar dos capas al proyecto, las cuales serán del tipo librería de clases y quedaría según la siguiente imagen:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4iy9ebzv8tvzm5w8v66k.png)

Ahora tenemos: la capa Model de nuestras entidades, la capa Services para conectarnos a fuentes externas y nuestra capa de presentación que para esta ocasión es de tipo API.

### Instalar los paquetes nugget

Para conectarnos a Redis debemos instalar los siguientes paquetes

En nuestra capa **CacheServiceApp.Services** instalamos el paquete

***Microsoft.Extensions.Caching.Abstractions***

Este paquete es el que a través de la interfaz *IDistributedCache* nos permitirá interactuar con **Redis** que lo veremos mas adelante.

En la capa donde exponemos la api **CacheServiceApp** instalamos el paquete:

***Microsoft.Extensions.Caching.StackExchange***

Esto nos permitirá en el archivo *Program.cs* inyectar la cadena de conexión a **Redis.**

### Conectar al servicio

#### CacheServiceApp

Lo primero como mencionaba en el punto anterior debemos generar la conexión en la capa de presentación **CacheServiceApp** (API) en el archivo *Program.cs* y dejaremos el string de conexión en el archivo *appsettings.json*.

appsettings.json:

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Caching": {
    "RedisConnection": "localhost:6379"
  },
  "Endpoint": {
    "UrlBirds": "https://aves.ninjas.cl/api/birds"
  }
}

```

- ***RedisConnection*** es nuestra cadena de conexión a redis. Recuerden que en este ejemplo utilizamos redis en docker, que el tutorial lo deje en el link mas arriba.
    
- La propiedad ***Endpoint*** es la url del servicio que consumiremos para simular la latencia entre nuestra api y el servicio y luego almacenaremos esos datos en Redis.
    

Program.cs:

```csharp
using CacheServiceApp.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var configuration = builder.Configuration;
builder.Services.AddControllers();

builder.Services.AddHttpClient<IBirdsService, BirdsService>(client =>
{
    client.BaseAddress = new Uri(configuration.GetValue<string>("Endpoint:UrlBirds"));
});

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = configuration.GetValue<string>("Caching:RedisConnection");
});

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.Run();

```

- Aca son importantes tres puntos en la clase:

1.  obtengo la configuración para traer el string de conexión del appsettings.json

```csharp
var configuration = builder.Configuration;
```

2. Genero la inyección del HttpClient del servicio que consumimos

```csharp
builder.Services.AddHttpClient<IBirdsService, BirdsService>(client =>
{
    client.BaseAddress = new Uri(configuration.GetValue<string>("Endpoint:UrlBirds"));
});
```

3.  Conexión a Redis

```csharp
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = configuration.GetValue<string>("Caching:RedisConnection");
});
```

#### CacheServiceApp.Services

Para conectarnos al servicio se debe crear la clase BirdService y la interfaz IBirdService para luego realizar la inyección de dependencia.

BirdService.cs:

```csharp
public class BirdsService : IBirdsService
{
    private readonly HttpClient _httpClient;
    private readonly IDistributedCache _cache;

    public BirdsService(HttpClient httpClient, IDistributedCache cache) => (_httpClient, _cache) = (httpClient, cache);

    public async Task<List<Bird>> Get()
    {
        string cacheKey = "listBirds";
        string serializedBirds;

        var redisBirds = await _cache.GetAsync(cacheKey);
        List<Bird> listBirds;

        if (redisBirds != null)
        {
            serializedBirds = Encoding.UTF8.GetString(redisBirds);
            listBirds = JsonConvert.DeserializeObject<List<Bird>>(serializedBirds);
        }
        else
        {
            listBirds = await _httpClient.GetFromJsonAsync<List<Bird>>(_httpClient.BaseAddress);
            serializedBirds = JsonConvert.SerializeObject(listBirds);
            redisBirds = Encoding.UTF8.GetBytes(serializedBirds);
            var options = new DistributedCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromSeconds(30))
                .SetSlidingExpiration(TimeSpan.FromSeconds(10));

            await _cache.SetAsync(cacheKey, redisBirds, options);
        }

        return listBirds;
    }
}
```

- Aca son importantes 3 puntos en la clase:

1.  Declarar en el contructor la interfaz IDistributedCache para obtener y gurdar datos en Redis

```
private readonly HttpClient _httpClient;
private readonly IDistributedCache _cache;

public BirdsService(HttpClient httpClient, IDistributedCache cache)
```

2.  Obtener datos desde redis

```
var redisBirds = await _cache.GetAsync(cacheKey);
```

3.  Guardar datos en Redis

```
var options = new DistributedCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromSeconds(30))
                .SetSlidingExpiration(TimeSpan.FromSeconds(10));

await _cache.SetAsync(cacheKey, redisBirds, options);
```

Ya con la implementación en la clase BirdService.cs estaríamos haciendo lo siguiente:

1.  Verificamos si existen datos en redis con la key listBirds
2.  Si hay datos los deserializa de arregglo de bytes y posteriormente de json a objeto
3.  Si no hay datos en la cache la primera vez consulta el servicio de Birds
4.  Serializa en json y luego los guardamos como arreglo de bytes en redis con un expiración absoluta de 30 segundos y la expiración parcial 10 segundos, lo que quire decir que si nadie consulta en 10 segundos expira con un máximo de 30 segundos.

### Probemos la solución

Corremos nuestra aplicación CacheServiceApp y nos mostrara el swagger.

Para probarla utilizare Postman que para mi caso la api utiliza el puerto 5112:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ip312qfw2rgt2eq5iky8.png)

La primera vez que ejecutamos el servicio demora 9.73 segundos

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vc7874o0kxap2v1kfryk.png)

La segunda vez que lo ejecutamos lo ira a buscar a Redis por lo que tendrá una mayor velocidad de respuesta bajando a 18 milisegundos.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/boo4p7veequg5zbq8ovv.png)

### Conclusiones

- Para este ejemplo entre 9 segundos y 18 milisegundos no hay tanta diferencia, pero si lo implementan en un ambiente productivo en servicio que aprox se demora 10 y 30 segundos y tiene cientos de solicitudes por minuto, ahí si se vera la diferencia.
    
- Cada vez que necesitamos mejorar el rendimiento debes evaluar si es necesario utilizar la cache distribuida o solo en la memoria RAM en donde esta montando el servicio.

:briefcase: <a href="http://airarrazabal.netlify.app/" target="_blank">Mi Portafolio</a>