Estado: Aceptado
Fecha: 2026-05-24
Responsable: Arquitectura
Disposición Evolith: Propuesto para adopción en Evolith — la deduplicación de requests a nivel HTTP es neutra en cuanto al runtime; aplicable a cualquier satélite ASP.NET Core
Relacionados:
Los command handlers de UMS son operaciones que mutan estado: crean agregados, actualizan estado, publican domain events al outbox e interactúan con proveedores de identidad externos. Un reintento de red, un doble toque desde un cliente móvil, o un paso de compensación de saga que re-emite un comando puede ejecutar la misma operación dos veces — creando tenants duplicados, cargos dobles, o estado en conflicto.
La capa de dominio hace cumplir las invariantes de negocio (p.ej., “el código de tenant ya existe”), pero devolver un error de dominio en la segunda llamada idéntica no siempre es el comportamiento correcto. El cliente puede esperar la misma respuesta exitosa que en la primera llamada.
Un IPipelineBehavior de MediatR se ejecuta después de la deserialización y validación — requiere un store de idempotencia persistente por tipo de handler. El middleware a nivel HTTP:
Implementar la deduplicación de requests como un middleware ASP.NET Core que lee el header Idempotency-Key, cachea la primera respuesta y la reproduce literalmente para requests duplicados.
| Escenario | Respuesta |
|---|---|
Sin header Idempotency-Key |
Pasa — la clave es opcional |
| Clave nueva, primer request | Ejecuta el pipeline, cachea la respuesta (TTL: 24h), devuelve resultado |
| Clave conocida, request completado | Devuelve la respuesta cacheada inmediatamente — handler NO invocado |
| Clave conocida, request en vuelo | Devuelve HTTP 409 “request already in progress” |
| Método no mutante (GET, DELETE) | Pasa — idempotente por naturaleza |
Solo POST, PUT, PATCH. GET y DELETE pasan incondicionalmente.
IMemoryCache (por defecto nodo único). Para despliegues multi-réplica, reemplazar con IDistributedCache (Redis o SQL Server) para compartir estado entre pods.
UUID v4 generado por el cliente, p.ej. 550e8400-e29b-41d4-a716-446655440000. El middleware no genera claves — el cliente es responsable de generarlas y reintentar con la misma clave.
24 horas (configurable vía IdempotencyOptions). Tras la expiración del TTL, una clave re-enviada se trata como un nuevo request.
// Program.cs / DependencyInjection
services.AddMemoryCache(); // requerido para IdempotencyStore de nodo único
app.UseIdempotency(); // debe ir después de UseCorrelationId, antes del routing
UseCorrelationId
→ UseSessionTracking
→ UseGlobalExceptionHandler
→ UseIdempotency ← aquí
→ UseRateLimiter
→ Routes
La posición después de UseGlobalExceptionHandler garantiza que las excepciones durante la ejecución del pipeline sean capturadas y no cacheadas. La posición antes del routing garantiza que la reproducción ocurra antes de la selección del endpoint.
IDistributedCache respaldado por Redis en producción2xx — las respuestas de error no se cachean (el cliente debe reintentar en caso de fallo con la misma clave)Idempotency-Key es un concepto a nivel de request; no puede prevenir eventos duplicados si el mismo comando es emitido con claves diferentes por un cliente con mal comportamientoIdempotencyMiddleware — sin import específico de UMS; depende solo de IMemoryCache y abstracciones de ASP.NET CoreIdempotencyOptions — POCO simple con TTL y flag habilitado/deshabilitadoUseIdempotency()| Registro ADR | CP-07 Idempotencia |