Consistência de dados e padrão Outbox
Tags: outboxpattern, consistência, transaçãodistribuida, eventualconsistency
Published: 2023-05-13 12:15:00

---
title: Consistência de dados e padrão Outbox
published: true
description: Garantir a consistência dos dados de um sistema distribuídos é grande desafio e padrão Outbox pode ser a solução que você precisa.
tags: OutboxPattern,Consistência,transaçãodistribuida,eventualconsistency
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/soxoo6nty2edg9k6yuec.png
published_at: 2023-05-13 12:15 +0000
---
Um dos pontos mais importantes em todas aplicações que trabalha com escrita de dados é garantir a consistência dos dados.

Um exemplo básico é a criação de um pedido que possui itens. Considerando um banco relacional, os dados são armazenados em duas tabelas `Orders` e `OrderItems`. Imagine que a aplicação realize um `INSERT` no banco na tabela `Orders` e recebe um erro na tentativa de fazer o `INSERT` na tabela `OrderItems`. Dessa forma o sistema ficou inconsistente, o usuário tem um pedido sem itens e isso pode, inclusive, impedir que ele consiga repetir o pedido.

![Aplicação de Pedidos que armazena no banco de dados](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mupjvxz0vsfxeu8ilxqi.png)

O desejado é que o pedido seja salvo completamente ou que seja nada salvo. Nos bancos relacionais a solução é o uso de transações.

```sql
BEGIN TRANSACTION
  INSERT INTO TABLE Orders...
  INSERT INTO TABLE OrderItems...
COMMIT
```

Caso o segundo `INSERT` não seja concluído deve ser realizado o rollback da transação mantendo o comportamento de "tudo ou nada".

## O problema de escrita dupla (dual write)

Agora vamos ampliar nosso cenário, um microsserviço de Pedidos é responsável por manter os dados de pedidos e notificar os outros sistemas sobre pedidos feitos. Para se comunicar com os outros serviços será utilizado uma solução de [mensageria](https://www.youtube.com/watch?v=R-zLsCDiTV8&list=PLqONbZa3fPe4Z6sg7RQGaHlYqUjsliKAH).

![A aplicação salva pedidos no banco de dados e envia uma mensagem para um sistema de mensageria](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wyz27ds1s9b2l8k5z9g2.png)

Dessa forma, ao receber um novo pedido deve ser feita uma escrita na tabela `Orders` no banco de dados e enviar uma mensagem `order.created` para uma fila. Como manter a consistência entre o banco de dados e o sistema de mensageria? Se, após inserir um registro no banco de dados, o envio da mensagem para a fila não tiver sucesso as aplicações que esperam a mensagem ficam inconsistentes. Invertendo a lógica e enviando a mensagem antes de salvar no banco de dados produz um problema maior pois as aplicações seguintes podem receber mensagens de operações que nunca foram registradas na aplicação que é dona desse dado.

Manter a consistência dos dados em sistemas distribuídos é um grande desafio e [diversas abordagens tem diferentes trade-offs](https://developers.redhat.com/articles/2021/09/21/distributed-transaction-patterns-microservices-compared). Comumente, é aceitável garantir que o sistema esteja consistente em algum ponto no futuro. Essa abordagem é chamada de **Consistência Posterior** (eventual consistency).

## Outbox Pattern

A ideia do outbox pattern é usar o banco de dados para garantir que a mensagem será enviada. As mensagens que serão enviadas para o sistema de mensageria devem ser armazenadas em uma tabela junto do status de envio. Ao inserir um registro da tabela `Orders` também deve-se inserir a mensagem, tudo dentro de uma transação.

```SQL
BEGIN TRANSACTION
  INSERT INTO Orders...
  INSERT INTO OutboxMessages(message, status)...
COMMIT
```

Essa tabela `OutboxMessages` deve ser verificada periodicamente em busca de mensagens com o status pendente para que sejam enviadas para a fila. Dessa forma tem-se a garantia que a mensagem será enviada **ao menos uma vez**. Por que ao menos uma vez? Porque o processo de envio da mensagem e a atualização do status de envio do registro do banco é uma escrita dupla e está sujeita a falhas. A operação de atualização do status como enviado pode falhar e portanto durante a próxima busca de mensagens pendentes essa mensagem seria enviada novamente.

## Idempotência

Considerando o problema anterior é possível que uma mesma mensagem seja enviada repetidamente para a fila. Assim, é necessário que os sistemas que processam essas mensagens sejam idempotentes, ou seja, a execução de operações repetidas não podem afetar o resultado da primeira operação. Algumas operações são idempotentes por natureza, como atribuir um determinado valor para um campo de um registro de uma tabela. Se executada múltiplas vezes o resultado final é o mesmo de se executar apenas uma vez. Em outras situações não garantir a idempotência pode ser catastrófico. Expandindo nosso exemplo, considere uma aplicação que recebe mensagens de pedidos realizados e decrementa o estoque dos produtos vendidos. Se a mensagem do pedido for duplicada, o pedido abateria do estoque o dobro de produtos comprados.

![O serviço de Produtos processa a mensagem de pedidos realizados e atualiza no seu banco de dados](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myk48c7tptbiq0l6l4py.png)

Uma maneira de implementar idempotência para esses casos é utilizar novamente o banco de dados. A aplicação que consome as mensagens deve possuir uma tabela para armazenar o id das mensagens processadas. Ao executar a operação no banco de dados, no nosso exemplo um `UPDATE` na tabela `Products`, deve-se inserir o id da mensagem na tabela de mensagens processadas. As duas operações devem ser realizadas dentro de uma transação pois se a restrição de chave primária da tabela `ProccessedMessages` retornar erro (ou seja, a mensagem já foi processada) a atualização do produto deve ser desfeita.

```sql
BEGIN TRANSACTION
  UPDATE Produtcs...
  INSERT INTO ProccessedMessages(Id) ...
COMMIT
  
```

## Implementação

Usando C# existem algumas opções de bibliotecas que implementam o padrão Outbox como [CAP](https://cap.dotnetcore.xyz/) e [Mass Transit](https://masstransit.io/). Para idempotência eu criei o [Ziggurat](https://github.com/rafaelpadovezi/Ziggurat) que implementa a solução descrita nesse texto. É possível ver um exemplo de implementação de CAP com Ziggurat [aqui](https://dev.to/luizhlelis/data-consistency-outbox-pattern-and-idempotency-in-a-microservice-architecture-32dl).

No python existe a biblioteca [django-outbox-pattern](https://github.com/juntossomosmais/django-outbox-pattern) que implementa o padrão outbox e também garante idempotência nos consumidores.