Já imaginou um usuário clicando várias vezes no botão de pagamento e sendo cobrado múltiplas vezes? Ou então um script de infraestrutura rodando de novo e criando recursos duplicados? Já passei por isso… e a causa sempre era a mesma: falta de idempotência.
Duplicação de requisições é algo comum de acontecer principalmente em arquiteturas distribuidos. Implementar validações de requisições duplicadas é essencial para evitar bugs e falhas. Em resumo: idempotência garante que executar uma ação várias vezes sempre leve ao mesmo resultado final, ou seja, chega de reprocessar e causar entradas duplicadas em seus softwares.
Logo de cara, é importante entender que alguns serviços de mensageria não garantem idempotência nativamente, o que significa que uma mesma mensagem pode ser entregue mais de uma vez. Em comunicações síncronas isso também acontece, mecanismos de retry e timeouts podem gerar requisições duplicadas, já que o cliente pode não receber uma resposta e tentar novamente. Sem um controle adequado, isso pode resultar em processamentos repetidos e até inconsistências nos dados.
Olha aqui um exemplo de um serviço excepcional da AWS, o AWS SQS:
Filas Standard garantem a entrega de mensagens pelo menos uma vez, porém, por causa da arquitetura altamente distribuída, mais de uma cópia de uma mensagem pode ser entregue e as mensagens às vezes podem chegar fora de ordem. Apesar disso, as filas padrão fazem o possível para manter a ordem em que as mensagens são enviadas. https://docs.aws.amazon.com/pt_br/AWSSimpleQueueService/latest/SQSDeveloperGuide/standard-queues.html
Mas antes de falarmos sobre como implementar a idempotência, é fundamental entender o conceito de chave forte ou chave de idempotência. Basicamente, essa chave é um identificador único associado a uma operação, garantindo que, caso a mesma solicitação seja enviada mais de uma vez, o sistema reconheça e trate como uma única execução.
Em APIs síncronas, essa chave geralmente é enviada no cabeçalho da requisição (mas as vezes é um concate de varios valores do payload), como um UUID (Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
) ou um hash gerado a partir do corpo da requisição (exemplo: SHA-256("user123-100USD-2024-02-10")
).
Já em sistemas assíncronos, a chave pode ser o próprio ID único da mensagem, como um MessageId
em um serviço de mensageria (MessageId: msg-abc-123
) ou um identificador customizado gerado pelo produtor antes do envio (Order-Id: 987654321
ou Order-Id: action-user-date-hour
).
Sem uma chave forte bem definida, não há como garantir a idempotência de forma eficaz.
Bom, agora q vc entende o que é a chave de idempotencia, vamos falar um pouco de como usa-las:
1. Idempotência em APIs Sync / Síncronas (RESTful)
Para evitar que um mesmo pedido seja processado mais de uma vez em uma API de pagamento, você pode implementar da seguinte forma:
1️⃣ O cliente gera um Idempotency-Key
único para cada requisição e envia no cabeçalho ou até mesmo no corpo do Payload .
2️⃣ O servidor verifica se essa chave já foi processada antes:
- Se sim, retorna a resposta armazenada.
- Se não, processa a requisição e armazena o resultado.
Exemplo de implementação
python
CopyEdit
from flask import Flask, request, jsonify
app = Flask(__name__)
idempotency_store = {} # Simulando um banco de dados
@app.route('/payment', methods=['POST'])
def process_payment():
idempotency_key = request.headers.get('Idempotency-Key')
if not idempotency_key:
return jsonify({"error": "Idempotency-Key is required"}), 400
if idempotency_key in idempotency_store:
return jsonify(idempotency_store[idempotency_key]) # Retorna a resposta salva
# Processa o pagamento (simulação)
response = {"status": "success", "message": "Payment processed"}
idempotency_store[idempotency_key] = response # Salva o resultado
return jsonify(response)
if __name__ == '__main__':
app.run(debug=True)
✅ Como você pode ver acima, com essa pequena validação você pode evitar uma duplicação. Se a mesma requisição for enviada novamente com a mesma chave, o servidor retorna o mesmo resultado sem processar de novo.
** IMPORTANTE: Note, que aqui estou armazenando a chave de idepotencia em memória… nem preciso falar que isso não é uma boa idéia né ?! hehe. É muito comum o uso de database para isso, principalmente in-memory DB (Redis e memcached) devido o baixo tempo de resposta.
2. Idempotência em APIs Async / Assíncronas (Mensageria – Kafka, SQS, RabbitMQ)
Um sistema de mensageria pode reenviar a mesma mensagem várias vezes por falhas de rede ou reprocessamento. Precisamos garantir que uma mensagem não seja processada duas vezes. Para isso você pode implementar idempotência da seguinte forma:
1️⃣ Cada mensagem enviada para a fila deve conter um ID único.
2️⃣ O consumidor da fila deve armazenar os IDs já processados para evitar duplicações.
3️⃣ Antes de processar uma nova mensagem, o sistema verifica se o ID já foi tratado.
Exemplo de implementação
python
CopyEdit
import uuid
from queue import Queue
message_queue = Queue()
processed_messages = set() # Simulando um banco de dados de mensagens já processadas
def produce_message():
message_id = str(uuid.uuid4()) # Gera um ID único
message_queue.put({"id": message_id, "content": "Process this!"})
print(f"Message {message_id} sent to queue")
def consume_message():
while not message_queue.empty():
message = message_queue.get()
message_id = message["id"]
if message_id in processed_messages:
print(f"Skipping duplicate message {message_id}")
continue # Ignora mensagens duplicadas
# Processa a mensagem
print(f"Processing message {message_id}: {message['content']}")
processed_messages.add(message_id) # Marca como processada
produce_message()
consume_message()
consume_message() # Tentamos consumir de novo, mas a lógica impede duplicação
✅ Isso evita que uma mesma mensagem seja processada mais de uma vez, garantindo idempotência.
** IMPORTANTE: Note, que aqui estou armazenando a chave de idepotencia em memória… nem preciso falar que isso não é uma boa idéia né ?! hehe. É muito comum o uso de database para isso, principalmente in-memory DB (Redis e memcached) devido o baixo tempo de resposta.
3. Idempotência na regra de negócio
Nem todas as funcionalidades exigem idempotência absoluta. Algumas operações são naturalmente idempotentes por design. Por exemplo, se você precisa atualizar uma tabela no banco de dados sem considerar o valor anterior, rodar o mesmo UPDATE
uma ou cinco vezes terá o mesmo efeito, certo? Em alguns cenários, pode ser mais eficiente lidar com duplicações eventuais do que implementar uma validação rigorosa para todas as requisições ou mensagens. No entanto, nem sempre a solução é tão simples. Mas quando for, por que complicar se você pode simplificar?
Conclusão
Seja em APIs RESTful (síncronas) ou sistemas assíncronos (mensageria), idempotência é essencial para evitar efeitos colaterais indesejados. Portanto, sempre que estiver atuando com alguma funcionalidade, criando uma automação, software ou até mesmo um snippet de código, pergunte-se: “Se isso rodar de novo, o que acontece?”, a resposta a essa pergunta com certeza irá deixar bem claro se você precisa criar uma validação ou não.
E aí, você já implementou idempotência nos seus serviços? Me conta!
Aproveitando, Se você curte conteúdos como esse e quer aprender os padrões e as práticas para criar e manter sistemas escaláveis, resilientes e modernos, ao mesmo tempo que se torna uma autoridade no assunto, te convido a fazer parte da Comunidade de Arquitetura Descomplicada (CaD). Saiba mais em https://mugnos-it.com/cad/
Abraços,
Douglas Mugnos
MUGNOS-IT