Fevereiro de 2018

Volume 33 – Número 2

Pontos de Dados - Como criar um Azure Functions que pode ler a partir do Cosmos DB quase sem código

Por Julie Lerman

Julie LermanNessas primeiras colunas, eu mostrei como usar o Entity Framework Core 2 (EF Core 2) para armazenar as pontuações dos jogos localmente no dispositivo Windows 10 onde eles são jogados. Na segunda coluna, eu expliquei como criar um Azure Functions que pega as pontuações do jogo enviadas por HTTP e armazena-as em um banco de dados Cosmos DB. O objetivo principal é modificar o jogo para poder enviar suas pontuações para essa Função, assim como recuperar os dados de pontuação de funções paralelas.

Nesta coluna, mostrarei como criar o Azure Functions, que responderá a solicitações para recuperar dois conjuntos de dados de pontuação: as cinco maiores pontuações para um usuário em todos os dispositivos do usuário e as cinco maiores pontuações de todos os usuários jogando CookieBinge em todo o mundo. Na próxima vez, na coluna final da série, integrarei o novo Azure Functions no aplicativo UWP do CookieBinge para permitir que usuários compartilhem e comparem suas pontuações.

O Azure Function I criado no artigo anterior responde a uma chamada HTTP e, usando as integrações da Function, consegue efetuar push dos dados a partir da solicitação HTTP no banco de dados do Cosmos DB sem eu ter que escrever qualquer código de acesso de dados. A Figura 1 exibe um dos documentos armazenados na coleção Binges do banco de dados CookieBinge. As primeiras sete propriedades são as que eu armazenei (“logged” por meio de “worthit”) e o resto são metadados criados pelo Cosmos DB quando o registro foi inserido.

Um registro da coleção de Binges CookieBinge
Figura 1 Um registro da coleção de Binges CookieBinge

Para as duas novas funções, aproveitarei novamente as integrações do Azure Function para interagir com o mesmo banco de dados do Cosmos DB. Dessa vez, o único código necessário para a interação com o banco de dados serão alguma SQL que representa a cadeia de caracteres de consulta necessária, que é compilada nas integrações, e não no código da função. As duas funções serão acionadas pelas chamadas HTTP. A primeira usará a ID do usuário que está fazendo a solicitação e, em seguida, retornará as cinco maiores pontuações que o usuário registrou em todos os dispositivos nos quais ele jogou o jogo. A segunda não usará nenhuma entrada; ela simplesmente retornará as cinco maiores pontuações de todos os jogadores em todo o mundo.

Explicarei como construir a primeira dessas funções usando o fluxo de trabalho do portal do Azure, em suplemento às lições da coluna anterior. Percorrer as primeiras dessas funções deve ajudar a entender como todas as peças do quebra-cabeça se encaixam. Em seguida, mostrarei um atalho quando for o momento de compilar a segunda. Lembre-se de que também é possível criar o Azure Functions com as ferramentas compiladas no Visual Studio 2017, assim como usando a interface de linha de comando das Funções, mais o Visual Studio Code.

Criando uma Função para retornar uma lista das principais pontuações do usuário

Voltemos agora ao aplicativo CookieBinge do Azure Functions no portal do Azure e vamos adicionar uma nova Função ao aplicativo. Lembre-se de que ao passar o ponteiro do mouse sobre o cabeçalho do grupo Functions no nome do aplicativo da função, você verá um sinal de mais que pode ser clicado. Quando fizer isso, você será solicitado a escolher um modelo para a nova função e, como com a função que você criou, será um gatilho HTTP C#. Nomeie a nova função GetUserScores, altere o nível de autorização para Anônimo e clique em Criar.

Antes de modificar o código no arquivo run.csx, compilarei as integrações da função. Lembre-se de que existem três tipos de integrações:

  • Trigger: Somente poderá haver um único gatilho e, neste caso, é a solicitação HTTP para a Função.
  • Integrações de entrada: Poderão existir várias integrações de entrada, que representam outros dados que você deseja disponibilizar para a Função.
  • Integrações de saída: No artigo anterior, eu liguei uma integração de saída ao banco de dados Cosmos DB do CookieBinge, o que gerou os resultados da Função a ser armazenada no banco de dados. Com esta nova função GetUserScores, a saída serão os resultados da Função enviada de volta para o solicitador como uma resposta HTTP.

Eu ligarei a integração de entrada aos dados de recuperação do banco de dados Cosmos DB, no qual a Função StoreScores do artigo do último mês foi armazenada. Essa entrada era um conceito confuso para mim no início. Eu presumi que teria de escrever algum código na Função para conectá-la a esse banco de dados e consultar as pontuações dos usuários. Mas a integração de entrada é muito legal. Eu posso defini-la com a SQL relevante para consultar o banco de dados Cosmos DB e permitir que ela saiba usar o valor UserId que foi enviado com a solicitação HTTP. E o Azure Functions toma conta do resto, conectando-se ao banco de dados, executando a solicitação e fornecendo-me os resultados da consulta com a qual trabalharei no código da função. Sim, isso é mesmo muito legal! Vamos fazer isso.

Criando uma integração de entrada para ler a partir do Cosmos DB usando os dados de solicitação HTTP

Comece clicando em Nova Entrada no painel Integrações, em seguida, na lista de tipos de entrada apresentados, selecione Cosmos DB e clique no botão Selecionar embaixo dele. Isso gera o formulário de entrada do Cosmos DB e seus campos preenchidos com valores padrão. Modifique os valores conforme mostrado na Figura 2.

Configurações de integração de entrada do Azure Cosmos DB
Figura 2 Configurações de integração de entrada do Azure Cosmos DB

Os valores do nome do banco de dados, o nome da coleção e a conexão da conta do Azure Cosmos DB devem corresponder às que você usou para criar a integração de saída da função StoreScores. No meu caso, é CookieBinge, Binges e datapointscosmosdb_DOCUMENTDB. Eu nomeei meu parâmetro “documentos”, que usarei no meu código da função. A ID do documento é deixada em branco. Se você estivesse compilando uma função para recuperar um único documento do Cosmos DB pela sua ID, isso seria uma opção útil. Porém, essa função faria consultas com base em uma UserId recebida.

A consulta SQL é interrompida na captura de tela, por isso, aqui está ela inteira. Lembre-se que você precisa adicioná-la como uma única linha, embora aqui eu tenha usado retornos de linha para facilitar a leitura:

SELECT  TOP 5 c.score,c.worthit,c.deviceName,c.dateTime
FROM c
WHERE c.userId={userId}
ORDER by c.score DESC

O Azure Cosmos DB tem uma sintaxe de consulta SQL que faz com que trabalhar com ele seja muito familiar.

Observe o espaço reservado da variável userID, {userId}. Usarei esta variável para ligar o gatilho e esta integração de entrada. Primeiro, preciso salvar as configurações desta entrada. Em seguida, precisarei criar um parâmetro de associação para associar o gatilho à entrada. Isso pode ser feito de uma forma que, no início, não foi imediatamente óbvia para mim. Na verdade, eu tinha configurado antes corretamente, e continuava recebendo o erro dizendo que o parâmetro de associação estava ausente e, até conseguir acertar, passei um bom tempo reunindo tudo o que eu estava aprendendo nos documentos, nos resultados de pesquisa na Web e com algumas experiências. Parte do problema era minha falta de conhecimento, porém, quando eu encontrei a solução, as conexões começaram a fazer sentido para mim. Felizmente, você encontrará a solução mais rapidamente porque eu vou deixá-la aqui de mão beijada para você!

Selecione o gatilho HTTP (req) e especifique um modelo de rota para o gatilho. Primeiro, o modelo deve fornecer um nome para a rota e, em seguida, especificar a variável de associação, userId.

A rota não tem que corresponder ao nome da função, mas é mais claro se for assim. Eu nomeei o meu GetUserScores/{userId}, como mostrado na Figura 3. Não se esqueça de salvar suas alterações.

Configurações de integração do gatilho HTTP
Figura 3 Configurações de integração do gatilho HTTP

A integração de saída pode permanecer como o padrão, que é uma resposta HTTP.

Agora é o momento de retornar ao código da função, ação que pode ser feita clicando na Função GetUserScores. Isso exibirá o código run.csx, que ainda está em seu estado padrão.

Escrevendo o código da Função para dar resposta às integrações

Considere o SQL para a entrada da integração, que recupera quatro colunas: score, worthit, deviceName e dateTime. Você poderia criar uma classe correspondendo a cada tipo (como eu fiz na Função StoreScores do artigo anterior) ou apenas dizendo a função por onde a entrada Function passará em um tipo dinâmico. É isso que o meu código faz. A Figura 4 mostra toda a função no arquivo run.csx.

Figura 4 O método de execução da Função GetUserScores

using System.Net;
public static HttpResponseMessage Run(string userId,
               HttpRequestMessage req, IEnumerable<dynamic> documents, 
               TraceWriter log)
{
  if (documents != null)
  {
    log.Info($"Document Count: {documents.Count()}");
    return req.CreateResponse(HttpStatusCode.OK,documents);
  }
  else
  {
    return req.CreateResponse(HttpStatusCode.NotFound);
  }
}

Em sua assinatura, o método Run reconhece que o primeiro parâmetro é uma cadeia de caracteres chamada userId que vem pela rota especificada nas configurações do gatilho. Depois do userId, os parâmetros esperam o HttpRequestMessage (req) e, em seguida, o IEnumerable contendo os documentos. Nos bastidores, a Função usará a userId que entrou pela rota do gatilho e a passará para a integração de entrada, que executará a consulta (usando a userId) e preencherá as variáveis dos documentos com os resultados da consulta. Depois disso, a Função finalmente chamará a lógica do método de execução que eu escrevi.

Primeiro, o código verifica se os documentos foram, de fato, transferidos para o método. Eu extraí uma mensagem do meu log com a contagem desses documentos e, depois, simplesmente os transferi de volta para a resposta HTTP. Se eu não estivesse fazendo nenhuma verificação e comprovação, o método ainda funcionaria com este formato mais simples:

public static HttpResponseMessage Run(string userId,
              HttpRequestMessage req, IEnumerable<dynamic> documents)
{
  return req.CreateResponse(documents);
}

Em outras palavras, para que toda essa Função receba uma userID e retorne aquelas principais pontuações do usuário a partir do banco de dados, basta uma única linha de código que nada mais é que “enviar de volta os documentos que vieram da integração de entrada”.

As integrações do Azure Functions realmente fizeram todo o trabalho por mim. Eu estou realmente impressionada.

Eu posso testar essa Função no portal ou em uma ferramente como o Fiddler ou o Postman. Testar de um navegador não funcionará com a Função em seu estado atual porque a Função está retornando objetos dinâmicos e um navegador não pode enviar o cabeçalho Accept. Em alternativa, você poderá definir uma classe que coincide com os resultados da consulta e usar essa classe em vez de um objeto dinâmico. Como assim como está, a Função não funcionará corretamente ao fazer chamadas a partir do Fiddler, Postman ou suas APIs, tal como a que você acabará usando no aplicativo UWP.

O link da URL da Função Get me diz que eu posso chamar https://cookie­binge.azurewebsites.net/api/Get­UserScores/{userId}. Se a Função não fosse anônima, eu teria que passar algumas credenciais, mas estou mantendo tudo simples aqui de propósito.

Com minha userId, 54321, no espaço reservado, a URL se parece com isto: https://cookiebinge.azurewebsites.net/api/GetUserScores/54321.

Esta URL retorna cinco documentos formatados JSON com o esquema moldado pela consulta. Aqui está um dos documentos retornados na resposta HTTP, mostrando que quando eu joguei com meu Xbox, eu devorei 18 bolachas, mas não fiquei assim tão feliz com o meu desempenho nesse dia.

{
  "score": 18,
  "worthit": false,
  "deviceName": "XBox",
  "dateTime": "2017-11-05T15:26:00"
}

Um atalho para criar outra Função do Azure totalmente integrada

Agora que eu tenho a primeira Função configurada, é hora de criar a segunda, que recuperará as cinco maiores pontuações de todos os jogadores em todo o mundo. Porém, em vez de percorrer todos os formulários de configurações novamente, vou pegar um atalho. As configurações de todas as integrações de uma Azure Function são armazenadas no arquivo function.json, que você pode abrir no portal no painel Exibir Arquivos. A Figura 5 mostra o arquivo function.json da Função GetUserScores. A seção de associações encapsula todas as configurações de integração.

Figura 5 O arquivo function.json de GetUserScores

{
"bindings": [
  {
    "authLevel": "anonymous",
    "name": "req",
    "type": "httpTrigger",
    "direction": "in",
    "route": "GetUserScores/{userId}"
  },
  {
    "name": "$return",
    "type": "http",
    "direction": "out"
  },
  {
    "type": "documentDB",
    "name": "documents",
    "databaseName": "CookieBinge",
    "collectionName": "Binges",
    "sqlQuery": "SELECT  TOP {top} c.score,c.worthit,c.deviceName,
      c.dateTime FROM c WHERE c.userId={userId} ORDER by c.score DESC",
    "connection": "datapointscosmosdb_DOCUMENTDB",
    "direction": "in"
  }
],
"disabled": false
}

A primeira associação é httpTrigger, anotada em sua propriedade de tipo. Observe que todo o resto de suas configurações são descritas pelas outras propriedades: authLevel, nome, direção e rota. Em seguida, você pode ver a associação de saída http e, por fim, a associação de entrada com todas as configurações eu especifiquei no formulário.

Agora que você conhece melhor todas as peças do quebra-cabeça da Função, você não precisa realmente percorrer todos os formulários se não quiser. Basta você criar o arquivo function.json diretamente e é isso o que eu vou fazer para a segunda Função. Você ainda precisa adicionar a nova função ao aplicativo da Função, então, faça isso usando um modelo HttpTrigger em C# e chame-o GetGlobalScores.

Porém, dessa vez, em vez de ir até a seção Integração, abra o arquivo function.json no painel Exibir Arquivos e substitua-o pelo código na Figura 6. Observe que o valor de sqlQuery está encapsulado qui na listagem, mas essa cadeia de caracteres deve estar em uma linha.

Figura 6 O arquivo function.json de GetUserScores

{
  "bindings": [
    {
      "authLevel": "anonymous",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in"
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    },
    {
      "type": "documentDB",
      "name": "documents",
      "databaseName": "CookieBinge",
      "collectionName": "Binges",
      "connection": "datapointscosmosdb_DOCUMENTDB",
      "direction": "in",
      "sqlQuery": "SELECT  TOP 5 c.userName, c.score,c.worthit,c.deviceName,
        c.dateTime FROM c ORDER by c.score DESC"
    }
  ],
  "disabled": false
}

Como essa solicitação não precisa passar em uma id, não há uma rota na associação do httpTrigger como na primeira Função. A associação de saída é a mesma que a anterior. A única diferença com a associação de entrada é que existe uma consulta diferente que não é filtrada em uma userId.

Entender e aplicar as associações em seu formato bruto pode certamente economizar muito o seu tempo. Seguir o caminho explícito de primeiro usar os formulários para inserir as configurações foi vantajoso, no entanto, também me ajudou a entender as configurações conforme ia aprendendo sobre elas. Na verdade, agora que você sabe como a configuração é armazenada, já está preparado para compilar seu Azure Functions no Visual Studio ou no Visual Studio Code, em vez de fazer isso no portal.

Agora para o código da Função, que é praticamente idêntico ao código da primeira Função, exceto pelo fato de não usar a associação userId como seu primeiro parâmetro. Aqui está a assinatura modificada do método Run:

public static HttpResponseMessage Run(
              HttpRequestMessage req, IEnumerable<dynamic> documents, TraceWriter log)

Eu modifiquei manualmente alguns dos dados de teste na minha coleção Binges do Cosmos DB para ter certeza que havia uma quantidade de valores UserId diferentes antes de executar a nova Função GetGlobalUserScores para confirmar que tudo está funcionando corretamente.

Vinculando o aplicativo UWP com o Azure Functions

Com as três funções do Azure Functions e o banco de dados de documentação Cosmos DB para armazenar e recuperar as pontuações do usuário em curso, a parte final desta série retornará ao aplicativo UWP para integrar essas Funções. Lembre-se de que o aplicativo UWP atualmente usa o Entity Framework Core 2 para armazenar as pontuações do usuário localmente sobre qualquer dispositivo do Windows 10 no qual o jogo está sendo jogado. Na próxima iteração, além do armazenamento local, as pontuações do usuário (com sua devida permissão) serão enviadas para a Função StoreScores do Azure para serem armazenadas na nuvem. E os usuários poderão recuperar uma lista de suas pontuações mais altas em todos os dispositivos nos quais eles jogaram, assim como ver as maiores pontuações de todos os jogadores em todo o mundo. O aplicativo chamará as Funções criadas aqui para relatar esses dados ao usuário.

Observe que eu provavelmente desligarei minha própria Função de demonstração nas URLs anotadas anteriormente neste artigo. Elas estão hospedadas na conta de assinatura do Visual Studio, que é usada para fins de teste e tem um limite de gastos.


Julie Lerman é Diretora Regional da Microsoft, MVP da Microsoft, coach e consultora de equipes de software e reside nas colinas de Vermont. Você pode encontrá-la em apresentações sobre acesso de dados ou sobre outros tópicos em grupos de usuários e conferências em todo o mundo. Ela escreve no blog thedatafarm.com/blog e é autora do “Programming Entity Framework”, bem como de uma edição do Code First e do DbContext, todos da O'Reilly Media. Siga-a no Twitter em @julielerman e confira seus cursos da Pluralsight em juliel.me/PS-Videos.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Jeff Hollan
Jeff Hollan é Gerente de Programa Sênior do Microsoft Azure Functions.  Ele entrou na Microsoft há pouco mais de ano anos e tem passado seu tempo gerenciando sistemas e integrações de TI de back-end, assim como ajudando a gerenciar outros produtos no Azure incluindo os Aplicativos Lógicos. Jeff é apaixonado por tudo o que diz respeito a tecnologia e é conhecido por suas apresentações em conferências ao redor do mundo. Você pode seguir Jeff no Twitter @jeffhollan


Discuta esse artigo no fórum do MSDN Magazine