Idempotência: O Guia Definitivo para Sistemas Distribuídos Resilientes
Imagine que você está em um aplicativo de banco e clica em “Confirmar Pagamento”. A tela trava, a internet oscila e você recebe uma mensagem de erro. Bate o desespero: “Será que pagou?”. Você clica de novo. Sem Idempotência, você acabaria de pagar a mesma conta duas vezes.
Em sistemas distribuídos, falhas de rede não são uma exceção, são uma certeza. A idempotência é a garantia matemática e arquitetural de que, não importa quantas vezes você tente realizar a mesma operação, o efeito colateral no sistema será o mesmo de uma única execução bem-sucedida.
O Conceito Matemático na Computação
Matematicamente, uma função é idempotente se f(x) = f(f(x)). No mundo das APIs, isso significa que múltiplas requisições idênticas devem produzir o mesmo estado final no servidor.
Verbos HTTP e Idempotência Nativa
Nem todo método HTTP nasce igual:
- GET, HEAD, OPTIONS: São seguros e idempotentes (não alteram estado).
- PUT: É idempotente. Se você atualiza o nome de um usuário para “Augusto” dez vezes, o nome continuará sendo “Augusto”.
- DELETE: É idempotente. Deletar algo que já foi deletado resulta no mesmo estado final (o recurso não existe).
- POST: Não é idempotente. Criar um recurso duas vezes geralmente resulta em dois registros diferentes. É aqui que precisamos da nossa lógica customizada.
A Chave de Idempotência (Idempotency Key)
A maneira mais robusta de implementar idempotência em sistemas distribuídos é através de uma Chave de Idempotência. O cliente gera um identificador único (geralmente um UUID) e o envia no cabeçalho da requisição.
Exemplo de Fluxo no Servidor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@PostMapping("/v1/payments")
public ResponseEntity<PaymentResponse> processPayment(
@RequestHeader("Idempotency-Key") String idempotencyKey,
@RequestBody PaymentRequest request) {
// 1. Verificar se a chave já existe no cache (ex: Redis)
var cachedResponse = idempotencyService.getResponse(idempotencyKey);
if (cachedResponse.isPresent()) {
return ResponseEntity.ok(cachedResponse.get());
}
// 2. Tentar adquirir um lock distribuído para esta chave
try (var lock = distributedLock.acquire(idempotencyKey)) {
// 3. Processar a lógica de negócio
var response = paymentService.execute(request);
// 4. Salvar o resultado no cache com um TTL (ex: 24h)
idempotencyService.saveResponse(idempotencyKey, response);
return ResponseEntity.status(201).body(response);
} catch (LockAcquisitionException e) {
// 5. Se não conseguiu o lock, significa que há outra requisição em curso
return ResponseEntity.status(409).build(); // Conflict
}
}
Funcionamento Interno e Desafios
Race Conditions
Não basta apenas checar se a chave existe. Em sistemas de alta concorrência, duas requisições com a mesma chave podem chegar ao mesmo tempo em instâncias diferentes da sua API. Se ambas checarem o banco e virem que a chave não existe, ambas processarão o pagamento.
A solução: Use um INSERT atômico em um banco de dados ou um SET NX (set if not exists) no Redis. Isso garante que apenas uma thread ganhe o direito de processar aquela chave.
Armazenamento: Banco de Dados ou Redis?
- Redis: Excelente para performance. Você pode definir um TTL (Time To Live) para que as chaves expirem após 24 ou 48 horas.
- Banco de Dados (Relacional): Oferece garantias ACID. Você pode salvar a chave de idempotência na mesma transação que altera o saldo do usuário, garantindo integridade absoluta.
Curiosidades Técnicas
- 409 Conflict vs 200 OK: Alguns puristas defendem que se uma requisição está sendo processada, você deve retornar
409 Conflict. Se ela já terminou, você retorna o resultado original com200 OK. - Determinismo: Para a idempotência funcionar, sua lógica deve ser determinística. Se o resultado de um pagamento depende de um número aleatório gerado no momento do processamento, você terá problemas ao retornar a resposta em cache.
Aplicações Práticas além de APIs
A idempotência também é fundamental em Consumidores de Mensagens (Kafka/RabbitMQ). Como o Kafka garante a entrega at-least-once, seu consumidor pode receber a mesma mensagem duas vezes. A lógica de “Check-and-Insert” usando o ID da mensagem como chave de idempotência é o que evita o processamento duplicado.
Uma reflexão final
Na próxima vez que você estiver desenhando um novo endpoint que altera o estado do sistema, pare um segundo e pergunte-se: “Se o cliente enviar esta requisição e minha resposta se perder no caminho, o que acontecerá quando ele tentar novamente?”. Se a resposta envolver duplicidade de dados ou inconsistência financeira, seu trabalho ainda não terminou. A idempotência é a rede de segurança que permite que sistemas imperfeitos operem de forma perfeita.