# Redis

[Redis](https://redis.io/) es uno de los motores de almacenamiento de base de datos en memoria más populares, comúnmente utilizado como caché o intermediario de mensajes.

Esta biblioteca es una integración entre Vapor y [**RediStack**](https://github.com/swift-server/RediStack), que es el controlador subyacente que se comunica con Redis.

!!! note "Nota"
    La mayoría de las capacidades de Redis son proporcionadas por **RediStack**.
    Recomendamos encarecidamente familiarizarse con su documentación.
    
    _Se proporcionan enlaces donde corresponda._

## Paquete

El primer paso para usar Redis es añadirlo como una dependencia a tu proyecto en tu manifiesto de paquete Swift.

> Este ejemplo es para un paquete existente. Para obtener ayuda sobre cómo iniciar un nuevo proyecto, consulta la guía [Comenzando](../getting-started/hello-world.md).

```swift
dependencies: [
    // ...
    .package(url: "https://github.com/vapor/redis.git", from: "4.0.0")
]
// ...
targets: [
    .target(name: "App", dependencies: [
        // ...
        .product(name: "Redis", package: "redis")
    ])
]
```

## Configurar

Vapor emplea una estrategia de agrupación para instancias de [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisconnection), y hay varias opciones para configurar las conexiones individuales así como los propios grupos.

El mínimo requerido para configurar Redis es proporcionar una URL para conectar:

```swift
let app = Application()

app.redis.configuration = try RedisConfiguration(hostname: "localhost")
```

### Configuración de Redis

> Documentación de la API: [`RedisConfiguration`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration)

#### serverAddresses

Si tienes varios puntos de conexión con Redis, como un grupo de instancias de Redis, querrás crear una colección de [`[SocketAddress]`](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/socketaddress) para pasar en el inicializador.

La forma más común de crear un `SocketAddress` es con el método estático [`makeAddressResolvingHost(_:port:)`](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/socketaddress/makeaddressresolvinghost(_:port:)).

```swift
let serverAddresses: [SocketAddress] = [
  try .makeAddressResolvingHost("localhost", port: RedisConnection.Configuration.defaultPort)
]
```

Para un único punto de conexión con Redis, puede ser más fácil trabajar con los inicializadores de conveniencia, ya que manejará la creación del `SocketAddress` por ti:

- [`.init(url:pool)`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/init(url:tlsconfiguration:pool:)-o9lf) (con `String` o [`Foundation.URL`](https://developer.apple.com/documentation/foundation/url))
- [`.init(hostname:port:password:database:pool:)`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/init(hostname:port:password:tlsconfiguration:database:pool:))

#### password

Si tu instancia de Redis está asegurada con una contraseña, deberás pasarla como el argumento `password`.

Cada conexión, según se crea, será autenticada usando la contraseña.

#### database

Este es el índice de la base de datos que deseas seleccionar cuando se crea cada conexión.

Esto te ahorra tener que enviar el comando `SELECT` a Redis tú mismo.

!!! warning "Advertencia"
    La selección de la base de datos no se mantiene. Ten cuidado al enviar el comando `SELECT` por tu cuenta.

### Opciones del Grupo de Conexiones

> Documentación de la API: [`RedisConfiguration.PoolOptions`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/pooloptions)

!!! note "Nota"
    Aquí solo se destacan las opciones que se cambian con más frecuencia. Para todas las opciones, consulta la documentación de la API.

#### minimumConnectionCount

Este es el valor que establece cuántas conexiones deseas que cada grupo mantenga en todo momento.

Si el valor es `0`, entonces si las conexiones se pierden por cualquier motivo, el grupo no las recreará hasta que sea necesario.

Esto se conoce como una conexión de "inicio en frío" ("cold start"), y tiene cierta sobrecarga sobre el mantenimiento de un recuento mínimo de conexiones.

#### maximumConnectionCount

Esta opción determina el comportamiento de cómo se mantiene el recuento máximo de conexiones.

!!! seealso "Ver También"
    Consulta la API `RedisConnectionPoolSize` para familiarizarte con las opciones disponibles.

## Enviando un Comando

Puedes enviar comandos usando la propiedad `.redis` en cualquier instancia de [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) o [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request), lo que te dará acceso a un [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisclient).

Cualquier `RedisClient` tiene varias extensiones para todos los diversos [comandos de Redis](https://redis.io/commands).

```swift
let value = try app.redis.get("my_key", as: String.self).wait()
print(value)
// Optional("my_value")

// o

let value = try await app.redis.get("my_key", as: String.self)
print(value)
// Optional("my_value")
```

### Comandos no soportados

Si **RediStack** no soporta un comando con un método de extensión, aún puedes enviarlo manualmente.

```swift
// cada valor después del comando es el argumento posicional que Redis espera
try app.redis.send(command: "PING", with: ["hello"])
    .map {
        print($0)
    }
    .wait()
// "hello"

// o

let res = try await app.redis.send(command: "PING", with: ["hello"])
print(res)
// "hello"
```

## Modo Pub/Sub

Redis admite la capacidad de entrar en un [modo "Pub/Sub"](https://redis.io/topics/pubsub) donde una conexión puede escuchar "canales" específicos y ejecutar closures (métodos) específicos cuando los canales suscritos publican un "mensaje" (algún valor de datos).

Hay un ciclo de vida definido para una suscripción:

1. **subscribe**: invocado una vez cuando la suscripción comienza por primera vez
2. **message**: invocado 0 o más veces a medida que se publican mensajes en los canales suscritos
3. **unsubscribe**: invocado una vez cuando la suscripción termina, ya sea por solicitud o por pérdida de conexión

Cuando creas una suscripción, debes proporcionar al menos un [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redissubscriptionmessagereceiver) para manejar todos los mensajes que son publicados por el canal suscrito.

Opcionalmente, puedes proporcionar un `RedisSubscriptionChangeHandler` para `onSubscribe` y `onUnsubscribe` que maneje sus respectivos eventos del ciclo de vida.

```swift
// crea 2 suscripciones, una para cada canal
app.redis.subscribe
  to: "channel_1", "channel_2",
  messageReceiver: { channel, message in
    switch channel {
    case "channel_1": // haz algo con el mensaje
    default: break
    }
  },
  onUnsubscribe: { channel, subscriptionCount in
    print("unsubscribed from \(channel)")
    print("subscriptions remaining: \(subscriptionCount)")
  }
```
