As transações são uma parte essencial dos aplicativos. Sem elas, seria impossível manter a consistência dos dados.

Um dos tipos mais avançados de transações é chamado de Two-Phase Commit (Compromisso em duas fases), que é resumido quando o commit de uma primeira transação depende da conclusão de uma segunda. É útil principalmente quando você precisa atualizar várias entidades ao mesmo tempo, como confirmar um pedido e atualizar o estoque de uma só vez.

No entanto, quando você está executando a orquestração de microsserviços, por exemplo, as coisas ficam mais complicadas. Cada serviço é um sistema à parte, com seu próprio banco de dados, e você não pode mais aproveitar a simplicidade dos commits locais de duas fases para manter a consistência de todo o sistema.

Quando você perde essa capacidade, o RDBMS se torna uma péssima opção de armazenamento, pois é possível realizar a mesma "transação atômica de entidade única", mas dezenas de vezes mais rápido, usando apenas um Banco de dados NoSQL como o Couchbase. É por isso que a maioria das empresas que trabalham com microsserviços também está usando NoSQL.

Para exemplificar esse problema, considere a seguinte arquitetura de microsserviços de alto nível de um sistema de comércio eletrônico:

No exemplo acima, não se pode simplesmente fazer um pedido, cobrar do cliente, atualizar o estoque e enviá-lo para entrega em uma única transação ACID. Para executar todo esse fluxo de forma consistente, seria necessário criar uma transação distribuída.

Todos sabemos como é difícil implementar qualquer coisa distribuída, e as transações, infelizmente, não são exceção. Lidar com estados transitórios, eventual consistência entre serviços, isolamentos e reversões são cenários que devem ser considerados durante a fase de projeto.

Felizmente, nós criamos alguns bons padrões para isso, pois estamos implementando transações distribuídas há mais de 20 anos. O padrão sobre o qual gostaria de falar hoje é o chamado padrão Saga.

O padrão SAGA

A padrão de saga é uma sequência de transações locais em que cada transação atualiza dados em um único serviço. A primeira transação em uma saga é iniciada por uma solicitação externa correspondente à operação do sistema e, em seguida, cada etapa subsequente é acionada pela conclusão da anterior.

Um dos padrões mais conhecidos para transações distribuídas é chamado Saga. O primeiro artigo sobre ele foi publicado em 1987 e as sagas têm sido um popular solução desde então.

Usando nosso exemplo anterior de comércio eletrônico, em um design de alto nível, uma implementação do padrão saga seria parecida com a seguinte:

saga implementation

Há algumas maneiras diferentes de implementar uma transação de saga, mas as duas mais populares são:

  • Eventos/Coreografia: Quando não há coordenação central, cada serviço produz e ouve os eventos de outros serviços e decide se uma ação deve ser tomada ou não.
  • Comando/OrquestraçãoQuando um serviço de coordenador é responsável por centralizar a tomada de decisões da saga e sequenciar a lógica comercial

Vamos nos aprofundar um pouco mais em cada implementação para entender como as sagas funcionam.

 

Eventos/Coreografia

Na abordagem de eventos/coreografia, o primeiro serviço executa uma transação e, em seguida, publica um evento. Esse evento é ouvido por um ou mais serviços que executam transações locais e publicam (ou não) novos eventos.

A transação distribuída termina quando o último serviço executa sua transação local e não publica nenhum evento ou o evento publicado não é ouvido por nenhum dos participantes da saga.

Vejamos como seria o padrão de saga em nosso exemplo de comércio eletrônico:

  1. Serviço de pedidos salva um novo pedido, define o estado como pendente e publicar um evento chamado EVENTO_CRIADO_DO_PEDIDO.
  2. O Serviço de pagamento ouve EVENTO_CRIADO_DO_PEDIDOcobrar o cliente e publicar o evento EVENTO_DE_ORDEM_FATURADA.
  3. O Serviço de estoque ouve EVENTO_DE_ORDEM_FATURADAatualizar o estoque, preparar os produtos comprados no pedido e publicar EVENTO_PREPARADO_ORDEM.
  4. Serviço de entrega ouve EVENTO_PREPARADO_ORDEM e, em seguida, coleta e entrega o produto. No final, ele publica um EVENTO_DE_ENTREGA_DO_PEDIDO
  5.  Finalmente, Serviço de pedidos ouve EVENTO_DE_ENTREGA_DO_PEDIDO e definir o estado do pedido como concluído.

No caso acima, se o estado do pedido precisar ser rastreado, o Order Service poderá simplesmente ouvir todos os eventos e atualizar seu estado.

 

Reversões em transações distribuídas

A reversão de uma transação distribuída não é gratuita. Normalmente, é necessário implementar outra transação de compensação para o que foi feito anteriormente.

Suponha que o Stock Service tenha falhado durante uma transação. Vamos ver como seria a reversão:

  1. Serviço de estoque produz EVENTO_DE_ESTOQUE_DO_PRODUTO;
  2. Ambos Serviço de pedidos e Serviço de pagamentoe ouvir a mensagem anterior:
    1. PServiço de pagamento reembolsar o cliente
    2. Serviço de pedidos definir o estado da ordem como falha     

Observe que é fundamental definir uma ID compartilhada comum para cada transação, de modo que, sempre que você lançar um evento, todos os ouvintes poderão saber imediatamente a qual transação ele se refere.

 

Vantagens e desvantagens de usar o padrão de design Event/Choreography do Saga

O Events/Choreography é uma maneira natural de implementar um padrão de orquestração Saga. Ele é simples, fácil de entender, não exige muito esforço para ser criado e todos os os participantes são fracamente acoplados, pois não têm conhecimento direto uns dos outros. Se sua transação envolver de 2 a 4 etapas, pode ser uma boa opção.

No entanto, essa abordagem pode se tornar confusa rapidamente se você continuar adicionando etapas extras à sua transação, pois é difícil rastrear quais serviços ouvem quais eventos. Além disso, ela também pode adicionar uma dependência cíclica entre os serviços pois eles precisam assinar as assinaturas uns dos outros eventos.

Por fim, seria difícil implementar testes usando esse design, pois, para simular o padrão de transação, você deve ter todos os serviços em execução.

 

Na próxima postagemEm seguida, explicarei como resolver a maioria dos problemas com a Saga Abordagem de eventos/coreografia usando outra implementação do Saga chamada Comando/Orquestração.

Enquanto isso, se você tiver alguma dúvida sobre o Saga Design Pattern sobre microsserviços, a arquitetura Saga ou os aplicativos Saga, fique à vontade para me perguntar em @deniswsrosa

Autor

Postado por Denis Rosa, defensor dos desenvolvedores, Couchbase

Denis Rosa é um Developer Advocate do Couchbase e mora em Munique, na Alemanha. Ele tem uma sólida experiência como engenheiro de software e fala fluentemente Java, Python, Scala e Javascript. Denis gosta de escrever sobre pesquisa, Big Data, IA, microsserviços e tudo o mais que possa ajudar os desenvolvedores a criar um aplicativo bonito, mais rápido, estável e escalável.

2 Comentários

  1. Duas perguntas sobre como isso pode funcionar melhor com o Couchbase.

    Primeiro, para qualquer armazenamento que não tenha transações ACID de vários documentos... Até onde sei, o padrão saga pressupõe que cada chamada de serviço corresponda a uma única transação de banco de dados que possa ser confirmada ou revertida. Mas e se eu tiver uma chamada de serviço que grava vários documentos do Couchbase? Isso implica modelar cada gravação de documento como um evento distinto?

    Em segundo lugar, especificamente para o couchbase, e se também estivermos usando o couchbase lite? Um cliente móvel pode executar uma sincronização downstream no meio de uma transação de saga, que pode ser revertida, e o cliente provavelmente não participará do fluxo de eventos. Acho que podemos simplesmente presumir que o cliente acabará alcançando o estado do servidor couchbase e terá que lidar com os conflitos que surgirem. Mas e quanto à sincronização upstream?

  2. [...] o padrão saga é uma sequência de transações locais em que cada transação atualiza dados em um único serviço. [...]

Deixar uma resposta