Janeiro de 2018

Volume 33 – Número 1

Pontos de Dados – Criando Funções do Azure Functions para interagir com o Cosmos DB

Por Julie Lerman

Julie LermanNa minha última coluna, eu mostrei o processo para criar um simples aplicativo Plataforma Universal do Windows (UWP) — um jogo chamado CookieBinge — que era basicamente uma desculpa para eu explorar usando o Entity Framework Core 2.0 (EF Core) em um aplicativo de dispositivo associado do Windows 10. A adição de provedores para bancos de dados portáteis como o SQLite completa a figura para tornar isso uma possibilidade.

O aplicativo, em seu estado atual, armazena a pontuação do jogo localmente no dispositivo no qual está sendo jogado, usando o EF Core 2.0 para enviar os dados a um banco de dados SQLite. No último artigo, prometi que a próxima iteração desse jogo permitiria que os usuários compartilhassem os dados com outros jogadores na Internet. Para cumprir essa meta, vou usar dois ótimos recursos do Azure: o Azure Cosmos DB e o Azure Functions.

Um pouco mais sobre o Azure Cosmos DB

O Azure Cosmos DB é a próxima geração daquilo que começou a vida como Azure Document DB — uma tecnologia sobre a qual escrevi diversas vezes nesta coluna. O artigo “Uma visão geral do Microsoft Azure DocumentDB” (msdn.com/magazine/mt147238), de junho de 2015, foi seguido por mais dois artigos, nos quais eu o usei como back-end de um aplicativo Aurelia Web por meio de uma API de servidor Node.js.

O DocumentDB evoluiu para se tornar um banco de dados distribuído globalmente com alguns recursos extraordinários. Além da facilidade com que pode ser distribuído globalmente, seus modelos de consistência têm sido realinhados para que haja mais opções de escolha do que apenas força e eventual. Entre esses dois extremos, agora os usuários também podem escolher entre desatualização limitada, sessão ou prefixo consistente. Há muitos outros recursos importantes para dar suporte ao armazenamento de dados, e o banco de dados de documento agora contém muitos outros modelos de dados — documentos acessíveis por meio de APIs do MongoDB, bancos de dados de gráfico, tabela (par chave/valor) e armazenamento de dados da coluna. Todos esses modelos estão sob a abrangência do Cosmos DB. Na verdade, se você tinha um Azure DocumentDB, ele foi automaticamente alternado para um Azure Cosmos DB, permitindo que seu banco de dados de documento existente se beneficiasse de todos os novos recursos do Cosmos DB. Você pode ler muito mais sobre o Cosmos DB, começando com cosmosdb.com.

E um pouco sobre o Azure Functions

Vou usar o Cosmos DB para armazenar as pontuações do CookieBinge. Porém, em vez de escrever todo o código sozinha e usando as APIs do Document DB, vou aproveitar um outro recurso relativamente novo do Azure: o Azure Functions. O Azure Functions é a oferta de “computação sem servidor” da Microsoft. Eu nunca gostei muito dessa frase porque a computação continua em um servidor...Só não no meu servidor. Mas tendo finalmente a chance de trabalhar com o Azure Functions, agora eu tenho um grande respeito pelo conceito. O Azure Functions permite se concentrar na lógica real que você deseja realizar no aplicativo, enquanto ele se ocupa das preocupações transversais, como implantação, suporte de APIs e conexões a outras funcionalidades, como armazenamento de dados ou envio de emails. Eu não tinha entendido isso completamente até eu mesmo usá-lo, assim, espero que você também tenha seu momento de epifania depois ver o caminho que eu sigo para preparar esse recurso para o aplicativo CookieBinge.

Preparação para criar a primeira função do Azure Function

Há três formas de criar funções do Azure Functions. Uma é com as ferramentas do Visual Studio 2017. Outra é diretamente no portal do Azure. Você também pode usar o Visual Studio Code junto com a interface de linha de comando (CLI) do Azure. Eu decidi começar meu aprendizado com o portal porque ele me guiou durante todas as etapas necessárias. Outro benefício é que, sem a ajuda das ferramentas do Visual Studio 2017, eu fui obrigada a pensar um pouco mais sobre todas as partes móveis. Acredito que minha compreensão foi muito melhor dessa forma. Claro que também há muitos e ótimos recursos para fazer isso no Visual Studio 2017, mas o outro motivo pelo qual gosto de usar o portal é por ele ser baseado na Web e, portanto, ser uma opção de plataforma cruzada. Lembre-se de que, embora você consiga implantar seu código de função e seus ativos a partir do controle do código-fonte no Azure, tudo o que você criar diretamente no portal terá de ser baixado para seu computador (uma tarefa simples) e, de lá, enviado por push para seu repositório.

Se você deseja continuar e ainda não tem uma assinatura do Azure, fico feliz em informar que é possível obter uma assinatura gratuita — e não apenas por um curto período de avaliação. Alguns produtos do Azure estarão disponíveis gratuitamente por um ano, e algumas dezenas permanecerão gratuitas para sempre. Vá até azure.com/free para ver as configurações. Eu estou usando a conta que obtive como parte da minha assinatura do Visual Studio, a qual conta com créditos mensais para experimentar com o Azure.

Antes de criar as funções, é preciso definir as metas. Eu quero que meu aplicativo seja capaz de:

  • Armazenar as pontuações do usuário na Web, mantendo não só algumas informações do usuário e a data junto com a pontuação, mas também o tipo de dispositivo no qual o jogo foi executado.
  • Permitir que os usuários recuperem suas pontuações mais altas em todos os dispositivos nos quais eles jogaram.
  • Permitir que um usuário recupere suas pontuações mais altas em todos os usuários.

Eu não vou sobrecarregar essa lição com questões do tipo criar e autenticar contas, embora isso seja preciso no mundo real, com toda a certeza. Meu objetivo é mostrar o Azure Functions e, por fim, a interação com o aplicativo UWP.

Criando uma função do Azure Functions no portal do Azure

O Azure Functions é um serviço agrupado em um aplicativo de funções que permite que você defina e compartilhe configurações entre seu conjunto de funções. Assim, vou começar criando um novo aplicativo de funções. No portal do Azure, clique em Novo e filtre por “aplicativo de funções” para encontrar essa opção mais facilmente. Na lista de resultados, clique em Aplicativo de funções e depois em Criar. Vai ser solicitado que você preencha alguns metadados, como nome para o aplicativo. Eu pus o nome de cookiebinge.azurewebsites.net. Como é apenas de uma demonstração, vou aceitar o restante dos padrões da página Criar. Para um acesso futuro mais fácil ao seu novo aplicativo de funções, selecione a opção Afixar no Dashboard e depois clique no botão Criar. Demorou apenas cerca de 30 segundos para a implantação do meu aplicativo de funções ser concluída.

Agora você pode adicionar algumas funções no seu aplicativo de funções. Minhas funções serão criadas para dar suporte à lista de metas mencionada anteriormente. O serviço do Azure Functions tem um conjunto de eventos predefinido (e bastante rico) ao qual pode responder, incluindo uma solicitação HTTP, uma alteração no banco de dados do Cosmos DB ou um evento em um blob ou uma fila. Como eu quero chamar essas funções a partir do meu aplicativo UWP, quero funções que respondam a solicitações vindas do HTTP. O portal fornece uma série de modelos em diversas linguagens: Bash, Batch, C#, F#, JavaScript, PHP, PowerShell, Python e TypeScript. Eu vou suar C#.

Para criar a primeira função dentro do aplicativo de funções, clique no sinal de mais ao lado do cabeçalho Funções. Você verá botões para criar funções predefinidas, mas, se rolar para baixo desses botões, encontrará um link para criar uma função personalizada. Escolha essa opção para ver uma grade rolável repleta de opções de modelos, conforme mostrado na Figura 1. A opção HTTP Trigger – C# deve estar no topo da lista, e é essa que você deve escolher.

Uma parte da lista de modelos para criar uma função nova e personalizada do Azure Functions
Figura 1 Uma parte da lista de modelos para criar uma função nova e personalizada do Azure Functions

Dê um nome para a função e depois clique no botão Criar. À minha eu dei o nome de StoreScores.

O portal vai criar uma função com alguns códigos padrão para que você possa ver como ela é estruturada. A função é criada em um arquivo chamado run.csx (ver Figura 2). Você também pode ter uma lógica adicional para arquivos de suporte, mas isso é algo mais avançado em relação ao que se precisa para essa primeira abordagem.

Função lógica padrão para um novo HTTPTrigger
Figura 2 Função lógica padrão para um novo HTTPTrigger

O único método no exemplo é chamado de Executar, que será o chamado pelo Azure em resposta a uma solicitação HTTP a essa função. Ele tem um parâmetro para capturar a solicitação e outro para retransmitir a informação a um log.

No exemplo, você pode ver que a função está procurando dados de entrada que representem um nome, e a função é flexível o suficiente para buscar por ele nos parâmetros de consulta e no corpo da solicitação. Caso o nome não seja encontrado, a função vai retornar Http­ResponseMessage com uma mensagem de erro amigável, caso contrário, vai retornar “Olá, [nome]” na resposta.

Personalizando a função para interagir com o Cosmos DB

A meta da função é armazenar os dados de entrada em um banco de dados do Cosmos DB. É aqui que a mágica começa. Não há necessidade de criar conexões, comandos e outros códigos para realizar essa tarefa. O Azure Functions tem a capacidade de se integrar facilmente a muitos outros produtos do Azure — e o Cosmos DB é um deles.

Na lista de funções, você deve ver sua nova função e três itens abaixo dela. Um desses itens é o Integrate. Selecione-o e você verá a forma exibida parcialmente na Figura 3. Note que ela diz que o gatilho é uma solicitação HTTP e que a saída retorna algo através do HTTP. Como eu quero retornar uma mensagem de sucesso ou falha, realmente quero manter essa saída HTTP. Mas também quero adicionar uma saída que tenha uma coleção do Cosmos DB e seus destinos.

Definindo os pontos de integração das funções
Figura 3 Definindo os pontos de integração das funções

Para isso, clique em Nova Saída, e uma lista de ícones será exibida. Role para baixo até a chamada Azure Cosmos DB, selecione-a e depois role mais para baixo da página e você verá um botão SELECIONAR. Você sabe o que fazer. (Clique nesse botão!)

A tela para configurar essa integração é pré-preenchida com os padrões. O Nome do parâmetro do documento representa o parâmetro que você vai usar no run.csx. Vou deixá-lo com o nome padrão: outputDocument. Depois há os nomes do banco de dados do Cosmos DB e a coleção dentro desse banco de dados, assim como a conexão à conta do Cosmos DB, na qual reside o banco de dados. Você também vai ver uma caixa de seleção para criar um banco de dados para você. Como eu já tenho algumas contas do Cosmos DB criadas, vou usar uma delas, mas eu deixei minha função criar um novo banco de dados chamado CookieBinge com uma coleção chamada Binges nessa conta. A Figura 4 mostra como eu preenchi esse formulário antes de salvar a definição de saída. Pelo fato de eu ter marcado a caixa de seleção para criar o banco de dados e a coleção, eles vão ser criados para mim, mas não quando eu salvar essa saída. Quando essa função tentar armazenar dados no banco de dados pela primeira vez e vir que ele não existe, a função vai criar o banco de dados na hora.

Definindo um Cosmos DB como saída da função
Figura 4 Definindo um Cosmos DB como saída da função

Personalizando o run.csx

Agora é a hora de redefinir o código da função. A nova versão da função espera um objeto JSON transmitido que se alinhe com a classe BingeRequest, a qual é adicionada no arquivo run.csx abaixo do método Executar:

public class BingeRequest{
  public string userId {get;set;}
  public string userName {get;set;}
  public string deviceName {get;set;}
  public DateTime dateTime {get;set;}
  public int score{get;set;}
  public bool worthit {get;set;}
}

Mas essa não é a mesma estrutura dos dados que quero armazenar porque quero capturar mais de uma propriedade — a data e a hora que os dados são registrados no banco de dados. Vou fazer isso usando uma segunda classe, BingeDocument, que herda todas as propriedades do BingeRequest e adiciona mais um registro de propriedade nomeada. O construtor pega uma BingeRequest preenchida e, depois de definir o valor de registro, transfere os valores da BingeRequest a suas próprias propriedades:

public class BingeDocument:BingeRequest
  {     
    public BingeDocument(BingeRequest binge){
    logged=System.DateTime.Now;
    userId=binge.userId;
    userName=binge.userName;
    deviceName=binge.deviceName;
    dateTime=binge.dateTime;
    score=binge.score;
    }
    public DateTime logged{get;set;}
  }

Com esses tipos no lugar, o método Executar pode tirar vantagem deles. A Figura 5 mostra a lista modificada do run.csx, incluindo os espaços reservados das classes BingeRequest e BingeDocument descritas anteriormente.

Vamos analisar o novo método Executar. A assinatura toma uma solicitação e um TraceWriter assim como a assinatura original, mas agora ela também tem um parâmetro de saída assíncrono chamado outputDocument. O resultado do parâmetro de saída é o que será enviado por push para a saída do Cosmos DB que eu defini. Note que o nome se alinha com o nome do parâmetro de saída na configuração da saída na Figura 4. O TraceWriter me permite enviar mensagens para a janela de log abaixo da janela de código. Eu vou tirá-las em algum momento, mas é como nos velhos tempos, sem os IDEs que permitem fazer a depuração. Mas não me entenda mal. A janela de código é maravilhosa para analisar a linguagem na qual você está trabalhando, e ao salvá-la, quaisquer erros do compilador, que são muito detalhados, também saem para a janela de depuração. Ele também faz coisas do tipo inserir uma chave de fechamento quando você digita em uma chave de abertura. Há uma quantidade impressionante de recursos do editor, na verdade. Por exemplo, você pode clicar com o botão direito do mouse em uma janela do editor para ver a longa lista de recursos do editor, da qual você pode obter vantagens.

Figura 5 O novo arquivo run.csx capturando o Binge e o armazenando na saída, Cosmos DB

using System.Net;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req,
  TraceWriter log,    IAsyncCollector<object> outputDocument)
{
  BingeRequest bingeData =  await req.Content.ReadAsAsync<BingeRequest>();
  log.Verbose("Incoming userId:" + bingeData.userId);
  var doc=new BingeDocument(bingeData,log);
  log.Verbose("Outgoing userId:" + doc.userId);
  await outputDocument.AddAsync(doc);
  if (doc.userId !=" " ){
    return req.CreateResponse(HttpStatusCode.OK,$"{doc.userId} was created" );
  }
  else {
    return req.CreateResponse(HttpStatusCode.BadRequest,
      $"The request was incorrectly formatted." );
  }
}
public class BingeRequest{ . . . }
public class BingeDocument { . . . }

A primeira linha de código no Executar lê a solicitação de modo assíncrono e converte seus resultados em um objeto BingeRequest.

Depois, eu crio uma nova instância BingeDocument, transmitindo o objeto que recém criei a partir da solicitação, o que resulta em um objeto BingeDocument totalmente preenchido, junto com a propriedade registrada preenchida.

Então, eu uso TraceWriter para mostrar alguns dados a partir da solicitação nos logs para que, quando eu fizer a depuração, possa ver se o BingeDocument realmente obteve os dados do objeto da solicitação.

Por fim, de modo assíncrono, eu adiciono o BingeDocument que recém criei no outputDocument assíncrono.

É o resultado desse objeto outputDocument que é enviado para o Cosmos DB pela função. Ele é armazenado no DocumentDB como JSON — a função o converte novamente em segundo plano para você. Como eu vinculei tudo com as configurações de integração, eu não tenho de escrever nenhum código para fazer com que isso aconteça.

Quando tudo tiver sido dito e feito, eu retorno uma mensagem via HttpResponse, retransmitindo o sucesso ou a falha da função.

Compilando e testando a função

As funções são compiladas ao serem salvas. Eu vou excluir aleatoriamente algo importante do código para que você possa ver o compilador em ação, que será exibido na janela de log abaixo da janela de código. A Figura 6 mostra a saída do compilador, destacando o caos que eu criei ao excluir uma chave de abertura na linha 13. Mesmo sem um depurador, eu descobri que observar esses erros me ajuda a trabalhar com o código que tive de escrever sem o auxílio do IntelliSense ou outras ferramentas de codificação em meus IDEs. Ao mesmo tempo, eu descobri o quanto dependo dessas ferramentas!

A janela de log exibindo as informações do compilador, incluindo erros
Figura 6 A janela de log exibindo as informações do compilador, incluindo erros

Quando o código é corrigido e o compilador fica satisfeito, o log exibirá a mensagem “Recarregando”, seguida por “Compilação bem-sucedida”.

Agora é o momento de testar a função, e você pode fazer isso na mesma janela em que a está codificando. À direita do editor de códigos há dois painéis com guias. Um exibe a lista de arquivos relacionados à função. Por padrão, há apenas dois, o arquivo run.csx, para o qual estou olhando no momento, e o arquivo function.json, que contém todas as configurações definidas na interface do usuário.  A outra guia é para executar testes. Essa interface do usuário de teste integrado é como um aplicativo mini-Postman ou Fiddler para criar solicitações HTTP com muito menos esforço, uma vez que já está ciente da função a ser testada. Tudo o que você precisa fazer é inserir um objeto JSON para representar a solicitação de entrada. A interface do usuário de teste é padronizada para enviar um HTTP Post, assim, você não precisa alterar essa opção para esse teste. Insira o seguinte JSON na caixa de texto do Corpo da solicitação. O esquema é importante, mas você pode usar os valores que quiser:

{
  "userId": "54321",
  "userName": "Julie",
  "deviceName" : "XBox",
  "dateTime": "2017-10-25 15:26:00",
  "score" : "5",
  "worthit" : "true",
  "logged": ""
}

Depois, clique no botão Executar no painel Teste. O teste vai chamar a função, transmitindo o corpo da solicitação e, depois, exibe quaisquer resultados HTTP na janela Saída. Na Figura 7, você pode ver a saída “54321 foi criado,” assim como a saída de log da função na janela Logs.

O painel Teste depois de executar um teste na função
Figura 7 O painel Teste depois de executar um teste na função

Exibindo os novos dados no banco de dados do Cosmos DB

O que você não consegue ver aqui é que, como resultado do primeiro teste bem-sucedido, foi criado o banco de dados do Cosmos DB do CookieBinge e, nele, a coleção do Binge na qual esse documento foi armazenado. Vamos dar uma olhada nisso antes de encapsular a parte da minha coluna de várias partes.

Você pode fazer isso abrindo antes a conta do Cosmos DB no portal em que você criou esse banco de dados. A minha se chama datapointscosmosdb, assim, eu vou até Todos os Recursos e digito datapoints no filtro para encontrá-la. Assim que eu abro a conta, posso ver todas as coleções e os bancos de dados lá, embora a única que seja minha seja a coleção Binges no banco de dados CookieBinge, conforme mostrado na Figura 8. Foi isso que acabou de ser criado pela função.

A coleção Binges listada na conta datapointscosmosdb
Figura 8 A coleção Binges listada na conta datapointscosmosdb

Clique em Binges para abrir o Data Explorer dessa coleção. Eu executei o teste duas vezes, então, é possível ver, na Figura 9, que dois documentos foram armazenados na coleção. As primeiras sete propriedades do documento são as que eu defini. O restante são metadados que o Cosmos DB e as APIs relevantes usam para tarefas como indexar, buscar, particionar e outras mais.

Observando os documentos armazenados no Cosmos DB no portal
Figura 9 Observando os documentos armazenados no Cosmos DB no portal

Olhando para o futuro

Se você olhar de novo a Figura 7, verá que há um link Obter URL da Função acima da janela de código. Essa URL é o que vou usar no aplicativo CookieBinge para enviar dados para a nuvem.

Agora que você viu como criar a função e conectá-la ao Cosmos DB, minha próxima coluna mostrará como criar mais duas funções para recuperar diferentes exibições dos dados. A parte final mostrará como chamar as funções do aplicativo CookieBinge e exibir seus resultados.


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 ao seguinte especialista técnico da Microsoft pela revisão deste artigo: Jeff Hollan


Discuta esse artigo no fórum do MSDN Magazine