Fluxos de trabalho transacionais em uma arquitetura de microsserviços no Google Cloud

Last reviewed 2022-04-04 UTC

Em um aplicativo empresarial tradicional, as solicitações dos clientes são executadas em uma transação do banco de dados. Geralmente, todos os dados necessários para concluir uma solicitação são armazenados em um único banco de dados que tenha propriedades ACID. O termo ACID significa atomicidade, consistência e isolamento. e durabilidade. Portanto, é possível garantir a consistência usando os recursos transacionais do sistema de banco de dados relacional. Quando algo der errado com a transação, o sistema de banco de dados poderá reverter automaticamente e repetir a transação. Neste documento, descrevemos como projetar fluxos de trabalho transacionais usando o Cloud Run, Pub/Sub, Workflows e Firestore no modo Datastore (Datastore). Ele é destinado a desenvolvedores de aplicativos que querem projetar fluxos de trabalho transacionais em um aplicativo baseado em microsserviços.

Este documento pertence a uma série que tem as seguintes partes:

Transações de ponta a ponta em microsserviços

Em arquiteturas de microsserviços, uma transação de ponta a ponta pode abranger vários serviços. Cada serviço pode fornecer um recurso específico e ter o próprio banco de dados independente, conforme mostrado no diagrama a seguir.

Serviços A, B e C com os respectivos bancos de dados.

Conforme mostrado na imagem anterior, um cliente acessa vários microsserviços por meio de um gateway. Por causa dessa disposição de acesso do cliente, não é possível confiar nos recursos transacionais de um único banco de dados para ajudar seu sistema de banco de dados a se recuperar de falhas e garantir consistência. Em vez disso, recomendamos a implementação de um fluxo de trabalho transacional na sua arquitetura de microsserviços.

Neste documento, descrevemos dois padrões que podem ser usados para implementar um fluxo de trabalho transacional em uma arquitetura de microsserviços. Os padrões são os seguintes:

  • Saga baseada em coreografias
  • Orquestração síncrona

Exemplo de aplicativo

Para ajudar a demonstrar o fluxo de trabalho, este documento usa um aplicativo de exemplo simples, que pode processar transações de pedido para um site de compras. O aplicativo gerencia clientes e pedidos. Os clientes têm um limite de crédito, e o aplicativo precisa confirmar que um novo pedido não excederá o limite de crédito do cliente. Conforme mostrado no diagrama a seguir, o fluxo de trabalho transacional é executado nos seguintes microsserviços: os serviços Order e Customer.

O diagrama de arquitetura de aplicativo de exemplo.

O fluxo de trabalho é o seguinte:

  • Um cliente envia uma solicitação de pedido que especifica um ID de cliente e vários itens.
  • O serviço Order atribui um ID de pedido e armazena as informações do pedido no banco de dados. O status do pedido está marcado como pending.
  • O serviço Customer aumenta o uso de crédito do cliente armazenado no banco de dados de acordo com o número de itens pedidos. Por exemplo, um aumento de 100 créditos em um único item.
  • Se o uso total de crédito for menor ou igual ao limite predefinido, o pedido será aceito e o serviço Order mudará o status do pedido no banco de dados para accepted.
  • Se o uso total de crédito for maior que o limite predefinido, o serviço Order mudará o status do pedido para rejected. Nesse caso, o uso de crédito não aumenta.

O serviço Order

O serviço Order gerencia o status de um pedido. Ele gera e armazena um registro de pedido no banco de dados Order para cada solicitação do cliente. O registro consiste nas seguintes colunas:

  • Order_id: o ID do pedido. Esse ID é gerado pelo serviço Order.
  • Customer_id: o ID de cliente.
  • Number: a quantidade de itens no pedido.
  • Status: o status de um pedido.

Atendimento ao cliente

O serviço Customer gerencia os créditos dos clientes. Ele gera e armazena um registro do cliente no banco de dados Customer para cada solicitação do cliente. O registro consiste nas seguintes colunas:

  • Customer_id: o ID do cliente, que é gerado pelo serviço Customer.
  • Credit: o número de créditos do cliente. Ele aumenta quando o cliente faz o pedido de itens.
  • Limit: o limite de crédito individual do cliente. Um pedido será rejeitado quando o crédito do cliente estiver acima do limite definido.

Saga baseada em coreografias

Nesta seção, descrevemos como implementar um padrão de microsserviços de saga baseada em coreografia em um fluxo de trabalho transacional.

Visão geral da arquitetura

Em um padrão de microsserviços de saga baseada em coreografia, eles funcionam como um sistema distribuído automaticamente. Quando um serviço muda o status da própria entidade, ele publica um evento para notificar outros serviços sobre atualizações. O evento de notificação aciona outros serviços para agir. Dessa forma, vários serviços funcionam juntos para concluir um processo transacional. A comunicação entre microsserviços é assíncrona. Quando um serviço publica um evento, nenhuma informação é enviada ao serviço de publicação para confirmar os serviços que recebem o evento ou quando o recebem.

A imagem a seguir mostra um exemplo de padrão de microsserviços de saga com base em coreografia.

Padrão de microsserviços de saga baseada em coreografia.

O exemplo de arquitetura mostrado na imagem anterior é o seguinte:

  • O Cloud Run atua como um ambiente de execução de microsserviços.
  • O Pub/Sub atua como um serviço de mensagens para entregar eventos entre microsserviços.
  • O Datastore fornece um banco de dados de cada serviço.

Use o Datastore para armazenar eventos antes de publicá-los. Conforme explicado no processo de publicação de eventos, os microsserviços armazenam os eventos em vez de publicá-los imediatamente.

Os serviços Order e Customer armazenam eventos no banco de dados do evento primeiro. Em seguida, os eventos armazenados são publicados periodicamente usando o Cloud Scheduler. O Cloud Scheduler invoca o serviço event-publisher, que publica eventos. Esse fluxo de eventos é mostrado na seguinte imagem:

O fluxo de trabalho de publicação de eventos.

Fluxo de trabalho transacional

Em um fluxo de trabalho transacional, dois serviços se comunicam entre si por meio de eventos. Nesta arquitetura, o pedido do cliente é processado da seguinte maneira:

  1. O cliente envia uma solicitação de pedido que especifica o ID do cliente e o número de itens que ele solicitou. A solicitação é enviada ao serviço Order por uma API REST.
  2. O serviço Order atribui um ID ao pedido e armazena as informações do pedido no banco de dados Order. O status do pedido é marcado como pending. O serviço Order retorna as informações do pedido para o cliente e publica um evento que inclui essas informações no seguinte tópico do Pub/Sub: order-service-event.
  3. O serviço Customer recebe o evento por uma notificação push. Isso aumenta o uso de crédito do cliente, que é armazenado no banco de dados Customer de acordo com o número de itens pedidos.
  4. Se o uso total de créditos for menor ou igual ao limite predefinido, o serviço Customer publicará um evento que declara que o aumento do crédito foi bem-sucedido. Como alternativa, ele publica um evento que afirma que o aumento do crédito falhou. Nesse caso, o uso de crédito não é aumentado.
  5. O serviço Order recebe o evento por uma notificação push. Ele altera o status do pedido para accepted ou rejected. O cliente pode rastrear o status do pedido usando o ID do pedido retornado do serviço Order.

O diagrama a seguir resume esse fluxo de trabalho:

Diagrama de sequência do fluxo de trabalho de saga baseado em coreografia.

O processo de publicação de eventos

Quando um microsserviço modifica os próprios dados no banco de dados e publica um evento para notificá-lo, essas duas operações precisam ser realizadas de maneira atômica. Por exemplo, se o microsserviço falhar após a modificação de dados sem publicar um evento, o processo transacional será interrompido. Nesse caso, os dados podem ser deixados em um estado inconsistente nos vários microsserviços envolvidos na transação. Para evitar esse problema, no aplicativo de exemplo usado neste documento, os microsserviços gravam dados de eventos no banco de dados de back-end em vez de publicar diretamente os eventos no Pub/Sub.

Os dados são modificados e os dados de eventos associados são gravados de maneira atômica, usando o recurso transacional do banco de dados de back-end. Esse padrão, mostrado na imagem a seguir, é chamado de eventos de aplicativo ou "caixa de saída transacional".

Padrão usando o recurso transacional.

Conforme mostrado na imagem anterior, inicialmente a coluna published nos dados do evento está marcada como False. Em seguida, o serviço event-publisher verifica periodicamente o banco de dados e publica eventos em que a coluna published é False. Depois de publicar um evento, o serviço event-publisher muda a coluna published para True.

Conforme mostrado na imagem a seguir, os bancos de dados Order e Event no mesmo namespace podem ser atualizados atomicamente por transações do Datastore.

Atualizações atômicas pelas transações do Cloud Datastore.

Se o serviço event-publisher falhar após a publicação de um evento sem alterar a coluna published, ele publicará o mesmo evento novamente após a recuperação. Como a republicação do evento causa um evento duplicado, os microsserviços que recebem o evento precisam verificar a possível duplicação e processá-la de acordo. Essa abordagem ajuda a garantir a idempotência do gerenciamento de eventos.

A imagem a seguir mostra como um aplicativo de exemplo lida com a duplicação de eventos.

Um microsserviço atualiza o respectivo banco de dados de back-end com base na lógica de negócios acionada por um evento.

Conforme mostrado no diagrama anterior, o aplicativo lida com eventos duplicados com o seguinte fluxo de trabalho:

  • Cada microsserviço atualiza o respectivo banco de dados de back-end com base na lógica de negócios acionada por um evento e grava o ID no respectivo banco de dados.
  • Essas duas gravações são realizadas de uma forma atômica, com o recurso transacional usado pelos bancos de dados de back-end.
  • Se os serviços receberem um evento duplicado, ele será detectado quando procurarem o ID do evento nos bancos de dados.

O processamento de eventos duplicados é uma prática comum no recebimento de eventos do Pub/Sub, porque há uma pequena chance de que o Pub/Sub possa causar uma entrega de mensagens duplicada.

Expandir a arquitetura

No aplicativo de exemplo, antes de processar uma mensagem, use o Datastore para verificar se ela está duplicada. Essa abordagem significa que o serviço que consome as mensagens (serviço Customer, neste caso) é idempotente. Essa abordagem geralmente é chamada de padrão "consumidor idempotente". Alguns frameworks implementam esse padrão como um recurso integrado, por exemplo, Eventuate.

No entanto, acessar o banco de dados sempre que você processar uma mensagem pode causar problemas de desempenho. Uma solução é utilizar um banco de dados com bom desempenho e escalonabilidade, por exemplo, Redis.

Orquestração síncrona

Nesta seção, descrevemos como implementar um padrão de microsserviços de orquestração síncrono em um fluxo de trabalho transacional.

Visão geral da arquitetura

Nesse padrão, um único orquestrador controla o fluxo de execução de uma transação. A comunicação entre os microsserviços e o orquestrador é feita de maneira síncrona por meio das APIs REST.

Na arquitetura de exemplo descrita neste documento, o Cloud Run é usado como um ambiente de execução de microsserviços e o Datastore é usado como um banco de dados de back-end para cada serviço. Além disso, os fluxos de trabalho são usados como um orquestrador. Esse padrão é mostrado na seguinte imagem:

Padrão com o Cloud Run como um ambiente de execução de microsserviços e o Datastore como um banco de dados de back-end para cada serviço.

Fluxo de trabalho transacional

Na arquitetura de um fluxo de trabalho síncrono, o pedido de um cliente é processado da seguinte maneira:

  1. O cliente envia uma solicitação de pedido que especifica o ID de um cliente e o número de itens que ele solicitou. A solicitação é enviada ao serviço Order processor pela API REST.
  2. O serviço Order processor executa um fluxo de trabalho em que o ID de cliente e o número de itens são transmitidos para os fluxos de trabalho.
  3. O fluxo de trabalho chama a API REST do serviço Order e transmite o ID do cliente e o número de itens que o cliente solicitou. Em seguida, o serviço Order atribui um ID de pedido ao pedido do cliente e armazena as informações do pedido no banco de dados Order. O status do pedido está marcado como pending. O serviço Order retorna as informações do pedido para o fluxo de trabalho.
  4. O fluxo de trabalho chama a API REST do serviço Customer e transmite o ID do cliente e o número de itens que o cliente solicitou. Em seguida, o serviço Customer aumenta o uso de crédito do cliente armazenado no banco de dados Customer de acordo com o número de itens pedidos.
  5. Se o uso total de créditos for menor ou igual ao limite predefinido, o serviço Customer retornará dados que explicam que o aumento de crédito foi bem-sucedido. Como alternativa, ele retorna dados que explicam que o aumento do crédito falhou. Nesse caso, o uso de crédito não é aumentado.
  6. O fluxo de trabalho chama a API REST Order do serviço para mudar o status do pedido para accepted ou rejected, conforme apropriado. Por fim, ele retorna as informações do pedido na atualização do status final para o serviço Order processor. Em seguida, o serviço Order processor retorna essas informações ao cliente.

Esse fluxo de trabalho é resumido no diagrama a seguir:

Diagrama de sequência do fluxo de trabalho da orquestração síncrona.

Vantagens e desvantagens

Ao considerar se é necessário implementar uma saga baseada em coreografia ou orquestração síncrona, a melhor opção para a organização é sempre o padrão mais adequado para as necessidades dela. No entanto, em geral, devido à simplicidade do design, a orquestração síncrona é geralmente a primeira escolha para muitas empresas.

Veja na tabela a seguir as vantagens e desvantagens da saga baseada em coreografia e os padrões de orquestração síncronas descritos neste documento.


Vantagens

Desvantagens

Saga baseada em coreografia

Acoplamento flexível: cada serviço publica eventos no Datastore quando há uma mudança nos próprios dados. Nenhuma informação é enviada para outros serviços. Essa abordagem torna cada serviço mais independente, e há menos chance de precisar modificá-los quando você introduz novos serviços no fluxo de trabalho.

Dependência complexa: a implementação de todo o fluxo de trabalho é distribuída entre os serviços. Como resultado, pode ser complexo entender o fluxo de trabalho. Essa abordagem pode acidentalmente introduzir complexidade em futuras mudanças de design e solução de problemas.

Orquestração síncrona

Dependência simples: um único orquestrador controla todo o fluxo de execução de uma transação. Como resultado, é mais simples entender como funciona o fluxo de transação. Esse padrão simplifica a modificação do fluxo de trabalho e a solução de problemas.

Risco do acoplamento rígido: o Orchestrator central depende de todos os serviços que compõem o fluxo de trabalho transacional. Como resultado, quando você modifica um desses serviços ou adiciona novos serviços ao fluxo de trabalho, talvez seja necessário modificar o orquestrador. O esforço extra necessário pode superar o benefício de modificar e adicionar serviços de maneira mais independente à arquitetura de microsserviços em comparação com sistemas monolíticos.

A seguir