¿Qué hace Estrategia de versionado de APIs HTTP?
TL;DR: Las versiones basadas en fecha ancladas por clave de API (modelo de Stripe) superan a
/v1/,/v2/para APIs públicas de larga duración porque permiten hacer pequeños cambios disruptivos sin forzar a los clientes a un nuevo árbol. Para APIs internas, la evolución aditiva junto con los encabezadosDeprecationySunset(RFC 9745, RFC 8594) suele ser suficiente. Siempre anuncie, siempre fije una fecha de descontinuación, nunca elimine sin más.
Salta a tu problema
| Síntoma | Sección |
|---|---|
| "Necesito romper un campo pero tengo 100.000 claves API" | Versiones ancladas por fecha |
"¿Debería usar /v1/ o Accept: application/vnd.foo.v2+json?" | Comparación de estrategias |
| "¿Cómo le digo a los clientes que un endpoint va a desaparecer?" | Deprecation + Sunset |
| "Mantener 6 versiones en el código nos está matando" | Transformadores de versiones |
| "API interna: ¿necesitamos siquiera versionado?" | Internas vs públicas |
Diagrama de decisión
flowchart TD
A[¿Necesito cambiar el comportamiento de la API?] --> B{¿Solo aditivo?<br/>nuevo campo, nuevo endpoint, nuevo parámetro opcional}
B -->|Sí| C[Simplemente publícalo<br/>no se necesita versionado]
B -->|No, es disruptivo| D{¿La API es pública<br/>+ muchos clientes opacos?}
D -->|No, es interna/pocos clientes| E[Coordinar migración<br/>+ encabezados Deprecation/Sunset]
D -->|Sí, es pública| F{¿Puedes crear una capa de<br/>compatibilidad por solicitud?}
F -->|Sí, tienes un transformador| G[Versión anclada por fecha<br/>Modelo de Stripe]
F -->|No, gran reescritura| H[Incremento de versión mayor en URI<br/>/v1 → /v2]
E --> I[Establecer Deprecation: @timestamp<br/>+ Sunset: HTTP-date<br/>+ Link: rel=deprecation]
G --> I
H --> I
1. Versiones ancladas por fecha (modelo de Stripe)
Stripe publicó su arquitectura en API versioning at Stripe:
La primera vez que un usuario hace una solicitud a la API, su cuenta se ancla automáticamente a la versión más reciente disponible, y a partir de entonces, cada llamada a la API que realice se le asigna esa versión implícitamente.
[Las versiones son] versiones rodantes que se nombran con la fecha en que se publican (por ejemplo,
2017-05-24).
Las propiedades clave:
| Propiedad | Por qué funciona |
|---|---|
| Versión predeterminada por cuenta (establecida en la primera llamada) | Los nuevos clientes se anclan automáticamente a la última versión; los clientes existentes no se rompen cuando publicas un cambio |
El encabezado Stripe-Version anula el anclaje por solicitud | Permite una migración gradual del cliente: el SDK puede optar por una nueva versión antes que la cuenta |
Nombres basados en fecha (2024-04-10) | Transmite actualidad; no hay debate sobre qué significa "v3"; permite arbitrariamente muchos pequeños cambios disruptivos en lugar de acumularlos para una gran versión v3 |
| Ruta de actualización en el panel | El cliente puede previsualizar el diff y luego actualizar la versión predeterminada de su cuenta |
Este es el único enfoque que escala para APIs públicas verdaderamente longevas (Stripe tiene versiones que se remontan una década). Te cuesta complejidad interna (el patrón de transformador en §4) pero ahorra a tus clientes el perpetuo ciclo de migración /v1/ → /v2/.
2. Comparación de estrategias de versionado
| Estrategia | Dónde vive la versión | A favor | En contra | Mejor para |
|---|---|---|---|---|
Segmento URI (/v1/, /v2/) | Ruta | Amigable con caché (diferente URL = diferente entrada de caché); configuración cero del cliente | No permite cambios disruptivos pequeños; fuerza una bifurcación del árbol; las URLs ya no son "identificadores de recursos estables" (según Fielding) | APIs internas, APIs públicas que rara vez cambian |
Encabezado personalizado (Stripe-Version: 2024-04-10) | Encabezado de solicitud | Muchísimas versiones económicas; granularidad por solicitud | No se pueden compartir URLs con la versión incluida; más difícil de probar en la barra de direcciones del navegador | APIs públicas a gran escala |
Tipo de medio Accept (Accept: application/vnd.foo.v2+json) | Encabezado Accept estándar | "Correcto según especificación" HTTP; reutiliza la maquinaria de negociación de contenido | Incómodo de configurar; el soporte de herramientas varía; la depuración con curl es verbosa | APIs hipermedia, diseños profundamente RESTful |
Parámetro de consulta (?version=2) | URL | Fácil de probar; visible en registros | Contamina la URL; semánticamente incorrecto (la versión no es una propiedad del recurso) | Solo para prototipado rápido |
| Sin versionado, solo aditivo | n/a | Sin sobrecarga; sin coordinación con el cliente | Nunca se puede hacer un cambio disruptivo sin un nuevo endpoint | Microservicios internos con despliegue compartido |
Elige seleccionando tu restricción:
- "Hacemos pequeños cambios disruptivos con frecuencia" → encabezado basado en fecha (Stripe)
- "Hacemos una gran reescritura cada 5 años" → segmento URI (
/v1/,/v2/) - "Nunca rompemos, solo añadimos" → sin versionado + evolución aditiva
3. El par de encabezados Deprecation/Sunset
Dos RFCs de IETF cubren la señal del ciclo de vida:
Encabezado Sunset — RFC 8594
El valor de Sunset es una marca de tiempo HTTP-date, según lo definido en la Sección 7.1.1.1 de [RFC7231], y DEBERÍA ser una marca de tiempo futura.
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Indica "se espera que el recurso deje de responder en un punto específico del futuro". Los clientes DEBERÍAN tratar la marca de tiempo como una sugerencia, no como un contrato estricto.
Encabezado Deprecation — RFC 9745
Deprecation: @1735689599
Sunset: Sun, 31 Dec 2026 23:59:59 GMT
Link: <https://api.example.com/docs/migrate-v1-to-v2>; rel="deprecation"
El valor de Deprecation es una marca de tiempo Unix (segundos, con prefijo @ según Structured Fields). Puede estar en el pasado ("ya obsoleto") o futuro ("será obsoleto"). Los requisitos imperativos (MUST):
- DEBE usar el formato de fecha structured-field según RFC 9651
SunsetNO DEBE ser anterior aDeprecation— la especificación es explícita; es una restricción de orden temporal- DEBERÍA incluir un
Linkconrel="deprecation"que apunte a la documentación de migración
El acto de enviar Deprecation no cambia el comportamiento del recurso — es una señal, no una degradación. Los servidores DEBERÍAN mantener el recurso funcional hasta la fecha de Sunset (salvo emergencias).
Cronograma recomendado
T+0 Primer Deprecation: encabezado enviado en producción
T+30d Anuncio público (changelog, correo electrónico, documentación)
T+90d Agregar registros de respuesta / métricas sobre el uso de la ruta obsoleta
T+180d Fecha de Sunset establecida a 90 días vista
T+270d Llega la fecha de Sunset: el endpoint devuelve 410 Gone con enlace de migración
Para cambios disruptivos menores en una API anclada a una versión mayor: 6 meses es la mediana. Para descontinuaciones completas de v1 → v2: 12-24 meses.
4. El patrón de transformador de versiones
La arquitectura interna de Stripe resuelve el problema de "tenemos 50 versiones en producción":
[El sistema utiliza] clases de recursos de API que definen las estructuras de respuesta actuales de la API, combinadas con módulos de cambio de versión que encapsulan transformaciones incompatibles hacia atrás. Al procesar las respuestas, el sistema recorre hacia atrás en el tiempo y aplica cada módulo de cambio de versión que encuentra hasta alcanzar la versión objetivo.
La forma:
// resource: la representación canónica (más reciente)
const charge = {
id: 'ch_123',
amount: 1000,
payment_method_details: { card: { brand: 'visa', last4: '4242' } },
}
// Cada cambio disruptivo es un módulo transformador:
const v_2024_03_01 = {
// Elimina el anidamiento 'card' dentro de payment_method_details, aplana los campos
apply(resource) {
return {
...resource,
card_brand: resource.payment_method_details?.card?.brand,
card_last4: resource.payment_method_details?.card?.last4,
}
},
}
const v_2023_10_15 = {
// Renombra 'amount' a 'amount_cents'
apply(resource) {
const { amount, ...rest } = resource
return { ...rest, amount_cents: amount }
},
}
// El pipeline aplica los transformadores en orden cronológico inverso
// hasta alcanzar la versión solicitada
function transform(resource, requestedVersion) {
const chain = transformers.filter(t => t.date > requestedVersion)
return chain.reduceRight((r, t) => t.apply(r), resource)
}
Los beneficios:
- El código central se mantiene moderno — cada endpoint se escribe contra el esquema más reciente; no hay
if (version < ...)disperso en la lógica de negocio. - Cada cambio disruptivo es un archivo — fácil de revisar, fácil de probar de forma aislada, fácil de eliminar cuando el último usuario lo supere.
- Telemetría — puedes contar cuántos transformadores se activan por día para planificar descontinuaciones según el uso real.
El costo: construir el framework de transformadores. Vale la pena si tienes >3 cambios disruptivos por año y >10.000 consumidores de API; es excesivo para una API interna con <50 clientes.
5. APIs internas vs públicas
La asimetría importa:
| Interna | Pública | |
|---|---|---|
| ¿Despliegue coordinado posible? | Sí — intercambio atómico | No — los clientes despliegan de forma independiente |
| Evolución del esquema | Aditivo + mensaje en Slack | Versionado estricto + correo + paneles + publicación en blog |
| Ventana de descontinuación | Días a semanas | 6-24 meses |
| Necesidad de versionado | A menudo ninguna — solo encabezados Deprecation | Obligatorio |
| Mejor estrategia | Evolución aditiva; versión URI solo en reescritura | Encabezados anclados por fecha (si la escala lo justifica), URI para reescrituras mayores |
La trampa: las empresas tratan todas las APIs como "públicas" y construyen la infraestructura pesada de versionado para una API utilizada solo por 3 servicios internos. La trampa inversa: una API interna filtrada tiene consumidores externos y no puedes romperla realmente.
Defensa: convierte "público" en una etiqueta binaria en el servicio. Los servicios públicos reciben toda la ceremonia de versionado; los servicios internos no, pero reciben una supervisión agresiva de quién los llama.
Antipatrones
| Antipatrón | Por qué muerde | Solución |
|---|---|---|
Pasar de /v1/ a /v2/ por un único cambio disruptivo | Obliga a los clientes a migrar todo por un campo | Versión basada en fecha O evolución aditiva |
Eliminar endpoints sin encabezado Sunset | Los clientes se rompen sin previo aviso; las solicitudes de soporte explotan | Siempre emparejar la eliminación con Deprecation 90+ días antes |
Enviar Deprecation pero sin Link | Los clientes saben que está obsoleto pero no qué hacer | Siempre incluir Link: <ruta-documentacion>; rel="deprecation" |
| Exenciones de versión codificadas por cliente | Se vuelve insostenible; no se puede razonar sobre el comportamiento | Usar el patrón de transformador; las "exenciones" se convierten en anclajes de versión |
| Versionar APIs internas como si fueran públicas | Sobrecarga de procesos sin beneficio | Evolución aditiva + despliegues atómicos + Slack |
Usar semver (v1.2.3) en APIs REST | Implica un significado que no aplica (¿qué es un "parche" para un esquema JSON?) | Nombres basados en fecha o solo mayor (v1) |
| Fecha de Sunset sin cumplimiento | El endpoint vive para siempre, el código se pudre | Recordatorio en calendario; prueba CI que falla 7 días antes del Sunset para forzar la acción |
Novato / Experto / Cronograma
| Novato | Experto | |
|---|---|---|
| Primer cambio disruptivo | Pasa de /v1 a /v2, copia y pega todo el árbol | Considera el anclaje por fecha, evalúa si el cambio es realmente disruptivo |
| Informar a los clientes sobre la descontinuación | Envío masivo de correos | Encabezados Deprecation + Sunset + Link + correo + banner en el panel |
| Mantener 5 versiones | 5 ramas if/else en cada controlador | Módulos transformadores, código central solo con la última versión |
| Cumplimiento del Sunset | "Ya lo haremos" | Fecha de Sunset en CI; el endpoint se convierte en 410 Gone en la fecha |
| Cambio en API interna | Misma ceremonia que en pública: lento | Evolución aditiva + despliegue atómico; reserva el proceso pesado para APIs públicas |
Prueba del cronograma: ¿cuánto tiempo pasa desde "queremos eliminar el campo X" hasta "el campo X ha desaparecido en producción"? Una organización experta tiene una respuesta única y repetible (por ejemplo, 90 días). Una organización novata no tiene respuesta porque no hay proceso.
Controles de calidad
Un cambio de versionado se publica cuando:
- Prueba: Eliminar o renombrar cualquier campo visible externamente está condicionado a un anuncio de descontinuación con al menos N días de antigüedad (donde N es la ventana de descontinuación documentada).
- Prueba: Cada endpoint obsoleto envía los encabezados
Deprecation: @<unix-ts>ySunset: <HTTP-date>, verificado en una prueba de integración. - Prueba: El valor de
Sunsetnunca es anterior al valor deDeprecation(según el requisito MUST de RFC 9745). - Prueba: Cada anuncio de descontinuación incluye un encabezado
Link: <url-docs>; rel="deprecation"Y un documento de migración público que existe en esa URL (verificación HEAD en CI). - Prueba: Existe telemetría de uso por versión: puedes responder "¿cuántas solicitudes en la ruta v2 ayer?" antes de eliminar v2.
- Prueba (solo anclado por fecha): Las nuevas claves de API se establecen por defecto en la última versión; las claves existentes mantienen su anclaje entre despliegues.
- Manual: Plan de cumplimiento del Sunset: un recordatorio en el calendario, una comprobación en CI, o un 410 Gone automatizado — algo se activa cuando llega la fecha.
NO es para esta habilidad
- Evolución del esquema de GraphQL (usar
graphql-schema-evolution) - Compatibilidad alámbrica de gRPC / Protobuf (usar
protobuf-evolution-rules) - Versionado de esquemas de bases de datos (usar
zero-downtime-database-migration) - Semver de librerías / SDK (usar
npm-package-versioning) - Evolución de esquemas de flujos de eventos (usar
kafka-schema-registry-design)
Fuentes
- Stripe: API versioning at Stripe — versiones ancladas por fecha, patrón de transformador
- API de Stripe: Idempotent requests / versioning policy
- IETF: RFC 8594 — The Sunset HTTP Header Field
- IETF: RFC 9745 — The Deprecation HTTP Header Field
- IETF: RFC 9651 — Structured Field Values for HTTP (formato de fecha para el encabezado Deprecation)
- Roy Fielding: REST APIs must be hypertext-driven — el argumento canónico contra el versionado en URI