El contrato de la v1 de Tienda Aroma está prácticamente cerrado: recursos, métodos, códigos, representaciones y colecciones. Y justo cuando un contrato se publica empieza el problema de verdad: el negocio cambia y la API tiene que cambiar con él, sin romper a nadie. Aroma Móvil tiene versiones instaladas en teléfonos que nadie va a actualizar en meses; RápidoEnvíos tiene una integración escrita hace un año que funciona y que nadie quiere tocar. Esta lección enseña a distinguir con precisión qué cambios se pueden hacer sin avisar y cuáles no, compara las cinco estrategias de versionado que se usan en el sector, justifica la de Tienda Aroma y diseña el ciclo completo de deprecación, desde el anuncio hasta el apagado.
Contenido
- Por qué versionar es un último recurso
- Cambios retrocompatibles frente a rompedores
- Tabla de cambios sobre la API de Tienda Aroma
- Estrategias de versionado
- Comparativa y decisión de Tienda Aroma
- SemVer aplicado a APIs y sus límites
- Convivencia de versiones
- Ciclo de vida y deprecación
- Cabeceras
Deprecation,SunsetyWarning - Estrategias para no tener que versionar
- Por qué versionar es un último recurso
Publicar una v2 suena a progreso. En realidad es una factura: dos bases de código que mantener, dos documentaciones, dos conjuntos de pruebas, dos superficies de seguridad, y una migración que hay que negociar con cada consumidor. Empresas conocidas llevan una década manteniendo su v1 porque apagarla resultó imposible.
Por eso la regla número uno del versionado es: evita necesitarlo. La mayoría de los cambios se pueden hacer de forma retrocompatible si el contrato se diseñó con tolerancia a la evolución (02-01) y si sabes distinguir con precisión qué rompe y qué no. Eso es exactamente lo que hace la sección siguiente.
- Cambios retrocompatibles frente a rompedores
La definición operativa, y la única que sirve:
Un cambio es retrocompatible si un cliente escrito contra el contrato anterior, y que se comportaba correctamente, sigue funcionando sin tocar una línea.
Fíjate en el matiz "que se comportaba correctamente": un cliente que se rompe porque recorría los campos del JSON asumiendo que eran exactamente cinco no es culpa tuya, siempre que la documentación advirtiera de que pueden aparecer campos nuevos. De ahí que el principio de robustez y el tolerant reader de 02-01 no sean un consejo blando: son la condición que hace posible evolucionar sin versionar.
2.1. Regla general
- Añadir es casi siempre seguro (campos, endpoints, valores de enumerado de salida, parámetros opcionales).
- Quitar, renombrar o restringir casi siempre rompe (campos, endpoints, valores admitidos, códigos de estado).
2.2. La asimetría entrada/salida
Es lo que más confunde, y merece pensarlo despacio: añadir un valor a un enumerado no es lo mismo en la respuesta que en la petición.
- En la salida (respuestas del servidor): añadir
estado: "devuelto"es un cambio potencialmente rompedor para clientes que hacen unswitchsin caso por defecto. Se considera aceptable solo porque la documentación avisa desde el día uno de que los enumerados crecen. - En la entrada (peticiones del cliente): aceptar un valor nuevo en
tuestees totalmente seguro, porque nadie lo estaba enviando antes.
La simétrica, y por la misma razón invertida: relajar una validación de entrada es seguro; endurecerla rompe. Si hoy aceptas comentarios de 5.000 caracteres y mañana los limitas a 500, hay clientes que funcionaban y dejan de funcionar.
- Tabla de cambios sobre la API de Tienda Aroma
| Cambio | ¿Rompe? | Por qué |
|---|---|---|
Añadir el campo puntuacionMedia a la respuesta de café |
No | Los clientes que lo ignoran siguen igual (tolerant reader) |
Añadir el endpoint /v1/suscripciones |
No | Nadie lo llamaba |
Añadir el filtro opcional ?disponible=true |
No | Comportamiento sin el parámetro, intacto |
Añadir el enlace devolver en _links de un pedido |
No | Aditivo, y ya avisamos de que los enlaces dependen del estado |
Aceptar un valor nuevo en tueste de entrada |
No | Nadie enviaba natural antes |
Añadir el valor devuelto a estado de pedido |
Casi: documentado como esperable | Rompe a quien no previó valores nuevos; se anuncia con antelación |
Renombrar precio a precioEuros |
Sí | Todo cliente que lea precio recibe undefined |
Eliminar el campo stock de la respuesta |
Sí | Desaparece un dato que se estaba usando |
Cambiar stock de número a cadena ("120") |
Sí | stock > 0 deja de comportarse igual; "0" es verdadero en JavaScript |
Cambiar precioEuros de euros a céntimos (1450) |
Sí, y del peor tipo | No falla: muestra precios cien veces mayores. Un cambio silencioso es peor que uno ruidoso |
Hacer obligatorio el campo origen al crear un café |
Sí | Peticiones que funcionaban ahora dan 400 |
Limitar comentario de 5.000 a 500 caracteres |
Sí | Endurecer una validación rompe a quien estaba en el margen |
Eliminar el valor pendiente_pago de estado |
Sí | Los clientes lo tienen en sus condicionales |
Cambiar POST /pedidos de 201 a 200 |
Sí | Los clientes que comprueban === 201 fallan; además desaparece Location |
Cambiar el codigo de error cafe_no_encontrado a no_encontrado_cafe |
Sí | El codigo es contrato; se compara en el código del cliente (02-04) |
Cambiar el texto de mensaje de un error |
No | Se documentó que mensaje es para humanos y puede cambiar |
Cambiar el orden por defecto de /cafes de nombre a -fechaCreacion |
Sí | Aunque parezca inocuo, cambia lo que ve la página 1 y estaba documentado |
Reducir limite máximo de 100 a 50 |
Sí | Peticiones válidas empiezan a devolver 400 |
Aumentar limite máximo de 100 a 200 |
No | Relajar un límite es seguro |
Cambiar el formato de id de caf_001 a caf_01HQ8ZK… |
No | Se documentó como cadena opaca (02-02); rompe solo a quien lo parseaba, que estaba avisado |
Devolver null en un campo que nunca lo era |
Sí | El cliente hace cafe.origen.toUpperCase() y revienta |
Migrar /cafes de offset a cursor eliminando desplazamiento |
Sí | Un parámetro publicado no se retira sin versión |
Añadir la cabecera Aroma-RateLimit-Restantes |
No | Las cabeceras nuevas se ignoran solas |
Corregir un 500 que ahora devuelve 400 |
No (se considera arreglo) | Estabas incumpliendo tu propio contrato |
La última fila señala una zona gris real: arreglar un bug puede romper a quien dependía del bug. Se documenta en el changelog, se anuncia, y en general se considera cambio no rompedor. Pero si el comportamiento erróneo llevaba dos años ahí, quizá se haya convertido de facto en contrato: hay que mirarlo caso por caso.
- Estrategias de versionado
4.1. Versión en la ruta
Es la más usada del sector: Twitter/X, GitHub (durante años), Stripe en su URL base, casi todas las APIs corporativas.
A favor: visible a simple vista; se prueba con un navegador o con curl sin cabeceras; el enrutado es trivial (un prefijo); los logs y las métricas separan versiones sin esfuerzo; los ejemplos de la documentación son autocontenidos.
En contra: puristas de REST objetan que la URI debería identificar el recurso, no su formato, y que /v1/cafes y /v2/cafes son "el mismo café" con dos URIs distintas; además, versiona la API entera aunque solo cambie un recurso, y los enlaces _links guardados por los clientes quedan clavados a una versión.
4.2. Versión en query param
A favor: la URI base es única; es fácil de probar; se puede poner un valor por defecto.
En contra: se mezcla con los parámetros de negocio (filtros, orden, paginación), se pierde con facilidad al copiar URLs, complica la caché y hace ambiguo qué versión se sirve si el parámetro falta.
4.3. Versión en cabecera personalizada
A favor: las URIs quedan limpias y estables; permite versionar de forma granular.
En contra: invisible. No se puede pegar un enlace en un ticket y esperar que reproduzca el problema; probar en un navegador es imposible sin herramientas; las cachés necesitan Vary: Aroma-Version y muchos gateways lo ignoran; y hay que decidir qué pasa si no se envía.
4.4. Versión en el media type
Es la opción más correcta desde el punto de vista de REST: la versión pertenece a la representación, y la representación se negocia con Accept (02-05). GitHub la usó durante años (application/vnd.github.v3+json).
A favor: teóricamente impecable; usa un mecanismo estándar de HTTP; permite versionar recurso a recurso.
En contra: la más difícil de usar. Nadie recuerda la cadena de memoria; curl requiere cabecera explícita; muchas herramientas y clientes generados no lo manejan bien; e igual que la cabecera personalizada, es invisible en la URL.
4.5. Versionado por fecha
Es el estilo de Stripe: cada cuenta queda anclada a la versión vigente el día que se integró, y el servidor aplica transformaciones encadenadas para adaptar la respuesta actual a la forma que esperaba aquella fecha.
A favor: no hay saltos traumáticos de v1 a v2; los cambios se introducen de forma continua; cada consumidor migra cuando quiere; es el que mejor escala en APIs públicas grandes.
En contra: complejidad alta. Exige mantener una cadena de transformaciones bien probada y una disciplina de ingeniería considerable. Es un patrón excelente para una empresa cuyo producto es la API, y desproporcionado para casi todas las demás.
- Comparativa y decisión de Tienda Aroma
| Criterio | Ruta | Query | Cabecera | Media type | Fecha |
|---|---|---|---|---|---|
| Visibilidad | Alta | Alta | Baja | Baja | Baja |
Facilidad de prueba (curl, navegador) |
Muy alta | Alta | Baja | Muy baja | Baja |
| Pureza REST | Baja | Baja | Media | Alta | Media |
| Granularidad (por recurso) | Baja | Baja | Alta | Alta | Alta |
| Facilidad de enrutado y despliegue | Muy alta | Media | Media | Baja | Baja |
| Caché e intermediarios | Sencilla | Media | Requiere Vary |
Requiere Vary |
Requiere Vary |
| Complejidad de implementación | Baja | Baja | Media | Media | Alta |
| Migración progresiva | Baja | Baja | Media | Media | Muy alta |
| Quién la usa | Twitter/X, la mayoría | APIs sencillas | Azure, algunas | GitHub (histórico) | Stripe |
Decisión de Tienda Aroma: versión en la ruta (/v1).
Las razones, en orden de peso:
- Visibilidad y soporte. Cuando RápidoEnvíos abra una incidencia y pegue una URL, sabremos exactamente contra qué está hablando. Con cabeceras, la mitad de los tickets empiezan con "¿qué versión estabas usando?".
- Coste de implementación y operación. Un prefijo de ruta se enruta, se despliega, se mide y se apaga con herramientas estándar. Con cuatro consumidores y un equipo pequeño, la pureza teórica no compensa.
- Docencia y documentación. Todos los ejemplos
curlde la documentación funcionan copiados y pegados, sin cabeceras ocultas. - Coherencia con lo ya decidido. La base URL con
/v1está fijada desde el módulo 1 y publicada a los cuatro consumidores.
Y las decisiones asociadas, que también son contrato:
- Solo se versiona el número mayor:
/v1,/v2. Nunca/v1.2. Los cambios menores son retrocompatibles por definición y no necesitan URL nueva. - La versión es obligatoria en la ruta. No existe
https://api.tiendaaroma.example/cafessin versión que "apunte a la última": un cliente que no elige versión acaba roto el día que la última cambia. - Todos los recursos comparten versión. Sube toda la API a la vez, aunque solo cambien los pedidos. Simplifica el razonamiento a cambio de algo de granularidad.
- SemVer aplicado a APIs y sus límites
SemVer (versionado semántico) define MAYOR.MENOR.PARCHE:
| Componente | Cuándo sube | Ejemplo en Tienda Aroma |
|---|---|---|
| MAYOR | Cambio rompedor | Renombrar precio a precioEuros |
| MENOR | Funcionalidad nueva, retrocompatible | Añadir /v1/suscripciones |
| PARCHE | Corrección sin cambio de contrato | Arreglar un cálculo de IVA |
Aplicado a una API HTTP, SemVer tiene tres límites que conviene tener claros:
- Solo el número mayor aparece en la URL. A un consumidor le da igual si está usando la
1.4.2o la1.7.0: el contrato que ve es el mismo. Menor y parche viven en el changelog, no en la ruta. - No hay "instalación" que fijar. Con una biblioteca, el consumidor decide cuándo actualizar; con una API alojada, el servidor actualiza para todos a la vez. Por eso los cambios menores tienen que ser rigurosamente retrocompatibles: no hay marcha atrás para el cliente.
- La frontera mayor/menor se negocia. Añadir un valor de enumerado en la salida (sección 2.2) es, técnicamente, potencialmente rompedor; declararlo MAYOR obligaría a publicar una
v2cada trimestre. Se documenta como esperable y se trata como MENOR con anuncio previo. Escribe esa política en la guía de estilo, porque es la decisión que más discusiones evita.
Tienda Aroma mantiene, por tanto, dos numeraciones: la versión de la URL (v1) para el contrato, y la versión semántica interna (1.7.0) en el changelog y en el campo info.version de OpenAPI (02-08).
- Convivencia de versiones
Cuando llega la v2, las dos conviven un tiempo. Las preguntas prácticas:
¿Cuántas versiones mantener?
Dos como máximo: la actual y la anterior en deprecación. Tres versiones vivas es señal de que la migración anterior nunca terminó, y el coste crece más que linealmente: cada corrección de seguridad y cada cambio de negocio hay que aplicarlo en todas.
¿Cuánto cuesta realmente?
| Coste | Detalle |
|---|---|
| Código | Rutas, transformaciones y, a veces, lógica de negocio duplicada |
| Pruebas | Toda la batería, dos veces (03-08) |
| Documentación | Dos referencias completas y coherentes |
| Soporte | El doble de casuística en cada incidencia |
| Seguridad | Cada parche, aplicado y verificado dos veces |
| Datos | La v1 puede necesitar campos que la v2 ya no usa |
¿Cómo se enrutan?
El patrón habitual, y el que usará Tienda Aroma en el módulo 3: una sola aplicación con dos capas de presentación sobre una lógica de negocio compartida.
graph TD
C1["Aroma Móvil 3.x"] --> R{"Enrutado<br/>por prefijo"}
C2["SPA de la tienda"] --> R
C3["RápidoEnvíos"] --> R
R -->|"/v1/*"| V1["Capa v1<br/><i>transforma al contrato antiguo</i>"]
R -->|"/v2/*"| V2["Capa v2<br/><i>contrato actual</i>"]
V1 --> N["Lógica de negocio<br/>y datos<br/><b>compartidos</b>"]
V2 --> N
La clave es no duplicar la lógica de negocio. La v1 se implementa como una capa de adaptación sobre el modelo actual: renombra campos, recorta lo que no existía, calcula lo que se eliminó. Duplicar el servicio entero garantiza que las dos versiones diverjan en comportamiento y que aparezcan bugs que solo ocurren en una.
Cuando la transformación deja de ser posible —porque el modelo de datos cambió de verdad— es la señal de que la v1 debe apagarse, no de que haya que duplicar el sistema.
- Ciclo de vida y deprecación
Toda versión recorre cuatro fases:
graph LR
A["<b>Vigente</b><br/>versión recomendada"] --> B["<b>Deprecada</b><br/>funciona, pero avisa"]
B --> C["<b>Sunset anunciado</b><br/>fecha de apagado fijada"]
C --> D["<b>Apagada</b><br/>410 Gone"]
El calendario de Tienda Aroma, escrito en la documentación antes de publicar la v1 —porque las condiciones de retirada se anuncian al principio, no cuando ya molestan—:
| Hito | Plazo | Qué ocurre |
|---|---|---|
Publicación de la v2 |
Día 0 | La v1 sigue vigente y soportada |
Deprecación de la v1 |
Día 0 | Cabecera Deprecation en todas las respuestas v1; changelog y correo a los consumidores |
| Recordatorios | Meses 3, 6, 9, 11 | Correo a los consumidores que siguen llamando, con sus cifras de uso |
| Solo lectura (opcional) | Mes 11 | Las escrituras en v1 devuelven 410; las lecturas siguen |
| Apagado | Mes 12 | Toda la v1 responde 410 Gone con enlace a la guía de migración |
Mínimo de 12 meses para consumidores externos como RápidoEnvíos. Para clientes propios (SPA, panel) el plazo puede acortarse porque controlamos el despliegue, pero no para Aroma Móvil: hay usuarios que no actualizan la app en un año, y aquí está el consumidor que de verdad marca el calendario.
Comunicación
Una deprecación que solo vive en las cabeceras HTTP es una deprecación que nadie lee. El paquete completo:
- Cabeceras en cada respuesta (sección 9) — para el software.
- Changelog con fecha, motivo y guía de migración campo a campo — para el desarrollador que investiga.
- Correo directo a los responsables técnicos de cada consumidor identificado — para el humano que decide.
- Aviso en el portal de desarrollador (05-06).
- Recordatorios segmentados: solo a quien sigue usando la versión antigua, con sus datos concretos de uso. Un correo genérico se ignora; "hemos registrado 12.400 llamadas vuestras a
/v1este mes" no.
Métricas: saber quién sigue ahí
No se apaga nada sin datos. Hay que medir, por versión y por consumidor:
| Métrica | Para qué |
|---|---|
| Peticiones por versión y día | Ver la curva de migración y decidir si el plazo es realista |
| Peticiones por versión y cliente de API | Saber a quién hay que llamar por teléfono |
Endpoints v1 más usados |
Priorizar la guía de migración por lo que de verdad se usa |
| Último acceso por cliente | Detectar integraciones zombis que quizá ya no importan |
Errores en v2 tras migrar |
Descubrir que la migración va mal antes de que se queje nadie |
Esto exige identificar a cada consumidor, lo que se consigue con la clave o el token de API de cada uno (04-03) y con la observabilidad de 04-07. Sin identificación de consumidores no hay deprecación posible: solo apagones a ciegas.
El apagado
Al llegar la fecha, la v1 responde:
HTTP/1.1 410 Gone
Content-Type: application/json
Link: <https://docs.tiendaaroma.example/migracion-v1-v2>; rel="deprecation"
{
"error": {
"codigo": "version_api_retirada",
"mensaje": "La versión v1 de la API se retiró el 2027-03-14. Migra a /v2.",
"detalles": [
{ "guiaMigracion": "https://docs.tiendaaroma.example/migracion-v1-v2" }
]
}
}410 Gone y no 404: el recurso existió y se ha eliminado deliberadamente (02-04). Y no se redirige v1 a v2 con un 301: los contratos son distintos, así que el cliente recibiría una respuesta con otra forma y fallaría de manera confusa. Es mejor un error claro que un éxito engañoso.
- Cabeceras
Deprecation, Sunset y Warning
Deprecation, Sunset y WarningEl estándar permite anunciar la retirada dentro del propio protocolo, de modo que el software pueda enterarse sin leer un correo.
HTTP/1.1 200 OK
Content-Type: application/json
Deprecation: @1773484200
Sunset: Sun, 14 Mar 2027 00:00:00 GMT
Link: <https://api.tiendaaroma.example/v2/cafes>; rel="successor-version",
<https://docs.tiendaaroma.example/migracion-v1-v2>; rel="deprecation"
{ "datos": [ ], "total": 137 }| Cabecera | Estándar | Qué dice |
|---|---|---|
Deprecation |
RFC 9745 | Que el recurso está deprecado, y desde cuándo (fecha con @ y segundos epoch, o true) |
Sunset |
RFC 8594 | Fecha exacta a partir de la cual dejará de responder, en formato de fecha HTTP |
Link con rel="successor-version" |
RFC 8288 | Dónde está el sustituto |
Link con rel="deprecation" |
RFC 9745 | Dónde está la explicación y la guía de migración |
Warning |
RFC 7234, obsoleta | Avisos legibles; retirada en la RFC 9111: no la uses en diseños nuevos |
Un detalle importante y que se olvida: estas cabeceras se pueden usar sin publicar una versión nueva, sobre un recurso o un campo concreto. Si Tienda Aroma va a retirar /v1/clientes/{id}/preferencias sustituyéndolo por otra cosa, ese endpoint concreto puede llevar Deprecation y Sunset mientras el resto de la v1 sigue tan sana.
Y una recomendación práctica para los consumidores, que conviene incluir en la documentación: registrad un aviso en vuestros logs cuando lleguen estas cabeceras. Es la forma barata de enterarse de una deprecación sin depender de que alguien lea el correo adecuado.
- Estrategias para no tener que versionar
Volvemos a donde empezamos: la mejor versión nueva es la que no hace falta. Cinco técnicas concretas.
10.1. Campos opcionales y aditivos
Añadir en lugar de cambiar. Cuando haya que sustituir un campo, conviven los dos durante la transición:
precio queda documentado como deprecado, con fecha de retirada, y desaparece en la v2. Cuesta duplicar un dato durante unos meses; ahorra una versión entera.
10.2. Tolerant reader
Es responsabilidad del consumidor, y hay que documentarla y repetirla:
// ✗ Frágil: se rompe con cualquier campo nuevo o cualquier valor nuevo
const { id, nombre, precioEuros, stock, tueste } = cafe;
switch (pedido.estado) {
case "pendiente_pago": mostrarBotonPagar(); break;
case "pagado": mostrarFactura(); break;
case "enviado": mostrarSeguimiento(); break;
}
// ✓ Tolerante: ignora lo desconocido y tiene caso por defecto
const nombre = cafe.nombre ?? "Sin nombre";
switch (pedido.estado) {
case "pendiente_pago": mostrarBotonPagar(); break;
case "pagado": mostrarFactura(); break;
case "enviado": mostrarSeguimiento(); break;
default: mostrarEstadoGenerico(pedido.estado);
}La segunda versión sobrevive al día en que aparezca estado: "devuelto". Un cliente tolerante es lo que convierte a "añadir" en una operación segura, y por eso Tienda Aroma lo documenta como requisito de integración, no como consejo.
10.3. Feature flags y despliegue progresivo
Un comportamiento nuevo se activa primero para un consumidor concreto, se mide y se generaliza. Permite validar un cambio dudoso con la SPA (que controlamos) antes de exponerlo a RápidoEnvíos. Cuidado: una bandera que se queda para siempre es una versión encubierta; toda bandera necesita fecha de retirada.
10.4. Expansión y selección de campos
Ya diseñadas en 02-05: expandir y campos absorben buena parte de las peticiones de cambio ("necesitamos los datos del cliente dentro del pedido") sin tocar el contrato, porque el mecanismo genérico ya estaba previsto.
10.5. Recursos nuevos en lugar de recursos cambiados
Si /cafes tiene que cambiar de forma radical, a veces la respuesta correcta no es la v2 completa, sino un recurso nuevo con nombre propio (/catalogo, /productos) que convive con el antiguo, ya deprecado. Se paga con dos nombres para conceptos parecidos, y se cobra en no versionar la API entera por un solo recurso.
Errores Comunes y Consejos
- Versionar por costumbre. Publicar
v2porque "toca" duplica el coste sin dar valor. Versiona solo cuando un cambio rompedor sea inevitable. - Creer que añadir un campo nunca rompe. Es cierto para clientes tolerantes; con validación estricta de esquema en el cliente, rompe. Por eso hay que documentar que la API puede añadir campos.
- Cambiar el significado de un campo sin cambiar su nombre. El peor cambio posible: no falla, miente.
precioEurospasando a céntimos multiplica los precios por cien silenciosamente. - No versionar el contrato de errores. Los
codigode error son contrato tanto como los campos: renombrarlos rompe clientes (02-04). - Mantener tres o cuatro versiones vivas. Es un síntoma, no una virtud: significa que ninguna migración se completó.
- Apagar sin datos ni aviso. Sin métricas por consumidor y sin plazo anunciado, el apagado es una incidencia grave con tu socio.
- Redirigir
v1av2con301. Los contratos son distintos: el cliente recibirá200con una forma que no espera y fallará de forma incomprensible. - Consejo: escribe el changelog desde el primer día. Es el artefacto más barato y el que más agradecen los consumidores (02-08).
- Consejo: aplica el "test del cliente congelado". Ante cada cambio, pregúntate: ¿seguiría funcionando la versión de Aroma Móvil que se instaló hace un año? Si la respuesta es no, es rompedor.
Ejercicios
Ejercicio 1: clasificar cambios
Para cada cambio propuesto sobre la v1 de Tienda Aroma, indica si es retrocompatible o rompedor y justifícalo. Si es rompedor, propón una alternativa que no lo sea.
- Añadir
puntuacionMediaynumeroResenasa la representación de café. - Renombrar
notasCataanotasDeCata. - Añadir el estado
devueltoa los pedidos. - Dejar de devolver
emailen la representación de cliente por privacidad. - Aceptar
PATCHconapplication/json-patch+json, además de Merge Patch. - Cambiar
totalEurosde29.00a"29.00"(cadena) para evitar problemas de coma flotante. - Hacer obligatorio
Idempotency-KeyenPOST /cafes/{id}/resenas. - Bajar el
limitemáximo de 100 a 50 por problemas de rendimiento.
Ejercicio 2: diseñar una migración
Tienda Aroma necesita soportar varias divisas. La forma actual es:
Y la deseada:
Diseña la migración completa: ¿es rompedor?, ¿se puede evitar la v2?, ¿qué se publica y cuándo?, ¿qué cabeceras se envían y qué se comunica a cada uno de los cuatro consumidores?
Ejercicio 3: planificar la retirada de la v1
Han pasado seis meses desde que se publicó la v2 y estos son los datos de uso de la v1:
| Consumidor | Peticiones/mes a v1 |
Peticiones/mes a v2 |
Último acceso |
|---|---|---|---|
| SPA de la tienda web | 0 | 4.200.000 | — |
| Aroma Móvil | 890.000 | 3.100.000 | Hoy |
| Panel interno | 12.000 | 45.000 | Hoy |
| RápidoEnvíos | 61.000 | 0 | Hoy |
Cliente desconocido api_key_7f2 |
340 | 0 | Hace 4 meses |
Decide si se puede apagar la v1 en el mes 12 y elabora el plan de acción para cada consumidor.
Soluciones
Solución 1
| # | Veredicto | Justificación y alternativa |
|---|---|---|
| 1 | Retrocompatible | Campos aditivos; los clientes que no los conocen los ignoran |
| 2 | Rompedor | Todo cliente que lea notasCata recibe undefined. Alternativa: devolver los dos campos, documentar notasCata como deprecado con Sunset, y eliminarlo en la v2 |
| 3 | Casi rompedor, aceptado | Rompe a quien no tenga caso por defecto, pero la documentación advierte de que los enumerados crecen. Se anuncia en el changelog con antelación y se comunica a los consumidores |
| 4 | Rompedor | Desaparece un dato en uso. Alternativa: dejar de devolverlo solo a los consumidores sin permiso para datos personales (04-03), que es un cambio de autorización, no de contrato; y para el resto, deprecarlo con plazo |
| 5 | Retrocompatible | Ampliar los formatos aceptados en la entrada es relajar, no restringir: nadie enviaba JSON Patch antes |
| 6 | Rompedor | Cambia el tipo: totalEuros * 2 deja de funcionar y las comparaciones numéricas fallan. Alternativa: añadir totalEurosTexto como campo nuevo y migrar poco a poco, o dejarlo para la v2 |
| 7 | Rompedor | Peticiones que funcionaban empiezan a dar 400. Alternativa: aceptarla como opcional, avisar durante meses de que será obligatoria, medir cuántos clientes la envían ya y hacerla obligatoria en la v2 |
| 8 | Rompedor | Peticiones válidas pasan a 400. Alternativas: optimizar la consulta; mantener 100 y limitar por consumidor mediante rate limiting (04-04); o anunciar la reducción con plazo largo y medir cuántos usan más de 50 |
Solución 2
¿Es rompedor? Sí, sin matices: precioEuros desaparecería y cambiaría de tipo (número → objeto). Cualquier cliente que muestre precios se rompe, y en el peor de los casos muestra [object Object].
¿Se puede evitar la v2? Sí, con la técnica de convivencia de campos, y esta es la respuesta correcta:
Fase 1 (mes 0). Se añade precio sin quitar nada:
Cambio retrocompatible: los clientes antiguos siguen leyendo precioEuros; los nuevos usan precio. Mientras solo haya euros, los dos campos coexisten sin ambigüedad. Se publica en el changelog, se documenta precioEuros como deprecado y se explica la equivalencia.
Fase 2 (meses 0-12). Respuestas con cabeceras a nivel de campo y seguimiento de uso:
Deprecation: @1773484200
Sunset: Sun, 14 Mar 2027 00:00:00 GMT
Link: <https://docs.tiendaaroma.example/migracion-precio>; rel="deprecation"Se mide qué consumidores siguen leyendo precioEuros —lo que en la práctica exige preguntarles, porque el servidor no ve qué campos usa el cliente: aquí es donde campos= de 02-05 resulta útil como indicio.
Fase 3. Cuando aparezca la primera divisa distinta del euro, precioEuros deja de ser representable y ahí sí nace la v2, que elimina el campo antiguo. Es decir: la v2 se pospone hasta que el negocio la justifique, no la provoca un cambio de forma.
Comunicación por consumidor:
| Consumidor | Acción |
|---|---|
| SPA de la tienda | Migración inmediata: la controlamos y se despliega en días |
| Aroma Móvil | Migrar en la siguiente versión de la app; hay que asumir 12 meses de convivencia por las instalaciones antiguas |
| Panel interno | Migración inmediata |
| RápidoEnvíos | Correo formal con la guía de migración y la fecha; no consume precios, así que probablemente no le afecte, pero se le informa igual |
Solución 3
Veredicto: no se puede apagar en el mes 12 sin trabajo previo. Hay dos bloqueos serios y uno menor.
| Consumidor | Diagnóstico | Plan de acción |
|---|---|---|
| SPA | Migrada al 100 % | Nada |
| Aroma Móvil | 890.000 llamadas/mes: hay versiones antiguas instaladas en teléfonos. No basta con publicar una versión nueva de la app | Forzar la actualización desde la propia app (aviso bloqueante), medir la distribución de versiones instaladas y no apagar hasta que la curva baje del umbral acordado. Es el bloqueo principal |
| Panel interno | 12.000 llamadas/mes: quedan pantallas sin migrar | Auditoría de endpoints v1 usados y migración; es interno, así que es cuestión de planificar el trabajo. Plazo: mes 8 |
| RápidoEnvíos | 0 llamadas a v2: no ha empezado a migrar. El bloqueo más peligroso, porque es externo y no controlamos su calendario |
Contacto directo inmediato con su equipo técnico, guía de migración específica, entorno de pruebas y fecha comprometida por escrito. Si no puede cumplir el mes 12, se negocia una extensión acotada solo para su clave de API, con fecha nueva y firmada |
api_key_7f2 |
340 llamadas, sin actividad en 4 meses: integración zombi o script olvidado | Intentar identificar al responsable por los datos de alta; si no hay respuesta en 30 días, avisar de la retirada y apagar en la fecha prevista. No debe condicionar el plan |
Plan revisado: mantener la fecha de deprecación, pero fijar el apagado en el mes 12 condicionado a dos hitos medibles: (1) que Aroma Móvil v1 baje del 2 % del tráfico total, y (2) que RápidoEnvíos confirme por escrito su migración. Entre los meses 9 y 12, recordatorios mensuales con cifras concretas; en el mes 11, v1 en solo lectura para forzar la detección de integraciones olvidadas; y un ensayo de apagado (brownout) de una hora en el mes 10, anunciado con antelación, que es la técnica más eficaz para que aparezcan los consumidores que nadie sabía que existían.
Conclusión
Versionar es caro, así que el objetivo real es necesitarlo lo menos posible. Ahora sabes distinguir con precisión un cambio retrocompatible de uno rompedor —añadir es seguro, quitar, renombrar, restringir y cambiar tipos no lo es, con la asimetría clave entre entrada y salida— y conoces la peor categoría de todas: el cambio silencioso que no falla, sino que miente. Has comparado las cinco estrategias de versionado y sabes por qué Tienda Aroma versiona en la ruta con /v1, priorizando visibilidad y coste de operación sobre la pureza REST del media type. Y tienes el ciclo de vida completo: convivencia de dos versiones como máximo sobre una lógica de negocio compartida, doce meses de plazo para los consumidores externos, cabeceras Deprecation y Sunset con Link al sucesor y a la guía de migración, métricas por consumidor para saber a quién llamar, y un apagado con 410 Gone en lugar de una redirección engañosa.
Con esto, el contrato de la v1 está completo y tiene además una política de evolución. Falta la pieza que lo convierte en algo utilizable por otros: contarlo. En la lección siguiente y última del módulo, 02-08 Documentación de APIs, veremos por qué la documentación es parte del producto, qué tipos de documento sirven a qué lector, qué debe incluir la referencia de cada endpoint, y qué cambia cuando el contrato se escribe en un formato legible por máquinas como OpenAPI. Cerraremos haciendo balance del contrato completo de Tienda Aroma y preparando el salto al módulo 3, donde por fin se implementa.
Curso de REST API: Principios de Diseño y Desarrollo de APIs RESTful
Módulo 1: Introducción a las APIs RESTful
- ¿Qué es una API?
- Historia y evolución de las APIs
- Fundamentos de HTTP para APIs
- Principios básicos de REST
- Modelo de madurez de Richardson y HATEOAS
- REST vs. SOAP
- REST frente a GraphQL, gRPC y webhooks
Módulo 2: Diseño de APIs RESTful
- Principios de diseño de APIs RESTful
- Recursos y URIs
- Métodos HTTP
- Códigos de estado HTTP
- Representaciones, cabeceras y negociación de contenido
- Filtrado, ordenación, paginación y búsqueda
- Versionado de APIs
- Documentación de APIs
Módulo 3: Desarrollo de APIs RESTful
- Configuración del entorno de desarrollo
- Creación de un servidor básico
- Manejo de peticiones y respuestas
- Validación de datos de entrada
- Persistencia y capa de acceso a datos
- Autenticación y autorización
- Manejo de errores
- Pruebas y validación
Módulo 4: Buenas Prácticas y Seguridad
- Buenas prácticas en el diseño de APIs
- Seguridad en APIs RESTful
- OAuth 2.0 y OpenID Connect en la práctica
- Rate limiting y throttling
- CORS y políticas de seguridad
- Caché HTTP y rendimiento
- Observabilidad: logs, métricas y trazas
Módulo 5: Herramientas y Frameworks
- Postman para pruebas de APIs
- Swagger y OpenAPI para documentación
- Frameworks populares para APIs RESTful
- Contratos, mocks y pruebas automatizadas de API
- Integración continua y despliegue
- API gateways y portales de desarrollador
