Cloud Spanner: crie um placar de jogos com C#

1. Visão geral

O Google Cloud Spanner é um serviço de banco de dados relacional horizontalmente escalonável e distribuído globalmente que fornece transações ACID e semântica SQL sem deixar de oferecer desempenho e alta disponibilidade.

Neste laboratório, você aprenderá a configurar uma instância do Cloud Spanner. Você conhecerá as etapas para criar um banco de dados e um esquema que possam ser usados em um placar de jogos. Primeiro, crie uma tabela de jogadores para armazenar informações sobre o jogador e uma tabela de pontuações para armazenar as pontuações dos jogadores.

Em seguida, preencha as tabelas com dados de amostra. Para concluir o laboratório, execute algumas consultas de exemplo do Top 10 e, por fim, exclua a instância para liberar recursos.

O que você aprenderá

  • Como configurar uma instância do Cloud Spanner.
  • Como criar um banco de dados e tabelas.
  • Como usar uma coluna de carimbo de data/hora de confirmação.
  • Como carregar dados em uma tabela de banco de dados do Cloud Spanner com carimbos de data/hora.
  • Como consultar o banco de dados do Cloud Spanner.
  • Como excluir uma instância do Cloud Spanner.

O que será necessário

Como você usará este tutorial?

Apenas leitura Leitura e exercícios

Como você classificaria sua experiência com o Google Cloud Platform?

Iniciante Intermediário Proficiente

2. Configuração e requisitos

Configuração de ambiente autoguiada

Se você ainda não tem uma Conta do Google (Gmail ou Google Apps), crie uma. Faça login no Console do Google Cloud Platform ( console.cloud.google.com) e crie um novo projeto.

Se você já tiver um projeto, clique no menu suspenso de seleção no canto superior esquerdo do console:

6c9406d9b014760.png

e clique no botão "NEW PROJECT" na caixa de diálogo exibida para criar um novo projeto:

f708315ae07353d0.png

Se você ainda não tiver um projeto, uma caixa de diálogo como esta será exibida para criar seu primeiro:

870a3cbd6541ee86.png

A caixa de diálogo de criação de projeto subsequente permite que você insira os detalhes do novo projeto:

6a92c57d3250a4b3.png

Lembre-se do código do projeto, um nome exclusivo em todos os projetos do Google Cloud. O nome acima já foi escolhido e não servirá para você. Faremos referência a ele mais adiante neste codelab como PROJECT_ID.

Em seguida, será preciso ativar o faturamento no Developers Console para usar os recursos do Google Cloud e ativar a API Cloud Spanner, caso ainda não tenha feito isso.

15d0ef27a8fbab27.png

A execução por meio deste codelab terá um custo baixo, mas poderá ser mais se você decidir usar mais recursos ou se deixá-los em execução. Consulte a seção "limpeza" no final deste documento. Os preços do Google Cloud Spanner estão documentados neste link.

Novos usuários do Google Cloud Platform estão qualificados para uma avaliação gratuita de US$ 300, o que torna este codelab totalmente gratuito.

Configuração do Google Cloud Shell

Embora o Google Cloud e o Spanner possam ser operados remotamente do seu laptop, neste codelab usaremos o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.

O Cloud Shell é uma máquina virtual com base em Debian que contém todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Isso significa que tudo que você precisa para este codelab é um navegador (sim, funciona em um Chromebook).

  1. Para ativar o Cloud Shell no Console do Cloud, basta clicar em Ativar o Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A. Leva apenas alguns instantes para provisionar e se conectar ao ambiente.

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

Screen Shot 2017-06-14 às 10.13.43 PM.png

Depois de se conectar ao Cloud Shell, você já estará autenticado e o projeto estará configurado com seu PROJECT_ID.

gcloud auth list

Resposta ao comando

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Resposta ao comando

[core]
project = <PROJECT_ID>

Se, por algum motivo, o projeto não estiver definido, basta emitir o seguinte comando:

gcloud config set project <PROJECT_ID>

Quer encontrar seu PROJECT_ID? Veja qual ID você usou nas etapas de configuração ou procure-o no painel do Console do Cloud:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

O Cloud Shell também define algumas variáveis de ambiente por padrão, o que pode ser útil ao executar comandos futuros.

echo $GOOGLE_CLOUD_PROJECT

Resposta ao comando

<PROJECT_ID>
  1. Defina a zona padrão e a configuração do projeto:
gcloud config set compute/zone us-central1-f

É possível escolher uma variedade de zonas diferentes. Para mais informações, consulte Regiões e zonas.

Resumo

Nesta etapa, você configurará seu ambiente.

A seguir

Agora configure uma instância do Cloud Spanner.

3. Configurar uma instância do Cloud Spanner

Nesta etapa, configuramos a instância do Cloud Spanner para este codelab. Pesquise a entrada do Spanner 1a6580bd3d3e6783.png no menu de navegação superior esquerdo 3129589f7bc9e5ce.png ou procure o Spanner pressionando "/" e digite "Spanner".

36e52f8df8e13b99.png

Depois, clique em 95269e75bc8c3e4d.png e preencha o formulário inserindo o nome da instância cloudspanner-leaderboard da sua instância, escolhendo uma configuração (selecione uma instância regional) e defina o número de nós para este codelab só precisaremos de um nó. Para instâncias de produção e se qualificar para o SLA do Cloud Spanner, você precisará executar três ou mais nós na sua instância do Cloud Spanner.

Por fim, mas não menos importante, clique em "Criar" e, em segundos, uma instância do Cloud Spanner está à sua disposição.

dceb68e9ed3801e8.png

Na próxima etapa, usaremos a biblioteca de cliente C# para criar um banco de dados e um esquema em nossa nova instância.

4. Criar um banco de dados e um esquema

Nesta etapa, criaremos nosso banco de dados e esquema de amostra.

Vamos usar a biblioteca de cliente C# para criar duas tabelas. uma tabela Jogadores para informações sobre os jogadores e uma tabela Pontuações para armazenar as pontuações dos jogadores. Para isso, vamos percorrer as etapas para criar um aplicativo de console C# no Cloud Shell.

Primeiro, clone o código de amostra do codelab digitando o seguinte comando no Cloud Shell:

git clone https://github.com/GoogleCloudPlatform/dotnet-docs-samples.git

Em seguida, mude para o diretório "applications" onde você criará o aplicativo.

cd dotnet-docs-samples/applications/

Todo o código necessário para este codelab está localizado no diretório dotnet-docs-samples/applications/leaderboard existente como um aplicativo C# executável como Leaderboard para servir como referência enquanto você avança no codelab. Criaremos um novo diretório e criaremos uma cópia do aplicativo Leaderboard em etapas.

Crie um novo diretório chamado "codelab" para o aplicativo e altere o diretório nele para ele usando o seguinte comando:

mkdir codelab && cd $_

Crie um novo aplicativo de console .NET C# chamado "Leaderboard" usando o seguinte comando:

dotnet new console -n Leaderboard

Esse comando cria um aplicativo simples de console que consiste em dois arquivos principais: o arquivo de projeto Leaderboard.csproj e o de programa Program.cs.

Vamos executá-la. Altere o diretório para o diretório recém-criado "Placar" onde o aplicativo reside:

cd Leaderboard

Em seguida, digite o seguinte comando para executá-lo.

dotnet run

A saída do aplicativo será "Hello World!".

Agora, vamos atualizar nosso app do console editando Program.cs para usar a biblioteca de cliente C# Spanner e criar um placar que consiste em duas tabelas "Players" e "Scores". É possível fazer isso diretamente no editor do Cloud Shell:

Abra o editor do Cloud Shell clicando no ícone destacado abaixo:

73cf70e05f653ca.png

Em seguida, abra o arquivo Program.cs no editor do Cloud Shell e substitua o código atual do arquivo pelo código necessário para criar o banco de dados leaderboard e as tabelas Players e Scores colando o seguinte código do aplicativo em C# no arquivo Program.cs:

using System;
using System.Threading.Tasks;
using Google.Cloud.Spanner.Data;
using CommandLine;

namespace GoogleCloudSamples.Leaderboard
{
    [Verb("create", HelpText = "Create a sample Cloud Spanner database "
        + "along with sample 'Players' and 'Scores' tables in your project.")]
    class CreateOptions
    {
        [Value(0, HelpText = "The project ID of the project to use "
            + "when creating Cloud Spanner resources.", Required = true)]
        public string projectId { get; set; }
        [Value(1, HelpText = "The ID of the instance where the sample database "
            + "will be created.", Required = true)]
        public string instanceId { get; set; }
        [Value(2, HelpText = "The ID of the sample database to create.",
            Required = true)]
        public string databaseId { get; set; }
    }

    public class Program
    {
        enum ExitCode : int
        {
            Success = 0,
            InvalidParameter = 1,
        }

        public static object Create(string projectId,
            string instanceId, string databaseId)
        {
            var response =
                CreateAsync(projectId, instanceId, databaseId);
            Console.WriteLine("Waiting for operation to complete...");
            response.Wait();
            Console.WriteLine($"Operation status: {response.Status}");
            Console.WriteLine($"Created sample database {databaseId} on "
                + $"instance {instanceId}");
            return ExitCode.Success;
        }

        public static async Task CreateAsync(
            string projectId, string instanceId, string databaseId)
        {
            // Initialize request connection string for database creation.
            string connectionString =
                $"Data Source=projects/{projectId}/instances/{instanceId}";
            using (var connection = new SpannerConnection(connectionString))
            {
                string createStatement = $"CREATE DATABASE `{databaseId}`";
                string[] createTableStatements = new string[] {
                  // Define create table statement for Players table.
                  @"CREATE TABLE Players(
                    PlayerId INT64 NOT NULL,
                    PlayerName STRING(2048) NOT NULL
                  ) PRIMARY KEY(PlayerId)",
                  // Define create table statement for Scores table.
                  @"CREATE TABLE Scores(
                    PlayerId INT64 NOT NULL,
                    Score INT64 NOT NULL,
                    Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
                  ) PRIMARY KEY(PlayerId, Timestamp),
                      INTERLEAVE IN PARENT Players ON DELETE NO ACTION" };
                // Make the request.
                var cmd = connection.CreateDdlCommand(
                    createStatement, createTableStatements);
                try
                {
                    await cmd.ExecuteNonQueryAsync();
                }
                catch (SpannerException e) when
                    (e.ErrorCode == ErrorCode.AlreadyExists)
                {
                    // OK.
                }
            }
        }

        public static int Main(string[] args)
        {
            var verbMap = new VerbMap<object>();
            verbMap
                .Add((CreateOptions opts) => Create(
                    opts.projectId, opts.instanceId, opts.databaseId))
                .NotParsedFunc = (err) => 1;
            return (int)verbMap.Run(args);
        }
    }
}

Para fornecer uma imagem mais clara do código do Programa, aqui está um diagrama do Programa com seus principais componentes rotulados:

b70b1b988ea3ac8a.png

Use o arquivo Program.cs no diretório dotnet-docs-samples/applications/leaderboard/step4 para ver um exemplo de como seu arquivo Program.cs deve ficar depois de você adicionar o código para ativar o comando create.

Em seguida, use o editor do Cloud Shell para abrir e editar o arquivo de projeto do programa Leaderboard.csproj, atualizando-o para que se pareça com o seguinte código. Salve todas as alterações usando o botão "Arquivo" do Cloud Shell Editor.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Google.Cloud.Spanner.Data" Version="3.3.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\..\commandlineutil\Lib\CommandLineUtil.csproj" />
  </ItemGroup>

</Project>

Essa alteração adicionou uma referência ao pacote C# Spanner Nuget Google.Cloud.Spanner.Data, necessário para interagir com a API Cloud Spanner. Essa mudança também adiciona uma referência ao projeto CommandLineUtil, que faz parte do repositório dotnet-doc-samples do GitHub e fornece um "verbmap" útil. à extensão CommandLineParser de código aberto; uma biblioteca útil para lidar com entradas de linha de comando para aplicativos de console.

Use o arquivo Leaderboard.csproj no diretório dotnet-docs-samples/applications/leaderboard/step4 para ver um exemplo de como seu arquivo Leaderboard.csproj deve ficar depois de você adicionar o código para ativar o comando create.

Agora está tudo pronto para executar a amostra atualizada. Digite o seguinte para ver a resposta padrão do seu aplicativo atualizado:

dotnet run

O resultado será assim:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  No verb selected.

  create     Create a sample Cloud Spanner database along with sample 'Players' and 'Scores' tables in your project.

  help       Display more information on a specific command.

  version    Display version information.

Com base nessa resposta, vemos que esse é o aplicativo Leaderboard, que pode ser executado com um dos três comandos possíveis: create, help e version.

Vamos testar o comando create para criar um banco de dados e tabelas do Spanner. Execute o comando sem argumentos para conferir os argumentos esperados.

dotnet run create

Você verá uma resposta como esta:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  A required value not bound to option name is missing.

  --help          Display this help screen.

  --version       Display version information.

  value pos. 0    Required. The project ID of the project to use when creating Cloud Spanner resources.

  value pos. 1    Required. The ID of the instance where the sample database will be created.

  value pos. 2    Required. The ID of the sample database to create.

Aqui podemos ver que os argumentos esperados do comando create são ID do projeto, ID da instância e ID do banco de dados.

Agora, execute o seguinte comando. Verifique se você substitui PROJECT_ID pelo ID do projeto que você criou no início deste codelab.

dotnet run create PROJECT_ID cloudspanner-leaderboard leaderboard

Após alguns segundos, será exibida uma resposta semelhante a esta:

Waiting for operation to complete...
Operation status: RanToCompletion
Created sample database leaderboard on instance cloudspanner-leaderboard

Na seção Cloud Spanner do Console do Cloud, você verá o novo banco de dados e as tabelas que aparecem no menu do lado esquerdo.

ba9008bb84cb90b0.png

Na próxima etapa, atualizaremos o nosso aplicativo para carregar alguns dados no novo banco de dados.

5. Carregar dados

Agora temos um banco de dados chamado leaderboard contendo duas tabelas; Players e Scores. Agora, vamos usar a biblioteca de cliente C# para preencher nossa tabela Players com jogadores e nossa tabela Scores com pontuações aleatórias para cada jogador.

Abra o editor do Cloud Shell clicando no ícone destacado abaixo:

4d17840699d8e7ce.png

Em seguida, edite o arquivo Program.cs no editor do Cloud Shell para adicionar um comando insert que possa ser usado para inserir 100 jogadores na tabela Players ou para inserir quatro pontuações aleatórias em Scores tabela para cada jogador na tabela Players.

Primeiro, adicione um novo bloco de comando insert no "Verbmap". na parte de cima do Programa, abaixo do bloco de comando create atual:

[Verb("insert", HelpText = "Insert sample 'players' records or 'scores' records "
        + "into the database.")]
    class InsertOptions
    {
        [Value(0, HelpText = "The project ID of the project to use "
            + "when managing Cloud Spanner resources.", Required = true)]
        public string projectId { get; set; }
        [Value(1, HelpText = "The ID of the instance where the sample database resides.",
            Required = true)]
        public string instanceId { get; set; }
        [Value(2, HelpText = "The ID of the database where the sample database resides.",
            Required = true)]
        public string databaseId { get; set; }
        [Value(3, HelpText = "The type of insert to perform, 'players' or 'scores'.",
            Required = true)]
        public string insertType { get; set; }
    }

Em seguida, adicione os seguintes métodos Insert, InsertPlayersAsync e InsertScoresAsync abaixo do método CreateAsync:

        public static object Insert(string projectId,
            string instanceId, string databaseId, string insertType)
        {
            if (insertType.ToLower() == "players")
            {
                var responseTask =
                    InsertPlayersAsync(projectId, instanceId, databaseId);
                Console.WriteLine("Waiting for insert players operation to complete...");
                responseTask.Wait();
                Console.WriteLine($"Operation status: {responseTask.Status}");
            }
            else if (insertType.ToLower() == "scores")
            {
                var responseTask =
                    InsertScoresAsync(projectId, instanceId, databaseId);
                Console.WriteLine("Waiting for insert scores operation to complete...");
                responseTask.Wait();
                Console.WriteLine($"Operation status: {responseTask.Status}");
            }
            else
            {
                Console.WriteLine("Invalid value for 'type of insert'. "
                    + "Specify 'players' or 'scores'.");
                return ExitCode.InvalidParameter;
            }
            Console.WriteLine($"Inserted {insertType} into sample database "
                + $"{databaseId} on instance {instanceId}");
            return ExitCode.Success;
        }

       public static async Task InsertPlayersAsync(string projectId,
            string instanceId, string databaseId)
        {
            string connectionString =
                $"Data Source=projects/{projectId}/instances/{instanceId}"
                + $"/databases/{databaseId}";

            long numberOfPlayers = 0;
            using (var connection = new SpannerConnection(connectionString))
            {
                await connection.OpenAsync();
                await connection.RunWithRetriableTransactionAsync(async (transaction) =>
                {
                    // Execute a SQL statement to get current number of records
                    // in the Players table to use as an incrementing value 
                    // for each PlayerName to be inserted.
                    var cmd = connection.CreateSelectCommand(
                        @"SELECT Count(PlayerId) as PlayerCount FROM Players");
                    numberOfPlayers = await cmd.ExecuteScalarAsync<long>();
                    // Insert 100 player records into the Players table.
                    SpannerBatchCommand cmdBatch = connection.CreateBatchDmlCommand();
                    for (int i = 0; i < 100; i++)
                    {
                        numberOfPlayers++;
                        SpannerCommand cmdInsert = connection.CreateDmlCommand(
                            "INSERT INTO Players "
                            + "(PlayerId, PlayerName) "
                            + "VALUES (@PlayerId, @PlayerName)",
                                new SpannerParameterCollection {
                                    {"PlayerId", SpannerDbType.Int64},
                                    {"PlayerName", SpannerDbType.String}});
                        cmdInsert.Parameters["PlayerId"].Value =
                            Math.Abs(Guid.NewGuid().GetHashCode());
                        cmdInsert.Parameters["PlayerName"].Value =
                            $"Player {numberOfPlayers}";
                        cmdBatch.Add(cmdInsert);
                    }
                    await cmdBatch.ExecuteNonQueryAsync();
                });
            }
            Console.WriteLine("Done inserting player records...");
        }

        public static async Task InsertScoresAsync(
            string projectId, string instanceId, string databaseId)
        {
            string connectionString =
            $"Data Source=projects/{projectId}/instances/{instanceId}"
            + $"/databases/{databaseId}";

            // Insert 4 score records into the Scores table for each player
            // in the Players table.
            using (var connection = new SpannerConnection(connectionString))
            {
                await connection.OpenAsync();
                await connection.RunWithRetriableTransactionAsync(async (transaction) =>
                {
                    Random r = new Random();
                    bool playerRecordsFound = false;
                    SpannerBatchCommand cmdBatch =
                                connection.CreateBatchDmlCommand();
                    var cmdLookup =
                    connection.CreateSelectCommand("SELECT * FROM Players");
                    using (var reader = await cmdLookup.ExecuteReaderAsync())
                    {
                        while (await reader.ReadAsync())
                        {
                            playerRecordsFound = true;
                            for (int i = 0; i < 4; i++)
                            {
                                DateTime randomTimestamp = DateTime.Now
                                        .AddYears(r.Next(-2, 1))
                                        .AddMonths(r.Next(-12, 1))
                                        .AddDays(r.Next(-28, 0))
                                        .AddHours(r.Next(-24, 0))
                                        .AddSeconds(r.Next(-60, 0))
                                        .AddMilliseconds(r.Next(-100000, 0));
                                SpannerCommand cmdInsert =
                                connection.CreateDmlCommand(
                                    "INSERT INTO Scores "
                                    + "(PlayerId, Score, Timestamp) "
                                    + "VALUES (@PlayerId, @Score, @Timestamp)",
                                    new SpannerParameterCollection {
                                        {"PlayerId", SpannerDbType.Int64},
                                        {"Score", SpannerDbType.Int64},
                                        {"Timestamp",
                                            SpannerDbType.Timestamp}});
                                cmdInsert.Parameters["PlayerId"].Value =
                                    reader.GetFieldValue<int>("PlayerId");
                                cmdInsert.Parameters["Score"].Value =
                                    r.Next(1000, 1000001);
                                cmdInsert.Parameters["Timestamp"].Value =
                                    randomTimestamp.ToString("o");
                                cmdBatch.Add(cmdInsert);
                            }
                        }
                        if (!playerRecordsFound)
                        {
                            Console.WriteLine("Parameter 'scores' is invalid "
                            + "since no player records currently exist. First "
                            + "insert players then insert scores.");
                            Environment.Exit((int)ExitCode.InvalidParameter);
                        }
                        else
                        {
                            await cmdBatch.ExecuteNonQueryAsync();
                            Console.WriteLine(
                                "Done inserting score records..."
                            );
                        }
                    }
                });
            }
        }

Em seguida, para que o comando insert funcione, adicione o seguinte código à seção "Main" (Principal) do seu programa :

                .Add((InsertOptions opts) => Insert(
                    opts.projectId, opts.instanceId, opts.databaseId, opts.insertType))

Use o arquivo Program.cs no diretório dotnet-docs-samples/applications/leaderboard/step5 para ver um exemplo de como seu arquivo Program.cs deve ficar depois de você adicionar o código para ativar o comando insert.

Agora, execute o programa para confirmar se o novo comando insert está incluído na lista de comandos possíveis. Execute este comando:

dotnet run

Você verá o comando insert incluído na saída padrão do programa:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  No verb selected.

  create     Create a sample Cloud Spanner database along with sample 'Players' and 'Scores' tables in your project.

  insert     Insert sample 'players' records or 'scores' records into the database.

  help       Display more information on a specific command.

  version    Display version information.

Agora, vamos executar o comando insert para conferir os argumentos de entrada. Digite o comando abaixo.

dotnet run insert

A seguinte resposta será retornada:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  A required value not bound to option name is missing.

  --help          Display this help screen.

  --version       Display version information.

  value pos. 0    Required. The project ID of the project to use when managing Cloud Spanner resources.

  value pos. 1    Required. The ID of the instance where the sample database resides.

  value pos. 2    Required. The ID of the database where the sample database resides.

  value pos. 3    Required. The type of insert to perform, 'players' or 'scores'.

A resposta mostra que, além do ID do projeto, da instância e do banco de dados, há outro argumento value pos. 3 esperado, que é o "tipo de inserção", que devem ser realizadas. Esse argumento pode ter o valor "players" ou "pontuações".

Agora, vamos executar o comando insert com os mesmos valores de argumento que usamos quando chamamos o comando create, adicionando "players" como o argumento adicional "tipo de inserção". Verifique se você substitui PROJECT_ID pelo ID do projeto que você criou no início deste codelab.

dotnet run insert PROJECT_ID cloudspanner-leaderboard leaderboard players

Após alguns segundos, será exibida uma resposta semelhante a esta:

Waiting for insert players operation to complete...
Done inserting player records...
Operation status: RanToCompletion
Inserted players into sample database leaderboard on instance cloudspanner-leaderboard

Agora, vamos usar a biblioteca de cliente C# para preencher nossa tabela Scores com quatro pontuações aleatórias, além de carimbos de data/hora para cada jogador na tabela Players.

A coluna Timestamp da tabela Scores foi definida como uma coluna de "carimbo de data/hora de confirmação" por meio da seguinte instrução SQL que foi executada quando executamos o comando create anteriormente:

CREATE TABLE Scores(
  PlayerId INT64 NOT NULL,
  Score INT64 NOT NULL,
  Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
) PRIMARY KEY(PlayerId, Timestamp),
    INTERLEAVE IN PARENT Players ON DELETE NO ACTION

Observe o atributo OPTIONS(allow_commit_timestamp=true). Isso torna Timestamp uma coluna de "timestamp de confirmação" e permite que ela seja preenchida automaticamente com o carimbo de data/hora exato da transação para operações INSERT e UPDATE em uma determinada linha da tabela.

Também é possível inserir seus próprios valores de carimbo de data/hora em uma coluna de "carimbo de data/hora de confirmação", desde que você insira um carimbo de data/hora com um valor que esteja no passado, que é o que vamos fazer para este fim de codelab.

Agora, vamos executar o comando insert com os mesmos valores de argumento que usamos quando chamamos o comando create adicionando "scores" como o argumento adicional "type of insert". Verifique se você substitui PROJECT_ID pelo ID do projeto que você criou no início deste codelab.

dotnet run insert PROJECT_ID cloudspanner-leaderboard leaderboard scores

Após alguns segundos, será exibida uma resposta semelhante a esta:

Waiting for insert players operation to complete...
Done inserting player records...
Operation status: RanToCompletion
Inserted players into sample database leaderboard on instance cloudspanner-leaderboard

Executar insert com o "tipo de inserção" especificado como scores chama o método InsertScoresAsync, que usa os seguintes snippets de código para inserir um carimbo de data/hora gerado aleatoriamente com uma data e hora no passado:

DateTime randomTimestamp = DateTime.Now
    .AddYears(r.Next(-2, 1))
    .AddMonths(r.Next(-12, 1))
    .AddDays(r.Next(-28, 0))
    .AddHours(r.Next(-24, 0))
    .AddSeconds(r.Next(-60, 0))
    .AddMilliseconds(r.Next(-100000, 0));
...
 cmdInsert.Parameters["Timestamp"].Value = randomTimestamp.ToString("o");

Para preencher automaticamente a coluna Timestamp com o carimbo de data/hora do momento exato em que a opção "Inserir" transação ocorre, é possível inserir a constante C# SpannerParameter.CommitTimestamp, como no snippet de código a seguir:

cmd.Parameters["Timestamp"].Value = SpannerParameter.CommitTimestamp;

Agora que concluímos o carregamento de dados, vamos verificar os valores que acabamos de gravar nas nossas novas tabelas. Primeiro, selecione o banco de dados leaderboard e depois a tabela Players. Clique na guia Data. Você verá dados nas colunas PlayerId e PlayerName da tabela.

7bc2c96293c31c49.png

Em seguida, para verificar se a tabela "Scores" também tem dados, clique na tabela Scores e selecione a guia Data. Você deve ter dados nas colunas PlayerId, Timestamp e Score da tabela.

d8a4ee4f13244c19.png

Muito bem! Vamos atualizar nosso programa para executar algumas consultas que podemos usar para criar um ranking de jogos.

6. Executar consultas de cabeçalho

Agora que configuramos o banco de dados e carregamos as informações nas tabelas, vamos criar um placar usando esses dados. Para fazer isso, precisamos responder a estas quatro perguntas:

  1. Quais jogadores são o "Top 10" de todos os tempos?
  2. Quais jogadores são os "Top 10" do ano?
  3. Quais jogadores são o "Top 10" do mês?
  4. Quais jogadores são os "Top 10" da semana?

Vamos atualizar nosso programa para executar as consultas SQL que responderão a essas perguntas.

Adicionaremos um comando query que fornecerá uma forma de executar as consultas de forma a responder às perguntas que produzirão as informações necessárias para nosso placar.

Edite o arquivo Program.cs no editor do Cloud Shell para atualizar o programa e adicionar um comando query.

Primeiro, adicione um novo bloco de comando query no "Verbmap". na parte de cima do Programa, abaixo do bloco de comando insert atual:

    [Verb("query", HelpText = "Query players with 'Top Ten' scores within a specific timespan "
        + "from sample Cloud Spanner database table.")]
    class QueryOptions
    {
        [Value(0, HelpText = "The project ID of the project to use "
            + "when managing Cloud Spanner resources.", Required = true)]
        public string projectId { get; set; }
        [Value(1, HelpText = "The ID of the instance where the sample data resides.",
            Required = true)]
        public string instanceId { get; set; }
        [Value(2, HelpText = "The ID of the database where the sample data resides.",
            Required = true)]
        public string databaseId { get; set; }
        [Value(3, Default = 0, HelpText = "The timespan in hours that will be used to filter the "
            + "results based on a record's timestamp. The default will return the "
            + "'Top Ten' scores of all time.")]
        public int timespan { get; set; }
    }

Em seguida, adicione os métodos Query e QueryAsync abaixo do método InsertScoresAsync atual:

public static object Query(string projectId,
            string instanceId, string databaseId, int timespan)
        {
            var response = QueryAsync(
                projectId, instanceId, databaseId, timespan);
            response.Wait();
            return ExitCode.Success;
        }        

public static async Task QueryAsync(
            string projectId, string instanceId, string databaseId, int timespan)
        {
            string connectionString =
            $"Data Source=projects/{projectId}/instances/"
            + $"{instanceId}/databases/{databaseId}";
            // Create connection to Cloud Spanner.
            using (var connection = new SpannerConnection(connectionString))
            {
                string sqlCommand;
                if (timespan == 0)
                {
                    // No timespan specified. Query Top Ten scores of all time.
                    sqlCommand =
                        @"SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
                            FROM Players p
                            JOIN Scores s ON p.PlayerId = s.PlayerId
                            ORDER BY s.Score DESC LIMIT 10";
                }
                else
                {
                    // Query Top Ten scores filtered by the timepan specified.
                    sqlCommand =
                        $@"SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
                            FROM Players p
                            JOIN Scores s ON p.PlayerId = s.PlayerId
                            WHERE s.Timestamp >
                            TIMESTAMP_SUB(CURRENT_TIMESTAMP(),
                                INTERVAL {timespan.ToString()} HOUR)
                            ORDER BY s.Score DESC LIMIT 10";
                }
                var cmd = connection.CreateSelectCommand(sqlCommand);
                using (var reader = await cmd.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        Console.WriteLine("PlayerId : "
                          + reader.GetFieldValue<string>("PlayerId")
                          + " PlayerName : "
                          + reader.GetFieldValue<string>("PlayerName")
                          + " Score : "
                          + string.Format("{0:n0}",
                            Int64.Parse(reader.GetFieldValue<string>("Score")))
                          + " Timestamp : "
                          + reader.GetFieldValue<string>("Timestamp").Substring(0, 10));
                    }
                }
            }
        }

Em seguida, para que o comando query funcione, adicione o seguinte código à seção "Main" (Principal) do seu programa :

                .Add((QueryOptions opts) => Query(
                    opts.projectId, opts.instanceId, opts.databaseId, opts.timespan))

Use o arquivo Program.cs no diretório dotnet-docs-samples/applications/leaderboard/step6 para ver um exemplo de como seu arquivo Program.cs deve ficar depois de você adicionar o código para ativar o comando query.

Agora, execute o programa para confirmar se o novo comando query está incluído na lista de comandos possíveis. Execute este comando:

dotnet run

O comando query será incluído na saída padrão do programa como uma nova opção de comando:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  No verb selected.

  create     Create a sample Cloud Spanner database along with sample 'Players' and 'Scores' tables in your project.

  insert     Insert sample 'players' records or 'scores' records into the database.

  query      Query players with 'Top Ten' scores within a specific timespan from sample Cloud Spanner database table.

  help       Display more information on a specific command.

  version    Display version information.

Agora, vamos executar o comando query para conferir os argumentos de entrada. Digite este comando:

dotnet run query

A seguinte resposta será retornada:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  A required value not bound to option name is missing.

  --help          Display this help screen.

  --version       Display version information.

  value pos. 0    Required. The project ID of the project to use when managing Cloud Spanner resources.

  value pos. 1    Required. The ID of the instance where the sample data resides.

  value pos. 2    Required. The ID of the database where the sample data resides.

  value pos. 3    (Default: 0) The timespan in hours that will be used to filter the results based on a record's timestamp. The default will return the 'Top Ten' scores of all time.

A resposta mostra que, além do ID do projeto, da instância e do banco de dados, há outro argumento value pos. 3 esperado, que permite especificar um período em horas a serem usados para filtrar registros com base no valor deles na coluna Timestamp da tabela Scores. Esse argumento tem um valor padrão de 0, o que significa que nenhum registro será filtrado por carimbos de data/hora. Assim, podemos usar o comando query sem um valor de "período" para acessar uma lista dos nossos "Top 10" todos os tempos.

Execute o comando query sem especificar um "timespan", usando os mesmos valores de argumento que usamos quando executamos o comando create. Verifique se você substitui PROJECT_ID pelo ID do projeto que você criou no início deste codelab.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard

Você deverá ver uma resposta que inclui os players "Top 10" de todos os tempos, como a seguinte:

PlayerId : 1843159180 PlayerName : Player 87 Score : 998,955 Timestamp : 2016-03-23
PlayerId : 61891198 PlayerName : Player 19 Score : 998,720 Timestamp : 2016-03-26
PlayerId : 340906298 PlayerName : Player 48 Score : 993,302 Timestamp : 2015-08-27
PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 857460496 PlayerName : Player 68 Score : 988,010 Timestamp : 2015-05-25
PlayerId : 1826646419 PlayerName : Player 91 Score : 984,022 Timestamp : 2016-11-26
PlayerId : 1002199735 PlayerName : Player 35 Score : 982,933 Timestamp : 2015-09-26
PlayerId : 2002563755 PlayerName : Player 23 Score : 979,041 Timestamp : 2016-10-25
PlayerId : 1377548191 PlayerName : Player 2 Score : 978,632 Timestamp : 2016-05-02
PlayerId : 1358098565 PlayerName : Player 65 Score : 973,257 Timestamp : 2016-10-30

Agora, vamos executar o comando query com os argumentos necessários para consultar os "Top 10" do ano especificando um "timespan" igual ao número de horas em um ano que é 8760. Verifique se você substitui PROJECT_ID pelo ID do projeto que você criou no início deste codelab.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 8760

Você deverá ver uma resposta que inclui os "Top 10" do ano, como a seguir:

PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 228469898 PlayerName : Player 82 Score : 967,177 Timestamp : 2018-01-26
PlayerId : 1131343000 PlayerName : Player 26 Score : 944,725 Timestamp : 2017-05-26
PlayerId : 396780730 PlayerName : Player 41 Score : 929,455 Timestamp : 2017-09-26
PlayerId : 61891198 PlayerName : Player 19 Score : 921,251 Timestamp : 2018-05-01
PlayerId : 634269851 PlayerName : Player 54 Score : 909,379 Timestamp : 2017-07-24
PlayerId : 821111159 PlayerName : Player 55 Score : 908,402 Timestamp : 2017-05-25
PlayerId : 228469898 PlayerName : Player 82 Score : 889,040 Timestamp : 2017-12-26
PlayerId : 1408782275 PlayerName : Player 27 Score : 874,124 Timestamp : 2017-09-24
PlayerId : 1002199735 PlayerName : Player 35 Score : 864,758 Timestamp : 2018-04-24

Agora, vamos executar o comando query para consultar os players "Top 10" do mês, especificando um "timespan" igual ao número de horas em um mês que é 730. Verifique se você substitui PROJECT_ID pelo ID do projeto que você criou no início deste codelab.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 730

Você verá uma resposta que inclui os "Top 10" do mês, como a seguinte:

PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 61891198 PlayerName : Player 19 Score : 921,251 Timestamp : 2018-05-01
PlayerId : 1002199735 PlayerName : Player 35 Score : 864,758 Timestamp : 2018-04-24
PlayerId : 1228490432 PlayerName : Player 11 Score : 682,033 Timestamp : 2018-04-26
PlayerId : 648239230 PlayerName : Player 92 Score : 653,895 Timestamp : 2018-05-02
PlayerId : 70762849 PlayerName : Player 77 Score : 598,074 Timestamp : 2018-04-22
PlayerId : 1671215342 PlayerName : Player 62 Score : 506,770 Timestamp : 2018-04-28
PlayerId : 1208850523 PlayerName : Player 21 Score : 216,008 Timestamp : 2018-04-30
PlayerId : 1587692674 PlayerName : Player 63 Score : 188,157 Timestamp : 2018-04-25
PlayerId : 992391797 PlayerName : Player 37 Score : 167,175 Timestamp : 2018-04-30

Agora execute o comando query para consultar os "Top 10" da semana, especificando um "timespan" igual ao número de horas em uma semana de 168. Verifique se você substitui PROJECT_ID pelo ID do projeto que você criou no início deste codelab.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 168

Você verá uma resposta que inclui os jogadores "Top 10" da semana, como a seguinte:

PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 61891198 PlayerName : Player 19 Score : 921,251 Timestamp : 2018-05-01
PlayerId : 228469898 PlayerName : Player 82 Score : 853,602 Timestamp : 2018-04-28
PlayerId : 1131343000 PlayerName : Player 26 Score : 695,318 Timestamp : 2018-04-30
PlayerId : 1228490432 PlayerName : Player 11 Score : 682,033 Timestamp : 2018-04-26
PlayerId : 1408782275 PlayerName : Player 27 Score : 671,827 Timestamp : 2018-04-27
PlayerId : 648239230 PlayerName : Player 92 Score : 653,895 Timestamp : 2018-05-02
PlayerId : 816861444 PlayerName : Player 83 Score : 622,277 Timestamp : 2018-04-27
PlayerId : 162043954 PlayerName : Player 75 Score : 572,634 Timestamp : 2018-05-02
PlayerId : 1671215342 PlayerName : Player 62 Score : 506,770 Timestamp : 2018-04-28

Ótimo trabalho!

Agora, quando você adicionar registros, o Spanner vai escalonar o banco de dados para o tamanho que você precisar.

Não importa o quanto seu banco de dados cresce, o placar do seu jogo pode continuar a escalonar com precisão com o Spanner e a tecnologia Truetime.

7. Limpeza

Depois de se divertir com o Spanner, precisamos limpar nosso playground, economizando recursos preciosos e dinheiro. Felizmente, esta é uma etapa fácil, basta acessar o console para desenvolvedores e excluir a instância que criamos na etapa do codelab chamada "Configurar uma instância do Cloud Spanner".

8. Parabéns!

O que vimos:

  • Instâncias, bancos de dados e esquema de tabelas do Google Cloud Spanner para um placar
  • Como criar um aplicativo do console do .NET Core em C#
  • Como criar um banco de dados e tabelas do Spanner usando a biblioteca de cliente C#
  • Como carregar dados em um banco de dados do Spanner usando a biblioteca de cliente C#
  • Como consultar "Top 10" resultados de seus dados usando carimbos de data/hora de confirmação do Spanner e a biblioteca de cliente C#

Próximas etapas:

Envie um feedback

  • Reserve um momento para completar nossa pesquisa curta