Recentemente eu escrevi um artigo aqui no site falando sobre Serverless Computing. Naquela ocasião falei um pouco sobre o que este modelo propõe, apresentei um exemplo prático para aterrisar a ideia e por fim, olhando para o Azure, falamos sobre as possibilidades (em termos de serviços desta natureza) disponíveis na plataforma. Se você ainda não efetuou a leitura deste texto, recomendo fortemente que a faça (seguindo este link), uma vez que ele serve como pano de fundo o texto de hoje.
Hoje quero aterrisar, de maneira prática, a implementação (ou terceirização, se preferir) de um pequeno pedacinho do Arda para o modelo de serverless computing do Azure – a saber, Azure Functions. O que iremos terceirizar, neste artigo, é o disparo de emails do Arda. Após estas implementações, toda e qualquer mensagem que precisar ser disparada por email para o usuário final do sistema, será feita através de duas functions especializadas, que servem os microserviços do Arda.
Uma observação importante sobre este artigo é: ele não é (e nem pretende ser) uma documentação sobre Azure Functions. A Microsoft já fez um excelente trabalho nesta frente, explicando todos os aspectos técnicos do serviço e que desenvolvedores devem conhecer para escrever seus sistemas de maneira integrada ao serviço serverless. Este artigo apenas irá documentar uma implementação que fizemos aqui no Arda e que utiliza Functions. É claro que, com isso, esperamos que você possa retirar seus insights e algum aprendizado sobre o fluxo de trabalho com o serviço.
A documentação oficial das Functions pode ser encontrada através dos seguintes links:
- Visão geral sobre Functions: https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview
- Criando sua primeira Function: https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-azure-function
- Triggers e Bindings: https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings
- Proxies: https://docs.microsoft.com/en-us/azure/azure-functions/functions-proxies
- Runtime: https://docs.microsoft.com/en-us/azure/azure-functions/functions-runtime-overview
Terceirizando o disparo de emails do Arda
O mecanismo de envio de emails do Arda sempre foi algo que me incomodou bastante. Foi algo que implementamos meio que “a toque de caixa” e como quase tudo que é feito dessa maneira, não ficou legal. Me incomodava porque ele estava entranhado de uma maneira muito ruim na arquitetura atual. Cada microserviço do Arda implementava seu próprio disparo de emails. Isso porque, na época, achamos que não faria sentido (ou que seria demais) ter um microserviço específico para executar esta operação. Mas reconheço, nossa decisão por replicar as rotinas nos microserviços foi ruim e por isso mesmo, esse artigo está ganhando vida.
A ideia aqui é fazer algo que já deveria ter sido feito naquela ocasião, isto é, quando um email precisar ser enviado pela plataforma, ela envia esta mensagem a ser disparada para uma fila. A fila poderia então ser lida e processada por algum mecanismo serverless para que o envio pudesse ser realizado de fato. Pois é exatamente isso que faremos aqui. A Figura 1 apresenta uma visão de arquitetura deste desacoplamento.
Figura 1. Arquitetura do Arda após o desacoplamento da rotina de emails
Se você acompanha este site, com certeza já deve conhecer a porção da arquitetura que não está em cor verde. Ela diz respeito a arquitetura geral do Arda. A novidade está justamente nos elementos de cor verde, que foram adicionados apenas agora e tratam das implementações que desacoplam o mecanismo de disparo de emails. São neles que iremos nos concentrar daqui por diante neste texto.
Como é possível observar, quando um microserviço do Arda precisar enviar uma mensagem por email para o usuário, uma requisição HTTP (Post) será enviada para um Azure Function que terá a responsabilidade de enfileirar esta mensagem utilizando um Azure Storage Queue. Em seguida, de maneira automática, uma outra Azure Function (que fica “ouvindo” a queue) terá a responsabilidade de recuperar a mensagem postada, enviar para o cliente por email (utilizamos SendGrid para isso) e, na sequência, remover a mensagem da queue. Simples e eficiente, certo?
Iniciamos então criando nossa Azure Function App. Como você irá constatar, o Azure Function App atua como um agrupador de functions de mesmo contexto sendo que, o contexto, você é quem irá definir. Como no Arda não teremos muitas functions, criei uma Function App chama “Arda-Function-App”. É importante observar que diferentes functions apps alocam diferentes recursos físicos por debaixo dos panos. Se a criticidade (concorrência por recursos físicos) de determinado conjunto de functions é alta, o ideal é que haja uma maior segmentação das functions em funcions apps, ok? Para criar uma Function App utilizando o portal do Azure, navegue até o menu principal à esquerda e cliente sobre o botão “+ New”. Na busca, procure por “Function App”. Selecione a primeira opção e então clique em “Create”, conforme apresenta a Figura 2.
Figura 2. Criando um novo Azure Function
Na sequência, uma nova tela requisitando informações adicionais para a criação da Function App será apresentada. Preencha todos os dados e finalmente clique em “Create”. Ao fazê-lo, o Azure irá realizar o deployment da Function App e uma tela semelhante aquela apresentada pela Figura 3 será apresentada, indicando que tudo está ok e que suas functions agora já podem ser criadas e executadas.
Figura 3. Function App criada corretamente
Como é possível visualizar no menu à esquerda, existe uma aba chamada “Functions”. Como você já deve estar imaginando, abaixo dela ficarão agrupadas todas as functions deste Function App. Portanto, vamos criar nossa primeira Function, que terá o papel de enfileirar as mensagens recebidas dos microserviços no Azure Queue Storage. Para isso, clique no botão com símbolo “+”, a frente da opção Functions. Ao fazê-lo, uma tela solicitando a seleção do template da function será apresentada. Selecione a opção “A C# function that will be run whenever it receives an HTTP request“. Ao selecionar esta opção, dois campos para serem preenchidos serão apresentados imediatamente abaixo. O primeiro requisita o nome da função enquanto o segundo, o nível de autorização da mesma. A Figura 4 apresenta as configurações da minha função. Adicione suas informações e clique em “Create”.
Figura 4. Criando uma nova function
Antes de seguirmos com os próximos passos, é importante mencionar alguns aspectos importantes desse processo que acabamos de superar. Vamos a eles:
- Ao criar uma function você sempre poderá optar por templates já prontos. Por exemplo, se você deseja adicionar determinado arquivo a um blob storage no Azure, existe um template que realiza esta operação já pronto, para fazer com que você seja mais produtivo. Em nosso caso, estamos criando uma function básica, porque iremos implementar nossa rotina por conta própria.
- Toda function tem um trigger, um tipo de evento que irá disparar a execução da(s) rotinas contidas no interior da function. Ou este trigger será por demanda (quando chegar uma solicitação HTTP), ou será por agendamento via Cron Expressions. Em nosso caso, estamos trabalhando por demanda.
- O nível de autorização refere-se ao esquema de permissionamento para a execução da function. Para a maioria esmagadora dos casos, o nível “Function” irá servir, já que serão chamada internas de sistema. Entretanto, é possível definir um modelo de autenticação por usuário, respeitando o RBAC do Azure. Para isso, uma configuração adicional deve ser realizada no Application Settings da function app.
Quando sua function foi criada, você deve ter sido levado a uma tela com um editor de códigos online e algum código em seu interior. Este código apenas simula uma chamada simples com algum conteúdo no corpo da requisição HTTP e portanto, não nos atende, uma vez que queremos receber uma mensagem através da requisição HTTP e armazená-la de maneira temporária em um Azure Queue Storage. Como quero armazenar o resultado do processamento de minha function em um Azure Queue Storage, e ainda, sabendo que as Functions trazem de maneira nativa integrações com os serviços de fila do Azure, vou então configurar uma operação “output” para minha function, que será justamente a saída para um Azure Queue Storage. Para isso, no menu principal à esquerda abaixo da function criada, clique na opção “Integrate”. Na tela que se abre, no menu superior, clique na opção “New output”, abaixo da coluna “Output”. Na tela que se abre, selecione a opção “Azure Queue Storage” e em seguida, clique em “Select”. Ao fazê-lo, você será apresentado a uma tela semelhante aquela apresentada pela Figura 5.
Figura 5. Configurando o output da Function recém criada
Como é possível observar, o processo de integração com Azure Queue Storage é simples. Precisamos informar apenas o parâmetro de mensagem (através do qual iremos interagir na function com a queue em si), o nome da queue que iremos trabalhar e finalmente, a storage account onde esta queue irá sobreviver. Como você pôde perceber também, em nenhum momento criamos um storage account específico para esta queue. Nós poderíamos, mas para simplificar as coisas, utilizaremos o próprio storage account que já é criado para o Function App. Outra observação importante aqui é: caso o Azure Functions não encontre uma ocorrência de queue com o nome informado (em nosso caso “ardaqueue”) no storage account apontado, ele cuidará de, automaticamente, criar a fila. Ok, agora é só clicar em “Save” e voltar para a tela de edição de código.
Agora que já possuímos nosso output configurado, só nos resta codificar as ações de nossa function. Para isso, substituí o código básico apresentado por ela por aquele apresentado pela Listagem 1.
Listagem 1. Código que adiciona a mensagem na fila
O código é extremamente simples, como você pode observar. Basicamente, ao receber um HttpRequestMessage
, obtenho o conteúdo do body (confiando que o Arda está enviando uma mensagem estruturada), transformo este conteúdo em Json e após isso, adiciono a mensagem na fila (veja a linha outputQueueItem.Add(json);
). Se tudo correu bem, retorno sucesso. Caso contrário, retorno um bad request. Uma observação importante aqui é o objeto “outputQueueItem”. Ele é o elo de ligação do output configurado com nosso código. É através dele que acessamos a fila.
Ok. tudo pronto. Agora podemos testar. Você pode utilizar algum aplicativo HTTP como Fiddler ou Postman ou ainda, a própria console de testes do portal do Azure. No meu caso, para demonstrar o funcionamento, vou utilizar o Postman. Veja o formato do envio do request na Figura 6 e a confirmação de sucesso na mesma imagem, na parte inferior à direita.
Figura 6. Enviando a mensagem para ser enfileirada pela function
Uma pergunta que você pode estar se fazendo: qual é o endpoint da function para que eu possa testar? Você pode facilmente obtê-lo no portal do Azure, na opção “Get function URL”, localizada no canto superior direito, imediatamente acima do editor de códigos da function. Para confirmar se a mensagem foi enfileirada corretamente, vou utilizar o aplicativo Azure Storage Explorer. Como é possível visualizar na Figura 7, a mensagem foi corretamente enfileirada.
Figura 7. Mensagem enfileirada com sucesso
Ok. Primeira parte do problema resolvida. Agora precisamos de uma segunda function que irá ficar “ouvindo” esta fila e que, quando uma nova mensagem chegar, de maneira assíncrona, irá enviar a mensagem por email e na sequência, retirar a mensagem da mesma.
O processo aqui é bem parecido com o que já mostramos até aqui. Isto é:
- Criar uma nova function (dentro do Function App já existente);
- Configurar o trigger de “ouvidoria” da fila existente;
- Configurar o output desta function criada para o envio de email via SendGrid (você já deverá ter uma conta ativa);
- Ajustar o código para o envio da mensagem segundo o SDK da SendGrid;
- Testar o funcionamento;
A nova function que criei neste caso foi chamada de “SendQueuedMessageByEmailToUser”. Após criá-la, utilizei a aba “Integrate” novamente para configurar tanto o trigger (que neste caso é uma nova mensagem na minha fila), quanto o output, que era o envio de um email utilizando a integração já existente com a Send Grid. A Figura 8 apresenta a configuração do trigger, enquanto a Figura 9, apresenta a configuração do output.
Figura 8. Configuração do trigger de monitoramento da fila (apontando para a fila já existente)
Figura 9. Configuração do output para envio com Send Grid (apenas adicionando a API da conta e o parâmetro message)
Pronto. Agora podemos partir para a codificação e o envio da mensagem em si. Para isso, substituí o código padrão trazido pela function no momento de sua criação por aquele apresentado pela Listagem 2.
Listagem 2. Enviando mensagem por email
Novamente temos um código bem simples aqui. Basicamente, recuperamos a mensagem na fila e criamos um objeto tipado para facilitar o trabalho com as informações (EmailClient client = JsonConvert.DeserializeObject(inputQueueItem);
), na sequência formatamos a mensagem de envio (note que estamos utilizando o objeto “message”) e, como já temos o output configurado para ser de maneira automática, é isso. Nem precisamos de um método “Send”. Também por conta da configuração do trigger, o processo de remoção da mensagem da fila é automatizado e ocorre assim que a function completa a execução de envio.
A Figura 10 apresenta o email disparado pelo sistema (aquele mesmo que enviamos quando estávamos enfileirando as mensagens) recém chegado em minha caixa de entrada.
Figura 10. A mensagem enviada corretamente por email
Pronto. Só nos resta agora construir no Arda, o método que irá postar as mensagens na primeira function, isto é, aquela que realiza o enfileiramento das mensagens. Este código foi implementado na classe “Arda.Common.Utils”, que serve algumas rotinas globais para os microserviços e pode ser visualizado através da Listagem 3.
Listagem 3. Postando as mensagens para a Function de enfileiramento
É isso pessoal! Espero que tenham gostado e que este texto possa servir como ponto de partida para que você possa aprofundar seus estudos sobre serverless no Azure, especialmente sobre Functions.
Se você quiser contribuir conosco no projeto Arda, seguem os links dos repositórios no GitHub:
- Arda: https://github.com/DXBrazil/Arda
- Arda Intelligence: https://github.com/DXBrazil/ArdaIntelligence
Forte abraço e até a próxima!
Facebook
Twitter
Instagram
LinkedIn
RSS