Skip to content

La idempotencia es un concepto crucial en sistemas distribuidos y servicios web, ya que garantiza que múltiples solicitudes idénticas tengan el mismo efecto que una sola.

Notifications You must be signed in to change notification settings

raulrobinson/idempotency-pattern

Repository files navigation

LocalStack - A fully functional local cloud stack

Patron de Idempotencia

Concepto, funcionamiento e implementacion práctica

Introducción

La idempotencia es un concepto crucial en sistemas distribuidos y servicios web, ya que garantiza que múltiples solicitudes idénticas tengan el mismo efecto que una sola. Este patrón es especialmente útil en escenarios como el procesamiento de pagos, la creación de pedidos y otras operaciones donde las solicitudes duplicadas pueden tener consecuencias imprevistas.

Funcionamiento

El patrón de idempotencia es un patrón arquitectónico utilizado para garantizar que una misma operación ejecutada repetidas veces produce siempre el mismo resultado, sin efectos secundarios adicionales.

En palabras simples:

  • Idempotencia = aunque llames varias veces, solo se procesa una vez...

Ejemplos típicos:

  • Evitar que un pago se procese dos veces si el usuario presiona el botón Pagar repetidamente.
  • Evitar que un microservicio ejecute dos veces la misma transacción cuando un cliente reintenta.
  • Evitar duplicados cuando hay fallos de red o timeouts.

¿Por qué es necesario este patrón?

En microservicios, especialmente usando WebFlux / Reactor / WebClient / AWS / colas / eventos:

  • Hay reintentos automáticos.
  • Hay duplicados inevitables.
  • Hay timeouts.
  • La red NO es confiable. (fallos, latencias, reconexiones)

Sin idempotencia, una operación crítica puede ejecutarse N veces.

  • Problemas que soluciona la idempotencia
    • Doble débito en un pago.
    • Duplicación de órdenes.
    • Reprocesamiento de eventos.
    • Creación de registros repetidos.
    • Inconsistencias en bases distribuidas.

¿Cómo funciona el patrón?

El patrón se basa en 4 conceptos:

  1. Idempotency-Key (clave única por operación)

    • El cliente envía en el header: Idempotency-Key: 11111111-aaaa-bbbb-cccc-000000000001
    • Esa clave identifica la intención del cliente.
  2. Registro de la operación en estado IN_PROGRESS

    • Cuando llega una solicitud nueva:
      • Se verifica si el key existe.
      • Si NO existe → se crea un registro:
        • state = IN_PROGRESS
        • requestHash =
        • Y se guarda en Redis + DynamoDB.
  3. Se ejecuta la operación SOLO si somos el owner

    Si nadie más está ejecutando este mismo Idempotency-Key, el servicio ejecuta el caso de uso.

  4. Se guarda el resultado FINAL

    • Cuando termina:

      • state = COMPLETED
      • responseJson = { ... }
      • httpStatus = 200
    • Si falló:

      • state = FAILED
      • responseJson = { error }

¿Qué pasa si la solicitud se repite?

El servicio revisa:

  • Si ya existe:
    • COMPLETED → devuelve la MISMA respuesta.
    • FAILED → devuelve el MISMO error.
    • IN_PROGRESS → devuelve 409 → “Transacción en progreso”
  • NO vuelve a ejecutar la operación física.

Ejemplo real en un banco por ejemplo

  • Cliente presiona “Pagar”

  • El móvil reintenta 3 veces por mala señal.

  • El servidor recibe 3 requests con el mismo Idempotency-Key.

    • Resultado:
      • El débito solo ocurre 1 vez.
      • Las 3 solicitudes reciben la misma respuesta.
      • No hay doble cobro.
      • No hay reprocesamiento.
      • No hay efectos secundarios adicionales.

¿De qué se compone este patrón en mi microservicio?

  • Redis: respuesta rápida, TTL corto.
  • DynamoDB: fuente de verdad, condición attribute_not_exists(pk).
  • Hash del request: evita reuso con payload diferente.
  • Estados:
    • IN_PROGRESS
    • COMPLETED
    • FAILED
  • Cache final del resultado.
  • Thread-safety distribuido.
  • Detección de concurrencia. (solo 1 owner)

El siguiente diagrama de secuencia ilustra el flujo de gestión de solicitudes idempotentes mediante una combinación de Redis para el almacenamiento en caché de ruta rápida y DynamoDB para el almacenamiento duradero. Este patrón garantiza que las solicitudes repetidas con la misma clave de idempotencia produzcan resultados consistentes sin efectos secundarios no deseados.

sequenceDiagram
    autonumber

    participant C as Client
    participant H as Handler
    participant F as IdempotencyFilter
    participant S as IdempotencyService
    participant R as Redis
    participant D as DynamoDB
    participant U as UseCase (Action)

    Note over C: Request with Idempotency-Key

    C->>H: POST /transactions<br/>headers: Idempotency-Key
    H->>F: apply(request, body, action, type)

    F->>S: execute(idemKey, body, action, type)

    Note over S: 1. Fast-path Redis lookup
    S->>R: GET idem:{key}
    alt Redis HIT (COMPLETED/FAILED/IN_PROGRESS)
        R-->>S: record
        S->>S: resolveFromExisting()
        alt COMPLETED
            S-->>F: return cached response
            F-->>H: response
            H-->>C: 200 OK (idempotent)
        else FAILED
            S-->>F: throw BusinessException
            F-->>H: error
            H-->>C: HTTP error cached
        else IN_PROGRESS
            S-->>F: IDEMPOTENCY_IN_PROGRESS
            F-->>H: 409 Conflict
            H-->>C: 409 Conflict
        end
    else Redis MISS
        Note over S: 2. Try insert IN_PROGRESS in Dynamo
        S->>D: PutItem IF attribute_not_exists(pk)
        alt Dynamo CREATED (owner)
            D-->>S: OK

            Note over S: sync IN_PROGRESS to Redis
            S->>R: SET IN_PROGRESS<br/>TTL short
            R-->>S: OK

            Note over S: 3. Execute action
            S->>U: process(body)
            U-->>S: response

            Note over S: 4. Save COMPLETED
            S->>D: UpdateItem<br/>state=COMPLETED,<br/>responseJson,httpStatus
            S->>R: SET COMPLETED<br/>TTL long

            S-->>F: response
            F-->>H: response
            H-->>C: 200 OK (first execution)
        else Dynamo EXISTS (not owner)
            D-->>S: ConditionalCheckFailed

            Note over S: Read truth from Dynamo
            S->>D: GetItem
            D-->>S: existing record

            S->>R: SET cache copy
            R-->>S: OK

            S->>S: resolveFromExisting()

            alt COMPLETED exists
                S-->>F: return existing response
                F-->>H: cached response
                H-->>C: 200 OK (idempotent)
            else FAILED exists
                S-->>F: throw cached error
                F-->>H: error
                H-->>C: cached error
            else still IN_PROGRESS
                S-->>F: IDEMPOTENCY_IN_PROGRESS
                F-->>H: 409
                H-->>C: 409 Conflict
            end
        end
    end
Loading

docs:

  • postman (dir): Contiene archivos de colección y entorno de Postman para probar el patrón de idempotencia.
  • docs:md: Este archivo README con una explicación detallada y un diagrama de secuencia.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

La idempotencia es un concepto crucial en sistemas distribuidos y servicios web, ya que garantiza que múltiples solicitudes idénticas tengan el mismo efecto que una sola.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published