Idempotent Consumer
Perspectiva y alcance
Un Idempotent Consumer aplica idempotency al procesamiento de mensajes: repetir una misma intención no multiplica el efecto de negocio relevante dentro de la frontera declarada. Este documento es una perspectiva de ese canónico, enfocada en handlers, reentregas y commits de consumidores.
La función puede ejecutarse varias veces y producir métricas o respuestas diferentes. Lo que debe quedar protegido es el efecto acordado, por ejemplo una reserva por pedido. No hace falta que todas las invocaciones tengan idéntico comportamiento auxiliar; sí hace falta identificar qué efectos importan.
Por qué aparecen repeticiones
Con at-least-once, un consumidor puede confirmar su trabajo y caer antes del acknowledgement. El broker no sabe que la base de datos terminó y entrega el mensaje otra vez. También puede haber publicaciones repetidas desde transactional-outbox, recuperación de conexiones o replay desde una DLQ.
Redelivery repite una entrega; duplicate publication puede crear otra entrada de transporte para el mismo evento. Una marca de «redelivered» no identifica todos los duplicados y un handler puede recibir dos copias simultáneamente. El diseño debe soportar la identidad de aplicación, no depender únicamente de indicadores del broker.
Propiedad, mecanismo y registro
| Concepto | Responsabilidad |
|---|---|
| idempotency | Propiedad del efecto al repetir la misma intención. |
| deduplication | Reconocer repeticiones y decidir qué trabajo suprimir. |
| inbox-pattern | Registro durable que puede coordinar reconocimiento y efectos locales. |
Un consumidor puede ser idempotente sin inbox. Un inbox puede implementar deduplicación; tenerlo no demuestra idempotencia de todos los efectos del handler. Enterprise Integration Patterns distingue explícitamente la deduplicación de diseñar una semántica que tolere repeticiones. Idempotent Receiver.
Dos estrategias
Idempotencia por semántica
Un consumidor que mantiene una proyección puede recibir el estado deseado y su versión. Reaplicar la misma versión no cambia otra vez esa proyección. Para soportar mensajes fuera de orden, la actualización debe rechazar versiones obsoletas; idempotencia por sí sola no resuelve ese problema.
Inventory Consumer también puede expresar «asegurar la reserva del pedido 42» en lugar de «restar una unidad». Si identidad de reserva, cantidad y cambio de disponibilidad se coordinan mediante las restricciones del dominio, repetir la intención conserva una sola reserva. No necesita una tabla separada de IDs de transporte.
Esta opción exige comprobar todos los efectos relevantes. Un upsert local repetible acompañado de un nuevo correo en cada intento solo protege el upsert. Tampoco puede fusionar pedidos distintos porque tengan la misma cantidad y producto.
Deduplicación explícita
Para un mensaje que incrementa, descuenta o inicia trabajo no naturalmente repetible, registrar su identidad puede evitar un segundo efecto. inbox-pattern describe el caso fuerte: marca de procesamiento y efecto local comparten una transacción, con arbitraje entre workers concurrentes.
El ID no tiene que vivir siempre en una tabla separada: una entidad de negocio puede conservar las identidades relevantes. La decisión depende de cardinalidad, retención y frontera del store. Richardson muestra ambas ubicaciones de los IDs procesados. Idempotent Consumer.
Identidad estable y ejemplo
Order Service envía evt-42-created para una unidad. Tras recibirlo, Inventory Consumer confirma la reserva y pierde la conexión antes del acknowledgement. La reentrega debe continuar representando el mismo evento, aunque el transporte cambie su identificador de entrega.
El consumidor puede reconocer la reserva de negocio existente o encontrar la identidad del evento en su inbox. En ambos casos, confirma al transporte sin descontar otra unidad. Si todavía no puede demostrar el commit, consulta o reintenta bajo la misma protección; no declara éxito por haber enviado una consulta.
Un nuevo evento evt-42-cancelled no es un duplicado del anterior. Dos eventos con IDs distintos que representan accidentalmente la misma intención requieren una regla adicional de identidad de negocio: la deduplicación por ID de mensaje no descubre esa equivalencia por sí sola.
Si un ID se reutiliza con parámetros incompatibles, hay que resolver el conflicto, no ocultarlo como repetición. El ámbito del consumidor también importa: reservas e informes pueden procesar legítimamente el mismo evento para producir efectos diferentes.
Coordinación transaccional y fallos
| Fallo | Qué necesita el consumidor |
|---|---|
| Crash antes del commit local | Permitir reintento, sin dejar una marca definitiva falsa. |
| Commit local, acknowledgement perdido | Reconocer el efecto confirmado en la reentrega. |
| Dos workers reciben el mismo evento | Una restricción o transición atómica que arbitre ambos. |
| Evento llega después de borrar la protección | Política de edad, identidad de negocio durable o aceptación explícita del riesgo. |
Separar «verificar ID», «aplicar» y «recordar ID» en commits independientes conserva ventanas de pérdida o duplicación. Un cache en memoria puede optimizar consultas, pero reiniciar o cambiar de instancia no debe borrar la protección que el contrato promete.
Los fallos permanentes no se arreglan suprimiendo todas las repeticiones. El consumidor necesita registrar qué terminó, qué sigue pendiente y qué debe inspeccionarse. dead-letter-queue cambia el destino operacional del mensaje; no vuelve exitoso el efecto que faltaba.
Efectos fuera del commit local
Enviar un correo, invocar una API o publicar otro mensaje añade otra frontera. El inbox local no impide que la llamada externa ocurra dos veces si su resultado se perdió, ni asegura que ocurra si se cae después de registrar el procesamiento.
Para publicar, transactional-outbox conserva una intención junto al efecto local. Para una API, puede usarse una identidad aceptada y coordinada por el receptor, si su contrato la ofrece. Cuando no existe esa cooperación, hacen falta consulta, reconciliación o recuperación semántica; no una promesa inventada de exactly-once.
Cuándo aplicar esta perspectiva
Aplicarla al diseñar consumidores, pasos de saga-pattern y replay operacional. Una saga necesita idempotencia en pasos y compensaciones que puedan repetirse, pero un handler idempotente no decide el orden ni la recuperación de toda la saga.
Evitar un inbox adicional cuando la semántica del dominio ya ofrece la protección necesaria. Evitar también afirmar que la deduplicación del broker sustituye esta revisión: puede cubrir otra identidad y un horizonte distinto. delivery-guarantees delimita esas fronteras; reliable-messaging conecta publicación, procesamiento y reparación.