Exercício – Conectar um aplicativo ao cache

Concluído

Agora que temos um Cache Redis criado no Azure, vamos criar um aplicativo para usá-lo. Verifique se você tem as informações de cadeia de conexão do portal Azure.

Observação

O Cloud Shell integrado está disponível no lado direito. Você pode usar o prompt de comando para criar e executar o código de exemplo que estamos construindo aqui, ou realizar essas etapas localmente se tiver um ambiente de desenvolvimento .NET Core configurado.

Criar um aplicativo de console

Usaremos um aplicativo de console simples para focarmos na implementação do Redis.

  1. No Cloud Shell, crie um aplicativo de console .NET Core e nomeie-o como SportsStatsTracker.

    dotnet new console --name SportsStatsTracker
    
  2. Altere o diretório atual para a pasta do novo projeto.

    cd SportsStatsTracker
    

Adicionar a cadeia de conexão

Vamos adicionar a cadeia de conexão que obtivemos do portal Azure no código. Nunca armazene esse tipo de credencial no seu código-fonte. Para simplificar esse exemplo, vamos utilizar um arquivo de configuração. Uma abordagem melhor para um aplicativo de servidor no Azure seria utilizar o Azure Key Vault com certificados.

  1. Crie um novo arquivo appsettings.json para adicionar ao projeto.

    touch appsettings.json
    
  2. Abra o editor de código inserindo code . na pasta do projeto. Caso esteja trabalhando localmente, use o Visual Studio Code. As etapas aqui serão alinhadas principalmente ao uso.

  3. Selecione o arquivo appsettings.json no editor e adicione o texto a seguir. Cole a cadeia de conexão no valor da configuração.

    {
      "CacheConnection": "[value-goes-here]"
    }
    
  4. Salve as alterações.

    Importante

    Sempre que você colar ou alterar o código em um arquivo no editor, salve usando o menu ... ou a tecla de atalho (Ctrl + S no Windows e no Linux, e Cmd + S no macOS).

  5. Selecione o arquivo SportsStatsTracker. csproj no editor para abri-lo.

  6. Adicione o seguinte bloco de configuração <ItemGroup> ao elemento raiz <Project> abaixo do elemento <PropertyGroup>. Esta configuração incluirá o novo arquivo no projeto e o copiará na pasta de saída. As instruções neste bloco garantem que o arquivo de configuração de aplicativos seja colocado no diretório de saída quando o aplicativo for compilado/criado.

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
          ...
      </PropertyGroup>
    
      <ItemGroup>
         <None Update="appsettings.json">
           <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </None>
      </ItemGroup>
    
    </Project>
    
  7. Salve o arquivo.

    Importante

    Se você não salvar o arquivo, perderá a alteração ao adicionar o pacote mais tarde.

Adicionar suporte para ler um arquivo de configuração JSON

Um aplicativo .NET Core exige pacotes NuGet adicionais para ler um arquivo de configuração JSON.

Na seção do prompt de comando da janela, adicione uma referência ao pacote NuGet Microsoft.Extensions.Configuration.Json:

dotnet add package Microsoft.Extensions.Configuration.Json

Adicionar um código para ler o arquivo de configuração

Agora que adicionamos as bibliotecas necessárias para habilitar a configuração de leitura, é necessário habilitar essa funcionalidade no aplicativo de console.

  1. Selecione Program.cs no editor. Substitua o conteúdo do arquivo pelo seguinte código:

    using Microsoft.Extensions.Configuration;
    
    namespace SportsStatsTracker
    {
        class Program
        {
            static void Main(string[] args)
            {
                var config = new ConfigurationBuilder()
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json")
                    .Build();
            }
        }
    }
    

    A instrução using permite acessar as bibliotecas para ler a configuração. O código no método Main inicializa o sistema de configuração para ler o arquivo appsettings.json.

Obtenha a cadeia de conexão da configuração

Em Program.cs, no final do método Main, utilize a nova variável config para recuperar a cadeia de conexão e armazená-la em uma nova variável denominada connectionString.

A variável config tem um indexador no qual você pode passar uma cadeia de caracteres para recuperação do seu arquivo appSettings.json.

string connectionString = config["CacheConnection"];

Adicionar suporte para o cliente Redis cache .NET

Em seguida, vamos configurar o aplicativo de console para utilizar o cliente StackExchange.Redis para .NET.

  1. Adicione o pacote NuGet StackExchange.Redis ao projeto utilizando o prompt de comando na parte inferior do editor do Cloud Shell.

    dotnet add package StackExchange.Redis
    
  2. Selecione Program.cs no editor e adicione um using para o namespace StackExchange.Redis

    using StackExchange.Redis;
    

Depois que a instalação for concluída, o cliente de cache Redis estará disponível para uso com o seu projeto.

Conectar-se ao cache

Vamos adicionar o código para conectar-se ao cache.

  1. Selecione Program.cs no editor.

  2. Criar um ConnectionMultiplexer utilizando ConnectionMultiplexer.Connect passando sua cadeia de conexão. Nomeie o valor retornado cache.

  3. Como a conexão criada é descartável, encapsule-a em um bloco using. Seu código deve ter uma aparência semelhante à seguinte.

    string connectionString = config["CacheConnection"];
    
    using (var cache = ConnectionMultiplexer.Connect(connectionString))
    {
    
    }
    

Observação

A conexão com o Cache do Azure para Redis é gerenciada pela classe ConnectionMultiplexer. Essa classe deve ser compartilhada e reutilizada em todo o seu aplicativo cliente. Nós não queremos criar uma nova conexão para cada operação. Em vez disso, queremos armazená-la como um campo em nossa classe e reutilizá-la para cada operação. Aqui, vamos usá-lo apenas no método Main, mas em uma aplicação de produção, ele deve ser armazenado em um campo de classe ou em um singleton.

Adicione um valor para o cache

Agora que temos a conexão, vamos adicionar um valor para o cache.

  1. Dentro do bloco using após a conexão ter sido criada, utilize o método GetDatabase para recuperar uma instância de IDatabase:

     IDatabase db = cache.GetDatabase();
    
  2. Chame StringSet no objeto IDatabase para definir a chave "test:key" para o valor "some value".

O valor retornado de StringSet é um bool que indica se a chave foi adicionada.

  1. Exiba o valor retornado de StringSet no console:

     bool setValue = db.StringSet("test:key", "some value");
     Console.WriteLine($"SET: {setValue}");
    

Obter um valor do cache

  1. Em seguida, recupere o valor usando StringGet. Esse método usa a chave para recuperar e retornar o valor.

  2. Exiba o valor retornado:

     string? getValue = db.StringGet("test:key");
     Console.WriteLine($"GET: {getValue}");
    
  3. Seu código deve ficar assim:

     using System;
     using Microsoft.Extensions.Configuration;
     using System.IO;
     using StackExchange.Redis;
    
     namespace SportsStatsTracker
     {
         class Program
         {
             static void Main(string[] args)
             {
                 var config = new ConfigurationBuilder()
                     .SetBasePath(Directory.GetCurrentDirectory())
                     .AddJsonFile("appsettings.json")
                     .Build();
    
                 string connectionString = config["CacheConnection"];
    
                 using (var cache = ConnectionMultiplexer.Connect(connectionString))
                 {
                     IDatabase db = cache.GetDatabase();
    
                     bool setValue = db.StringSet("test:key", "some value");
                     Console.WriteLine($"SET: {setValue}");
    
                     string? getValue = db.StringGet("test:key");
                     Console.WriteLine($"GET: {getValue}");
                 }
             }
         }
     }
    
  4. Execute o aplicativo para ver o resultado. Insira dotnet run na janela do terminal abaixo do editor. Verifique se você está na pasta do projeto, caso contrário, não encontrará seu código para compilar e executar.

     dotnet run
    

Dica

Se o programa for compilado, mas não faz o que você espera, pode ser porque você não salvou as alterações no editor. Não se esqueça de salvar as alterações ao alternar entre as janelas do terminal e do editor.

Usar as versões assíncronas dos métodos

Conseguimos obter e definir valores do cache, mas estamos usando as versões síncronas mais antigas desses métodos. Em aplicativos do lado do servidor, esses métodos não são um tipo de uso eficiente dos threads. Nesse caso, é melhor usar as versões assíncronas dos métodos. É fácil identificá-los: todos eles terminam com Async.

Para facilitar o trabalho com esses métodos, é possível utilizar o C# async e await palavras-chave. Se você estiver usando seu próprio ambiente de desenvolvimento do .NET Core em vez do Cloud Shell integrado, use pelo menos o C# 7.1 para poder aplicar essas palavras-chave ao método Main.

Aplicar a palavra-chave assíncrona

Para aplicar a palavra-chave async ao método Main. Teremos que fazer duas coisas.

  1. Adicione a palavra-chave async à assinatura do método Main.

  2. Alterar o tipo de retorno de void para Task.

    using Microsoft.Extensions.Configuration;
    using StackExchange.Redis;
    
    namespace SportsStatsTracker
    {
       class Program
       {
          static async Task Main(string[] args)
          {
             ...
    

Obter e definir valores de forma assíncrona

Podemos manter os métodos síncronos em vigor. Vamos adicionar uma chamada aos métodos StringSetAsync e StringGetAsync para adicionar outro valor ao cache. Defina o valor de counter como 100.

  1. Use os métodos StringSetAsync e StringGetAsync para definir e recuperar uma chave chamada counter. Defina o valor como 100.

  2. Aplicar a palavra-chave await para obter os resultados de cada método.

  3. Exiba os resultados na janela do console, assim como foi feito com as versões síncronas:

    // Simple get and put of integral data types into the cache
    setValue = await db.StringSetAsync("counter", "100");
    Console.WriteLine($"SET: {setValue}");
    
    getValue = await db.StringGetAsync("counter");
    Console.WriteLine($"GET: {getValue}");
    
  4. Execute o aplicativo novamente. Ele ainda deve funcionar e agora tem dois valores.

Incrementar o valor

  1. Use o método StringIncrementAsync para incrementar seu contador valor. Passe o número 50 para adicioná-lo ao contador:

    • Observe que o método utiliza as chaves e tanto uma long ou double.

    • Dependendo dos parâmetros passados, ele retorna um long ou double.

  2. Emitir os resultados do método ao console.

    long newValue = await db.StringIncrementAsync("counter", 50);
    Console.WriteLine($"INCR new value = {newValue}");
    

Outras operações

Por fim, vamos tentar executar alguns métodos adicionais com o suporte do ExecuteAsync.

  1. Execute PING para testar a conexão do servidor. Ele deverá responder com PONG.

  2. Execute FLUSHDB para limpar os valores do banco de dados. Ele deverá responder com OK:

    var result = await db.ExecuteAsync("ping");
    Console.WriteLine($"PING = {result.Type} : {result}");
    
    result = await db.ExecuteAsync("flushdb");
    Console.WriteLine($"FLUSHDB = {result.Type} : {result}");
    

O código final deve ter uma aparência semelhante à seguinte:

using Microsoft.Extensions.Configuration;
using StackExchange.Redis;

namespace SportsStatsTracker
{
   class Program
   {
      static async Task Main(string[] args)
      {
         var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();

         string connectionString = config["CacheConnection"];

         using (var cache = ConnectionMultiplexer.Connect(connectionString))
         {
            IDatabase db = cache.GetDatabase();

            bool setValue = db.StringSet("test:key", "some value");
            Console.WriteLine($"SET: {setValue}");

            string getValue = db.StringGet("test:key");
            Console.WriteLine($"GET: {getValue}");

            setValue = await db.StringSetAsync("counter", "100");
            Console.WriteLine($"SET: {setValue}");

            getValue = await db.StringGetAsync("counter");
            Console.WriteLine($"GET: {getValue}");

            long newValue = await db.StringIncrementAsync("counter", 50);
            Console.WriteLine($"INCR new value = {newValue}");  

            var result = await db.ExecuteAsync("ping");
            Console.WriteLine($"PING = {result.Type} : {result}");

            result = await db.ExecuteAsync("flushdb");
            Console.WriteLine($"FLUSHDB = {result.Type} : {result}");
         }
      }
   }
}

Agora, quando você executar o aplicativo, a seguinte saída será emitida:

SET: True
GET: some value
SET: True
GET: 100
INCR new value = 150
PING = SimpleString : PONG
FLUSHDB = SimpleString : OK

Desafio

Como desafio, tente serializar um tipo de objeto no cache. Aqui estão as etapas básicas.

  1. Crie um novo class com algumas propriedades públicas. É possível inventar um próprio ("Person" ou "Car" são populares) ou utilizar o exemplo "GameStats" da unidade anterior.

  2. Adicionar suporte para o pacote NuGet Newtonsoft.Json utilizando dotnet add package.

  3. Adicionar um using para o namespace Newtonsoft.Json.

  4. Criar um de seus objetos.

  5. Serialize com JsonConvert.SerializeObject e utilize StringSetAsync para efetuar push para o cache.

  6. Recupere-o do cache com StringGetAsync e, em seguida, desserialize-o com JsonConvert.DeserializeObject<T>.

Agora que temos um Cache Redis criado no Azure, vamos criar um aplicativo para usá-lo. Verifique se você tem as informações de conexão do portal Azure.

Observação

O Cloud Shell integrado está disponível no lado direito. É possível usar o prompt de comando para criar e executar o código de exemplo que estamos criando aqui ou executar essas etapas localmente, no caso de uma configuração do ambiente de desenvolvimento do Node.js.

Criar um aplicativo de console

Usaremos um aplicativo de console simples para focarmos na implementação do Redis.

  1. No Cloud Shell, crie um novo diretório chamado redisapp e inicialize um novo aplicativo Node.js nele.

     mkdir redisapp
     cd redisapp
     npm init -y
     touch app.js
    
  2. Nosso aplicativo usará os seguintes pacotes npm:

    • redis: o pacote JavaScript mais usado para se conectar ao Redis.
    • bluebird: usado para converter os métodos do estilo de retorno de chamada no pacote redis em uma Promessa com espera.
    • dotenv: carrega variáveis de ambiente de um arquivo .env, em que as informações de conectividade do Redis serão armazenadas.

    Vamos instalá-las agora. Execute este comando para adicioná-las ao aplicativo:

    npm install redis bluebird dotenv
    

Adicionar configuração

Vamos adicionar as informações de conexão obtidas no portal do Azure a um arquivo de configuração .env.

  1. Crie um arquivo .env para o projeto:

    touch .env
    
  2. Abra o editor de código inserindo code . na pasta do projeto. Caso esteja trabalhando localmente, use o Visual Studio Code. As etapas aqui serão alinhadas principalmente ao uso.

  3. Selecione o arquivo .env no editor e cole o texto a seguir:

    REDISHOSTNAME=
    REDISKEY=
    REDISPORT=
    
  4. Cole o nome do host, a chave primária e a porta após o sinal de igual em cada linha respectiva. O arquivo finalizado terá aparência semelhante à do exemplo a seguir:

    REDISHOSTNAME=myredishost.redis.cache.windows.net
    REDISKEY=K21mLSMN++z8d1FvIeMGy3VOAgoOmqaNYCqeE44eMDc=
    REDISPORT=6380
    
  5. Salve o arquivo com Ctrl+S no Windows e no Linux ou Cmd+S no macOS.

Configurar a implementação

Agora é hora de escrever o código do aplicativo.

  1. Selecione app.js no editor.

  2. Primeiro, adicione as instruções require. Cole o seguinte código na parte superior do arquivo.

    var Promise = require("bluebird");
    var redis = require("redis");
    
  3. Depois, vamos carregar a configuração .env e usar a função promisifyAll do bluebird para converter as funções e os métodos do pacote redis em uma Promessa com espera. Cole o código a seguir:

    require("dotenv").config();
    Promise.promisifyAll(redis);
    
  4. Agora, inicializaremos um cliente Redis. Cole no código de texto clichê da unidade anterior (usando process.env para acessar o nome do host, a porta e a chave) para criar o cliente:

    const client = redis.createClient(
       process.env.REDISPORT,
       process.env.REDISHOSTNAME,
       {
          password: process.env.REDISKEY,
          tls: { servername: process.env.REDISHOSTNAME }
       }
    );
    

Usar o cliente para trabalhar com o cache

Estamos prontos para escrever código para interagir com o cache Redis.

  1. Primeiro, vamos adicionar um wrapper de função async ao fim do arquivo para conter o código principal. Esse wrapper é necessário para executar await nas chamadas de função assíncronas que usaremos. Todo o restante do código que será adicionado nesta unidade estará nesse wrapper.

    (async () => {
    
       // The rest of the code you'll paste in goes here.
    
    })();
    
  2. Adicione um valor ao cache com o método setAsync e leia-o novamente com getAsync:

    console.log("Adding value to the cache");
    await client.setAsync("myKey", "myValue");
    
    console.log("Reading value back:");
    console.log(await client.getAsync("myKey"));
    
  3. Envie um ping para o cache com pingAsync:

    console.log("Pinging the cache");
    console.log(await client.pingAsync());
    
  4. Exclua todas as chaves no cache com flushdbAsync:

    await client.flushdbAsync();
    
  5. Por fim, feche a conexão com quitAsync:

    await client.quitAsync();
    
  6. Salve o arquivo. O aplicativo concluído deve ter esta aparência:

    var Promise = require("bluebird");
    var redis = require("redis");
    
    require("dotenv").config();
    
    Promise.promisifyAll(redis);
    
    const client = redis.createClient(
    process.env.REDISPORT,
    process.env.REDISHOSTNAME,
    {
       password: process.env.REDISKEY,
       tls: { servername: process.env.REDISHOSTNAME }
    }
    );
    
    (async () => {
      console.log("Adding value to the cache");
      await client.setAsync("myKey", "myValue");
      console.log("Reading value back:");
      console.log(await client.getAsync("myKey"));
      console.log("Pinging the cache");
      console.log(await client.pingAsync());
      await client.flushdbAsync();
      await client.quitAsync();
    })();
    
  7. Execute o aplicativo. No Cloud Shell, execute o seguinte comando.

    node app.js
    

    Você verá os seguintes resultados.

    Adding value to the cache
    Reading value back:
    myValue
    Pinging the cache
    PONG