Cómo diseñar APIs REST idempotentes y versionadas
Despliegas una nueva función y, de repente, tu API recibe peticiones POST duplicadas de clientes que reintentan tras un timeout. Tu base de datos ahora tiene dos pedidos idénticos. O lanzas un cambio incompatible y las apps móviles fallan porque esperaban el formato de respuesta anterior. Estos son dos de los problemas más comunes y dolorosos en el diseño de APIs: operaciones no idempotentes y cambios incompatibles. Esta guía te muestra cómo resolver ambos con patrones prácticos de idempotencia y versionado en APIs REST.
Por qué importa la idempotencia en las APIs REST
Idempotencia significa que hacer la misma petición varias veces tiene el mismo efecto que hacerla una sola vez. Los métodos HTTP tienen semánticas de idempotencia definidas: GET, HEAD, PUT, DELETE y OPTIONS son idempotentes, mientras que POST y PATCH no lo son. Pero las APIs del mundo real a menudo necesitan aceptar POST para operaciones como crear recursos o procesar pagos. Los fallos de red, los timeouts de los clientes y la lógica de reintentos pueden causar duplicados. Sin idempotencia, te arriesgas a cargos duplicados, registros duplicados o un estado inconsistente.
Técnicas para APIs REST idempotentes
1. Usa claves de idempotencia para peticiones POST
El enfoque más común es permitir que los clientes envíen una clave única (por ejemplo, un UUID) en una cabecera Idempotency-Key. El servidor almacena la clave y el resultado de la operación. Si recibe la misma clave de nuevo, el servidor devuelve el resultado almacenado en lugar de reejecutar la operación.
POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"amount": 1000,
"currency": "USD"
}
Pasos de implementación:
- El cliente genera una clave única (UUID v4) para cada operación lógica.
- El servidor comprueba si la clave existe en un almacén persistente (por ejemplo, Redis o una base de datos).
- Si no existe, procesa la petición y almacena la clave con el estado y el cuerpo de la respuesta.
- Si existe, devuelve la respuesta almacenada sin volver a procesarla.
Establece una expiración razonable (por ejemplo, 24 horas) para evitar un crecimiento ilimitado del almacenamiento.
2. Aprovecha las peticiones condicionales
Para las actualizaciones, usa las cabeceras ETag e If-Match para evitar actualizaciones perdidas. El servidor devuelve un ETag que representa el estado actual. El cliente lo reenvía con If-Match; si el recurso ha cambiado, el servidor devuelve 412 Precondition Failed. Esto hace que las peticiones PUT sean seguras frente a condiciones de carrera.
PUT /articles/123
If-Match: "abc123"
Content-Type: application/json
{"title": "Updated title"}
3. Diseña PUT para una idempotencia natural
Prefiere PUT sobre POST cuando el cliente pueda determinar la URL del recurso. Por ejemplo, PUT /users/{userId} es naturalmente idempotente porque las llamadas repetidas sobrescriben el mismo recurso. Usa POST solo cuando el servidor asigne el ID.
4. Gestiona la detección de duplicados con restricciones únicas
Combina las claves de idempotencia con restricciones únicas en la base de datos sobre claves de negocio (por ejemplo, el número de pedido). Si un duplicado se cuela, la base de datos lo rechaza y puedes devolver un 409 Conflict con un mensaje claro.
Estrategias de versionado para APIs REST
Las APIs evolucionan. Campos nuevos, comportamiento modificado o endpoints eliminados pueden romper a los clientes existentes. El versionado te permite introducir cambios sin interrumpirlos. Existen varias estrategias comunes:
| Estrategia | Ejemplo | Ventajas | Inconvenientes |
|---|---|---|---|
| Ruta URI | /v1/users |
Explícito, fácil de enrutar, compatible con caché | Las URLs cambian, puede generar desorden |
| Parámetro de consulta | /users?version=1 |
Simple, opcional | Fácil de omitir, no es RESTful |
| Cabecera personalizada | Accept-Version: v1 |
Mantiene las URLs limpias | Más difícil de probar en el navegador |
| Tipo de medio | Accept: application/vnd.api.v1+json |
Negociación de contenido, REST puro | Complejo, menos común |
Para la mayoría de los equipos, el versionado por ruta URI es la opción más pragmática. Es visible, fácil de documentar y funciona bien con API gateways y CDNs.
Cómo versionar sin romper a los clientes
- Empieza con v1 en la ruta desde el primer día. Añadir versionado más tarde es más difícil.
- Solo cambios aditivos dentro de una versión: nuevos campos opcionales, nuevos endpoints. Nunca elimines ni renombres campos.
- Deprecia con elegancia: anuncia el fin de vida, proporciona guías de migración y monitoriza el uso de las versiones antiguas.
- Usa cabeceras sunset:
Sunset: Sat, 31 Dec 2025 23:59:59 GMTpara informar a los clientes. - Mantén como máximo dos versiones activas para limitar la carga de mantenimiento.
Poniéndolo todo junto: un ejemplo práctico
Considera una API de pagos. Quieres crear un pago de forma idempotente y versionar el endpoint.
POST /v1/payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"amount": 1000,
"currency": "USD"
}
Lógica del servidor:
- Comprueba si
Idempotency-Keyexiste en Redis. - Si existe, devuelve la respuesta en caché (estado + cuerpo).
- Si no existe, procesa el pago, almacena la clave con la respuesta y devuelve 201 Created.
Para las actualizaciones, usa PUT con ETag:
PUT /v1/payments/123
If-Match: "xyz789"
Content-Type: application/json
{"amount": 1500}
Si el ETag no coincide, devuelve 412. Esto evita sobrescribir cambios concurrentes.
Errores comunes y cómo evitarlos
- Almacenar las claves de idempotencia para siempre: establece un TTL (por ejemplo, 24 h) para evitar la acumulación de almacenamiento.
- Ignorar las condiciones de carrera: usa operaciones atómicas (por ejemplo, Redis SETNX) para comprobar y establecer claves.
- Versionar demasiado tarde: añade /v1 desde el principio.
- Cambios incompatibles en versiones menores: trata cualquier cambio que altere la estructura de la respuesta como mayor.
- No documentar la idempotencia: indica claramente qué endpoints admiten Idempotency-Key.
Preguntas frecuentes
¿Qué es una API REST idempotente?
Una API REST idempotente garantiza que hacer la misma petición varias veces produce el mismo resultado que hacerla una sola vez. Esto es crucial para gestionar reintentos de forma segura, especialmente en métodos no idempotentes como POST.
¿Qué métodos HTTP son idempotentes?
GET, HEAD, PUT, DELETE, OPTIONS y TRACE son idempotentes. POST y PATCH no son idempotentes por defecto, pero puedes hacerlos idempotentes usando técnicas como las claves de idempotencia.
¿Cuál es la mejor forma de versionar una API REST?
El versionado por ruta URI (por ejemplo, /v1/users) es el enfoque más común y práctico. Es explícito, fácil de enrutar y funciona bien con caché y API gateways. Otras opciones incluyen parámetros de consulta, cabeceras personalizadas y tipos de medio, pero tienen sus compromisos.
¿Listo para probar las respuestas JSON de tu API? Usa nuestro JSON Formatter para validar y formatear payloads rápidamente.