Quando você usa uma operação eventualmente consistente que consiste em uma série de etapas, o padrão de transação de compensação pode ser útil. Especificamente, se uma ou mais das etapas falharem, você poderá usar o padrão de Transação de Compensação para desfazer o trabalho que as etapas executaram. Normalmente, você encontra operações que seguem o modelo de consistência eventual em aplicativos hospedados na nuvem que implementam processos de negócios e fluxos de trabalho complexos.
Contexto e problema
Os aplicativos executados na nuvem frequentemente modificam dados. Por vezes, estes dados estão distribuídos por várias fontes de dados em diferentes localizações geográficas. Para evitar a contenção e melhorar o desempenho num ambiente distribuído, uma aplicação não deve tentar fornecer uma consistência transacional forte. Em vez disso, a aplicação deve implementar a consistência eventual. No modelo de consistência eventual, uma operação de negócios típica consiste em uma série de etapas separadas. Embora a operação execute essas etapas, a visão geral do estado do sistema pode ser inconsistente. Mas quando a operação terminar e todas as etapas tiverem sido executadas, o sistema deve se tornar consistente novamente.
A Cartilha de Consistência de Dados fornece informações sobre por que as transações distribuídas não são bem dimensionadas. Este recurso também lista princípios do modelo de consistência eventual.
Um desafio no modelo de consistência eventual é como lidar com uma etapa que falha. Após uma falha, talvez seja necessário desfazer todo o trabalho que as etapas anteriores da operação foram concluídas. No entanto, nem sempre é possível reverter os dados, porque outras instâncias simultâneas do aplicativo podem tê-los alterado. Mesmo nos casos em que instâncias simultâneas não alteraram os dados, desfazer uma etapa pode ser mais complexo do que restaurar o estado original. Poderá ser necessário aplicar várias regras específicas às empresas. Para obter um exemplo, consulte o site de viagens que a seção Exemplo descreve mais adiante neste artigo.
Se uma operação que implementa consistência eventual abrange vários armazenamentos de dados heterogêneos, desfazer as etapas da operação requer visitar cada armazenamento de dados por vez. Para evitar que o sistema permaneça inconsistente, você deve desfazer de forma confiável o trabalho que você executou em cada armazenamento de dados.
Os dados afetados por uma operação que implementa consistência eventual nem sempre são mantidos em um banco de dados. Por exemplo, considere um ambiente de arquitetura orientada a serviços (SOA). Uma operação SOA pode invocar uma ação em um serviço e causar uma alteração no estado mantido por esse serviço. Para desfazer a operação, você também tem que desfazer essa mudança de estado. Esse processo pode envolver invocar o serviço novamente e executar outra ação que reverta os efeitos da primeira.
Solução
A solução é implementar uma transação de compensação. As etapas em uma transação de compensação desfazem os efeitos das etapas na operação original. Uma abordagem intuitiva é substituir o estado atual pelo estado em que o sistema estava no início da operação. Mas uma transação de compensação nem sempre pode adotar essa abordagem, porque pode substituir alterações que outras instâncias simultâneas de um aplicativo fizeram. Em vez disso, uma transação de compensação deve ser um processo inteligente que leva em conta qualquer trabalho que instâncias simultâneas façam. Esse processo geralmente é específico do aplicativo, impulsionado pela natureza do trabalho que a operação original executa.
Uma abordagem comum consiste em utilizar um fluxo de trabalho para implementar uma operação eventualmente consistente que necessite de compensação. À medida que a operação original prossegue, o sistema registra informações sobre cada etapa, incluindo como desfazer o trabalho que a etapa executa. Se a operação falhar a qualquer momento, o fluxo de trabalho retrocede pelas etapas concluídas. Em cada etapa, o fluxo de trabalho executa o trabalho que reverte essa etapa.
Dois pontos importantes são:
- Uma transação de compensação pode não ter que desfazer o trabalho na ordem exata inversa da operação original.
- Pode ser possível executar algumas das etapas de desfazer em paralelo.
Esta abordagem é semelhante à estratégia Sagas que é discutida no blog de Clemens Vasters.
Uma transação de compensação é uma operação eventualmente consistente em si, por isso também pode falhar. O sistema deve ser capaz de retomar a transação de compensação no ponto em que a falha ocorreu e prosseguir. Pode ser necessário repetir uma etapa que falha, então você deve definir as etapas em uma transação de compensação como comandos idempotentes. Para obter mais informações, consulte Padrões de idempotência no blog de Jonathan Oliver.
Em alguns casos, a intervenção manual pode ser a única maneira de recuperar de uma etapa que falhou. Nessas situações, o sistema deve emitir um alerta e fornecer o máximo de informações possível sobre o motivo da falha.
Problemas e considerações
Considere os seguintes pontos ao decidir como implementar esse padrão:
Pode não ser fácil determinar quando uma etapa de uma operação que implementa uma eventual consistência falha. Uma etapa pode não falhar imediatamente. Em vez disso, pode ser bloqueado. Talvez seja necessário implementar um mecanismo de tempo limite.
Não é fácil generalizar a lógica de compensação. Uma transação de compensação é específica da aplicação. Conta com o facto de a aplicação ter informações suficientes para conseguir anular os efeitos de cada passo numa operação falhada.
As transações compensatórias nem sempre funcionam. Deve definir os passos de uma transação de compensação como comandos idempotentes. Se o fizer, os passos podem ser repetidos se a própria transação de compensação falhar.
A infraestrutura que lida com as etapas deve atender aos seguintes critérios:
- É resiliente na operação original e na transação de compensação.
- Ele não perde as informações necessárias para compensar uma etapa falhada.
- Monitoriza de forma fiável o progresso da lógica de compensação.
Uma transação de compensação não necessariamente retorna os dados do sistema ao seu estado no início da operação original. Em vez disso, a transação compensa o trabalho que a operação concluiu com êxito antes de falhar.
A ordem das etapas na transação de compensação não é necessariamente o oposto das etapas na operação original. Por exemplo, um armazenamento de dados pode ser mais sensível a inconsistências do que outro. As etapas na transação de compensação que desfazem as alterações neste armazenamento devem ocorrer primeiro.
Certas medidas podem ajudar a aumentar a probabilidade de êxito da atividade global. Especificamente, você pode colocar um bloqueio baseado em tempo limite de curto prazo em cada recurso necessário para concluir uma operação. Você também pode obter esses recursos com antecedência. Em seguida, execute o trabalho somente depois de ter adquirido todos os recursos. Finalize todas as ações antes que os bloqueios expirem.
Uma lógica de repetição mais tolerante do que o habitual pode ajudar a minimizar as falhas que desencadeiam uma transação de compensação. Se uma etapa em uma operação que implementa consistência eventual falhar, tente lidar com a falha como uma exceção transitória e repetir a etapa. Pare a operação e inicie uma transação de compensação somente se uma etapa falhar repetidamente ou não puder ser recuperada.
Ao implementar uma transação de compensação, você enfrenta muitos dos mesmos desafios que enfrenta quando implementa uma eventual consistência. Para obter mais informações, consulte a seção "Considerações para implementar consistência eventual" em Cartilha de consistência de dados.
Quando utilizar este padrão
Utilize este padrão apenas para operações que têm de ser anuladas em caso de falha. Se possível, desenvolva as soluções de modo a evitar a complexidade de recorrerem a transações de compensação.
Design da carga de trabalho
Um arquiteto deve avaliar como o padrão de Transação de Compensação pode ser usado no design de sua carga de trabalho para abordar as metas e os princípios abordados nos pilares do Azure Well-Architected Framework. Por exemplo:
Pilar | Como esse padrão suporta os objetivos do pilar |
---|---|
As decisões de projeto de confiabilidade ajudam sua carga de trabalho a se tornar resiliente ao mau funcionamento e a garantir que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. | As ações de compensação abordam falhas em caminhos críticos de carga de trabalho usando processos como reverter diretamente as alterações de dados, quebrar bloqueios de transações ou até mesmo executar o comportamento nativo do sistema para reverter o efeito. - RE:02 Fluxos críticos - RE:09 Recuperação de desastres |
Como em qualquer decisão de design, considere quaisquer compensações em relação aos objetivos dos outros pilares que possam ser introduzidos com esse padrão.
Exemplo
Os clientes usam um site de viagens para reservar itinerários. Um único itinerário pode consistir em uma série de voos e hotéis. Um cliente que viaja de Seattle para Londres e depois para Paris pode executar as seguintes etapas ao criar um itinerário:
- Reservar um lugar no voo F1 de Seattle para Londres.
- Reservar um lugar no voo F2 de Londres para Paris.
- Reservar um lugar no voo F3 de Paris para Seattle.
- Reservar um quarto no hotel H1 em Londres.
- Reservar um quarto no hotel H2 em Paris.
Estes passos constituem uma operação eventualmente consistente, apesar de cada passo ser uma ação separada. Além de executar essas etapas, o sistema também deve registrar as operações do contador para desfazer cada etapa. Esta informação é necessária no caso de o cliente cancelar o itinerário. As etapas necessárias para executar as operações de contador podem ser executadas como uma transação de compensação.
As etapas da transação de compensação podem não ser exatamente o oposto das etapas originais. Além disso, a lógica em cada etapa da transação de compensação deve levar em conta as regras específicas do negócio. Por exemplo, cancelar uma reserva de voo pode não dar direito ao reembolso total do cliente.
A figura a seguir mostra as etapas de uma transação de longa duração para reservar um itinerário de viagem. Você também pode ver as etapas da transação de compensação que desfazem a transação.
Nota
Talvez seja possível executar as etapas na transação de compensação em paralelo, dependendo de como você projeta a lógica de compensação para cada etapa.
Em muitas soluções de negócios, a falha de uma única etapa nem sempre requer a reversão do sistema usando uma transação de compensação. Por exemplo, considere o cenário do site de viagens. Suponha que o cliente reserve voos F1, F2 e F3, mas não possa reservar um quarto no hotel H1. É preferível oferecer ao cliente um quarto em um hotel diferente na mesma cidade em vez de cancelar os voos. O cliente ainda pode decidir cancelar. Nesse caso, a transação de compensação é executada e desfaz as reservas para os voos F1, F2 e F3. Mas o cliente deve tomar essa decisão, não o sistema.
Próximos passos
- Manual Básico de Consistência de Dados. O padrão Transação de Compensação é muitas vezes utilizado para anular operações que implementam o modelo de consistência eventual. Esta cartilha fornece informações sobre os benefícios e compensações de uma eventual consistência.
- Padrões de idempotência. Em uma transação de compensação, é melhor usar comandos idempotentes. Esta postagem no blog descreve os fatores a serem considerados ao implementar a idempotência.
Recursos relacionados
- Padrão do Supervisor do Agente do Agendador. Este artigo descreve como implementar sistemas resilientes que executam operações de negócios que usam serviços e recursos distribuídos. Nesses sistemas, às vezes você precisa usar uma transação de compensação para desfazer o trabalho que uma operação executa.
- Padrão Repetição. As operações de compensação podem ser computacionalmente exigentes. Você pode tentar minimizar seu uso usando o padrão Repetir para implementar uma política eficaz de repetição de operações com falha.
- Padrão de transações distribuídas da Saga. Este artigo explica como usar o padrão Saga para gerenciar a consistência de dados em microsserviços em cenários de transações distribuídas. O padrão Saga lida com a recuperação de falhas com transações de compensação.
- Padrão de tubos e filtros. Este artigo descreve o padrão Pipes and Filters, que você pode usar para decompor uma tarefa de processamento complexa em uma série de elementos reutilizáveis. Você pode usar o padrão Pipes and Filters com o padrão Compensating Transaction como uma alternativa à implementação de transações distribuídas.
- Conceção para a recuperação automática. Este guia explica como projetar aplicativos de autorrecuperação. Você pode usar transações de compensação como parte de uma abordagem de autorrecuperação.