Idempotência no .NET
O que é Idempotência?
Idempotência refere-se à propriedade de que o resultado de uma operação não muda quando aplicada uma ou mais vezes. Em termos de desenvolvimento de software, um método idempotente é aquele que sempre retorna o mesmo resultado (ou efeito colateral único), independentemente de quantas vezes seja chamado com os mesmos dados.
Exemplo simples
A requisição
GET /produto/3
sempre retornará o produto com ID 3, sendo, portanto, idempotente.
Por que Idempotência é importante?
Ela é essencial em arquiteturas distribuídas e cenários onde podem ocorrer:
Reenvio automático por timeouts de cliente / gateway
Falhas de rede (cliente não sabe se o servidor processou)
Retentativas de mensageria / filas
Operações idempotentes garantem consistência mesmo quando a requisição é enviada múltiplas vezes.
Exemplo de serviço
Imagine um serviço de pagamento. Se, por falha de rede, o cliente reenviar a confirmação, um endpoint idempotente garante que o pagamento seja processado apenas uma vez.
Padrão geral de implementação
Identificar requisições unicamente: Introduza uma chave exclusiva (ex:
Idempotency-Key
) no cabeçalho.Persistir a chave junto com a resposta (ou um marcador de processamento) em armazenamento confiável/cache.
Interceptar novas requisições: Se a chave já existe, retornar a resposta previamente gerada (ou estado final) em vez de processar de novo.
Boas práticas
Gere a chave no cliente (ex: UUID) antes da primeira tentativa.
Defina uma expiração adequada: longa o suficiente para cobrir retentativas, curta para não crescer indefinidamente.
Em alta escala, use cache distribuído (Redis, por exemplo) e não apenas memória local.
Usando a biblioteca IdempotentAPI
IdempotentAPI facilita a implementação em ASP.NET Core para métodos que normalmente não são idempotentes (POST / PATCH). Ela usa um filtro que serializa os parâmetros do handler para compor a chave e armazenar a resposta.
Passos básicos
Instalar:
Registrar serviços em
Program.cs
:
Adicionar cache distribuído (exemplo in-memory):
Marcar objetos de resposta como [Serializable] (quando necessário):
Opcional: marcar controllers:
Minimal API:
Testando (fluxo genérico)
Chame sem
Idempotency-Key
(deve falhar se configurado para exigir).Chame com uma chave: ex.
1234
.Repita com a mesma chave: resposta reaproveitada.
Troubleshooting: Erro 500 (Referências Circulares) com IdempotentAPI
Cenário do problema
Ao chamar POST /todoitems/
com cabeçalho Idempotency-Key
, a aplicação retornava HTTP 500. A causa: falha de serialização por referências circulares quando o filtro da biblioteca tentava serializar todos os parâmetros do handler.
Handler original (inadequado):
O DbContext
(TodoDb
) contém um grafo complexo (ChangeTracker, Services, Proxies) que inclui referências circulares. Ao entrar na serialização para gerar a chave / armazenar a resposta, ocorre a exceção.
Correção aplicada
Arquivo | Alteração | Motivo |
---|---|---|
| Removido | Evita serializar o DbContext |
| Ajustado | Corrigir |
Handler após fix
Como reproduzir o erro antigo
Validando o fix
Subir a app:
Requisição inicial:
Esperado: 201 Created.
Repetir (mesma chave): reutiliza resposta (cache idempotente).
Nova chave:
Esperado: novo registro.
Por que funciona
O filtro agora só serializa o corpo simples (
TodoModel
).Objetos EF Core não entram na serialização/hash.
Elimina ciclos e a exceção.
Boas práticas extras
Evitar injetar diretamente objetos pesados (DbContext, HttpContext, services) em handlers quando a lib serializa parâmetros.
Usar DTOs/records para inputs e outputs.
Criar gerador de chave custom se precisar normalizar corpo (ordenar propriedades, remover campos voláteis, etc.).
Melhorias futuras
Testes de integração (WebApplicationFactory) cobrindo: primeira execução, repetição mesma chave, nova chave.
Logs estruturados indicando: HIT / MISS de idempotência.
Estratégia de invalidação / TTL configurável via
IdempotencyOptions
.
Conclusão
Idempotência reduz efeitos colaterais indesejados em operações não idempotentes e aumenta robustez de APIs. A biblioteca IdempotentAPI
abstrai o mecanismo de caching e repetição, mas requer atenção aos parâmetros serializados. Evitar incluir serviços complexos garante funcionamento estável.