AWS Serverless: Padrões de Micro-Serviços Serverless

Um guia de 19 padrões mostrando o poder do AWS Serverless

Image for post
Image for post

Sou um grande fã de construir micro-serviços com sistemas serverless. O serverless nos dá o poder de nos concentrar apenas no código e nos nossos dados sem nos preocuparmos com a manutenção e configuração dos recursos de computação abaixo disso. Os provedores de serviços na nuvem (como a AWS) também nos fornecem um grande número de serviços gerenciados que podemos juntar para criar micro-serviços serverless incrivelmente poderosos e massivamente escaláveis.

Eu li muitos posts que mencionam micro-serviços serverless, mas eles geralmente não entram em muitos detalhes. Eu sinto que isso pode deixar as pessoas confusas e dificultar a implementação de suas próprias soluções. Como trabalho com micro-serviços serverless o tempo todo, imaginei que compilar uma lista de padrões de design e como implementá-los na AWS, seria algo interessante para a comunidade. Abaixo descrevo 19 deles, embora tenha certeza de que há muito mais!

Nesta postagem, veremos todos os 19 em detalhes, para que você possa usá-los como modelos para começar a projetar seus próprios micro-serviços serverless.

Table de Conteúdo

  • Padrão 02: Webhook Escalável
  • Padrão 03: O Porteiro
  • Padrão 04: A API Interna
  • Padrão 05: A Entrega Interna
  • Padrão 06: O Agregador
  • Padrão 07: O Notificador
  • Padrão 08: O FIFOer
  • Padrão 09: O “Eles dizem que eu sou um streamer” ou Fluxo Contínuo
  • Padrão 10: O Estrangulador
  • Padrão 11: A Máquina de Estado
  • Padrão 12: O Roteador
  • Padrão 13: A API Robusta
  • Padrão 14: O Consumidor Simples
  • Padrão 15: O Mecanismo de Relatórios de Leitura Pesada
  • Padrão 16: O Fan-Out/Fan-In
  • Padrão 17: O Eventualmente Consistente
  • Padrão 18: A Invocação Distribuída
  • Padrão 19: O Disjuntor

Uma palavra rápida sobre comunicação entre micro-serviços

Comunicação Síncrona

Comunicação Assíncrona

Ótimo! Agora que estamos claros sobre isso, vamos direto aos padrões!

Padrões de Micro-Serviço Serverless

Para maior clareza e consistência, os diagramas de todos abaixo usam os mesmos símbolos para representar a comunicação entre os componentes:

  • Uma grande seta preta representa uma solicitação assíncrona.
  • As duas setas pretas menores representam uma solicitação síncrona.
  • Setas vermelhas indicam erros.

Faça bom proveito! 😁

01: O Serviço Web Simples

Padrão 01: O Serviço Web Simples
Padrão 01: O Serviço Web Simples

Padrão 01: O Serviço Web Simples

02: Webhook Escalável

Padrão 02: Webhook Escalável
Padrão 02: Webhook Escalável

Padrão 02: Webhook Escalável

03: O Porteiro

Padrão 03: Autorizador/Chave de Valet
Padrão 03: Autorizador/Chave de Valet

Padrão 03: Autorizador/Chave de Valet

04: A API Interna

Padrão 04: A API Interna
Padrão 04: A API Interna

Padrão 04: A API Interna

05: A Entrega Interna

1 — É possível que o evento seja processado mais de uma vez, então devemos ter certeza de que nossos eventos são idempotentes. 2 — Já que nos desconectamos de nossa função de chamada, precisamos ter certeza de capturar falhas para que possamos analisá-las e potencialmente reproduzi-las.

Anexar uma Fila de Devoluções Mortas (Dead Letter Queue) (DLQ) a funções Lambda assíncronas é sempre uma boa ideia. Eu gosto de usar uma fila SQS e monitorar o tamanho da fila com o CloudWatch Metrics. Dessa forma, posso obter um alerta se a fila de falhas atingir um determinado limite.

Padrão 05: A Entrega Interna
Padrão 05: A Entrega Interna

Padrão 05: A Entrega Interna

06: O Agregador

Padrão 06: O Agregador
Padrão 06: O Agregador

Padrão 06: O Agregador

07: O Notificador

Padrão 07: O Notificador
Padrão 07: O Notificador

Padrão 07: O Notificador

08: O FIFOer

Depois de concluirmos nosso processamento, a função remove as mensagens da fila e, em seguida, invoca a si mesma novamente (de forma assíncrona) usando o SDK da AWS para acessar a API HTTP do Lambda com o tipo de chamada Event. Esse processo será repetido até que todos os itens tenham sido removidos da fila. Sim, isso tem um efeito cascata, e eu não sou um grande fã de usar isso para qualquer outra finalidade, mas funciona muito bem nesse cenário. Se a função Lambda estiver ocupada processando um conjunto de mensagens, a regra do CloudWatch falhará devido à configuração de simultaneidade do Lambda. Se a auto-invocação for bloqueada por qualquer motivo, a nova tentativa continuará a cascata. Na pior das hipóteses, o processamento é interrompido e, em seguida, é iniciado novamente pela regra CloudWatch.

Se a mensagem estiver com defeito ou causar um erro de processamento, lembre-se de colocá-las em uma fila de devoluções (DLQ) para uma inspeção mais detalhada com a capacidade de reproduzi-las.

Padrão 08: O FIFOer
Padrão 08: O FIFOer

Padrão 08: O FIFOer

09: O “Eles dizem que eu sou um streamer” ou Fluxo Contínuo

Padrão 09: O "Eles dizem que eu sou um streamer" ou Fluxo Contínuo
Padrão 09: O "Eles dizem que eu sou um streamer" ou Fluxo Contínuo

Padrão 09: O “Eles dizem que eu sou um streamer” ou Fluxo Contínuo

10: O Estrangulador

Padrão 10: O Estrangulador
Padrão 10: O Estrangulador

Padrão 10: O Estrangulador

11: A Máquina de Estado

A AWS defende o uso de Step Functions para orquestrar fluxos de trabalho inteiros, ou seja, coordenar vários micro-serviços. Eu acho que isso funciona para certos padrões assíncronos, mas definitivamente não funcionará para serviços que precisam fornecer uma resposta síncrona aos clientes. Pessoalmente, gosto de encapsular funções de etapa dentro de um micro-serviços, reduzindo a complexidade do código e adicionando resiliência, mas ainda mantendo meus serviços desacoplados.

Padrão 11: A Máquina de Estado
Padrão 11: A Máquina de Estado

Padrão 11: A Máquina de Estado

12: O Roteador

No exemplo abaixo, uma chamada assíncrona para uma função Lambda está determinando qual tipo de tarefa deve ser usado para processar a solicitação. Esse é essencialmente uma declaração switch glorificada, mas também poderia acrescentar algum contexto adicional e enriquecimento de dados, se necessário. Observe que a função principal do Lambda está apenas invocando uma das três tarefas possíveis aqui. Como mencionei antes, Lambdas assíncronas deveria ter uma DLQ para armazenar as chamadas que falharam, possibilitando replays, incluindo as três Lambdas de "Tipo de Tarefa" abaixo. As tarefas, então, realizam seus trabalhos (o que quer que seja). Aqui estamos simplesmente escrevendo para tabelas do DynamoDB.

Padrão 12: O Roteador
Padrão 12: O Roteador

Padrão 12: O Roteador

13: A API Robusta

Embora isso seja um pouco semelhante ao padrão O Serviço Web Simples, considero esse padrão como A API Robusta, já que estamos adicionando mais complexidade ao interagir com serviços adicionais em nosso aplicativo no geral. É possível, conforme ilustrado abaixo, que várias funções possam compartilhar a mesma origem de dados, funções poderiam fazer chamadas assíncronas para outros serviços e funções poderiam fazer chamadas síncronas para outros serviços ou APIs externas e exigir uma resposta. Também é importante observar que, se criarmos serviços usando o padrão A API Interna, poderemos expô-los ao público usando o API Gateway.

Padrão 13: A API Robusta
Padrão 13: A API Robusta

Padrão 13: A API Robusta

14: O Consumidor Simples

Padrão 14: O Consumidor Simples
Padrão 14: O Consumidor Simples

Padrão 14: O Consumidor Simples

15: O Mecanismo de Relatórios de Leitura Pesada

Nota: O Elasticache não fala diretamente com o RDS, eu estava simplesmente tentando deixar a camada de cache clara. Tecnicamente, uma função Lambda precisaria se comunicar com os dois serviços.

Padrão 15: 15: O Mecanismo de Relatórios de Leitura Pesada
Padrão 15: 15: O Mecanismo de Relatórios de Leitura Pesada

Padrão 15: 15: O Mecanismo de Relatórios de Leitura Pesada

16: O Fan-Out/Fan-In

Em alguns casos, espalhar nosso trabalho pode ser tudo o que precisamos fazer. No entanto, às vezes precisamos agregar os resultados desses trabalhos menores. Uma vez que as Lambdas Trabalhadoras são todas desligadas da nossa invocação original, nós teremos que “fan-in” nossos resultados para um local comum. Isso é realmente mais fácil do que parece. Cada trabalhador simplesmente precisa gravar em uma tabela do DynamoDB com o identificador principal da tarefa, seu identificador de sub-tarefa e os resultados de seu trabalho. Como alternativa, cada trabalho poderia gravar na mesma pasta no S3 e os dados poderiam ser agregados a partir daí. Não esqueça seus DLQs das Lambdas, para pegar as invocações que falharam.

Padrão 16: O Fan-Out/Fan-In
Padrão 16: O Fan-Out/Fan-In

Padrão 16: O Fan-Out/Fan-In

17: O Eventualmente Consistente

No exemplo abaixo, estamos persistindo dados para uma tabela do DynamoDB chamando algum endpoint roteado para uma função Lambda por nosso API Gateway. Para nossos propósitos de API frontend, uma solução NoSQL funciona bem. No entanto, também queremos usar uma cópia desses dados em nosso sistema de relatórios e precisaremos fazer algumas junções, tornando o banco de dados relacional a melhor escolha. Podemos configurar outra função Lambda que assina o Stream da tabela do DynamoDB, que acionará eventos sempre que os dados forem adicionados ou alterados.

Os fluxos do DynamoDB funcionam como o Kinesis, portanto os lotes serão repetidos várias vezes (e ficarão em ordem). Isso significa que podemos limitar nossa função Lambda para garantir que não sobrecarregaremos nossa instância do RDS. Certifique-se de gerenciar seu próprio DLQ para armazenar atualizações inválidas e inclua um campo last_updated com todas as alterações de registro. Você pode usar isso para limitar sua consulta SQL e garantir que você tenha a versão mais recente.

Padrão 17: O Eventualmente Consistente
Padrão 17: O Eventualmente Consistente

Padrão 17: O Eventualmente Consistente

18: A Invocação Distribuída

Estamos usando o CloudWatch Logs como exemplo aqui, mas tecnicamente poderia usar qualquer tipo de evento que fosse suportado. Os eventos acionam nossa função Lambda (que tem nossa DLQ anexada) e, em seguida, envia o evento para um tópico do SNS. No diagrama abaixo, mostro três micro-serviços com buffers SQS sendo notificados. No entanto, as assinaturas do tópico do SNS seriam de responsabilidade individuais dos micro-serviços.

Padrão 18: A Invocação Distribuída
Padrão 18: A Invocação Distribuída

Padrão 18: A Invocação Distribuída

19: O Disjuntor

Veja como isso funciona. Quando o número de falhas atinge um certo limite, “abrimos” o circuito e enviamos imediatamente erros de volta ao cliente que o chamou, sem sequer tentar chamar a API. Após um curto tempo de espera, nós “abrimos parcialmente” o circuito, enviando apenas algumas solicitações para ver se a API está finalmente respondendo corretamente. Todas as outras solicitações recebem um erro. Se as solicitações de amostra forem bem-sucedidas, “fechamos” o circuito e começamos a liberar todo o tráfego. No entanto, se algumas ou todas essas solicitações falharem, o circuito será aberto novamente e o processo será repetido com algum algoritmo para aumentar o tempo limite entre as tentativas de repetição “semi-abertas”.

Esse é um padrão incrivelmente poderoso (e econômico) para qualquer tipo de solicitação síncrona. Você está acumulando cobranças sempre que uma função Lambda está em execução e aguardando a conclusão de outra tarefa. Permitir que seus sistemas se identifiquem automaticamente com problemas como esse, fornecendo um recuo incremental e que, em seguida, faça a auto-recuperação quando o serviço voltar a ficar on-line, fazendo com que você se sinta um super-herói!

Padrão 19: O Disjuntor
Padrão 19: O Disjuntor

Padrão 19: O Disjuntor

Para onde vamos daqui?

Assim como o termo “serverless”, não há uma definição formal e acordada do que um “micro-serviço” realmente consiste. No entanto, os micro-serviços serverless devem, pelo menos, aderir aos seguintes padrões:

  • Os serviços devem ter seus próprios dados privados. Se o seu micro-serviço estiver compartilhando um banco de dados com outro serviço, separe/replique os dados ou combine os serviços. Se nada disso funcionar para você, repense sua estratégia e arquitetura.
  • ‌Os serviços devem ser implantáveis ​​independentemente. Os micro-serviços (especialmente os serverless) devem ser completamente independentes e auto-contidos. É bom que eles sejam dependentes de outros serviços ou que outros dependam deles, mas essas dependências devem ser inteiramente baseadas em canais de comunicação bem definidos entre eles.
  • ‌Utilize a consistência eventual. A replicação de dados e a desnormalização são princípios fundamentais dentro das arquiteturas de micro-serviços. Só porque o Serviço A precisa de alguns dados do Serviço B, não significa que eles devam ser combinados. Os dados podem ter interface em tempo real por meio de comunicação síncrona, se possível, ou podem ser replicados entre os serviços. Respirem fundo pessoal do banco de dados relacional, isso é ok de se fazer.
  • ‌Use cargas de trabalho assíncronas sempre que possível. AWS Lambda cobra por cada 100ms de tempo de processamento usado. Se você está esperando por outros processos para concluir, você está pagando para que suas funções esperem. Isso pode ser necessário para muitos casos de uso, mas, se possível, entregue suas tarefas e deixe-as executar em segundo plano. Para orquestrações mais complicadas, use Step Functions.
  • ‌Mantenha os serviços pequenos, mas valiosos. É possível ir muito pequeno, mas também é provável que você seja grande demais. Sua arquitetura de “micro-serviços” não deve ser uma coleção de pequenos “monolíticos” que lidam com componentes de aplicativos grandes. Não há problema em ter algumas funções, tabelas de banco de dados e filas como parte de um único micro-serviço. Se você puder limitar o tamanho, mas ainda assim fornecer valor comercial suficiente, provavelmente estará onde precisa estar.

Boa sorte e divirta-se construindo seus micro-serviços serverless. Há algum padrão que você está usando e gostaria de compartilhar? Existem nomes legítimos para alguns desses padrões em vez daqueles que acabei de inventar? Deixe um comentário ou fale comigo no Twitter para me avisar.

Créditos ⭐️

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store