Você sabe como implementar Idempotência em APIs Sync e Async?

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

guest
0 Comentários
Mais Velhos
Mais Novos Mais Votados
Inline Feedbacks
Veja todos comentários
0
Gostaria muito de saber sua opinião!x