El reto

Conectar sistemas de forma mantenible

Las APIs son una pieza fundamental para integrar aplicaciones empresariales. Una API debe responder a las necesidades funcionales del proyecto y facilitar su evolución y mantenimiento.

La parte difícil no suele ser exponer el primer endpoint, sino poder cambiarlo después sin romper a quien ya lo consume: versionado, contratos explícitos y una capa de persistencia que no se filtre hacia fuera.

Qué cubre el trabajo sobre una API

  • Diseño de APIs REST
  • Desarrollo de endpoints
  • Integración entre aplicaciones
  • Evolución de APIs existentes
  • Servicios backend con Java
  • Integración con sistemas empresariales

Tecnologías

Tecnologías implicadas en la integración

  • Java
  • Spring Boot
  • Spring MVC
  • REST
  • JSON
  • Hibernate
  • SQL

Lo difícil no es el primer endpoint: es el segundo cambio

Exponer datos es cuestión de horas. Poder cambiarlos dentro de un año sin romper a quien los consume es lo que cuesta.

Una API deja de ser un detalle de implementación en el momento en que alguien la usa. A partir de ahí cada respuesta es un contrato, y los contratos solo se pueden cambiar de dos maneras: avisando o rompiendo. La diferencia entre una API sostenible y una que da miedo tocar se decide en las primeras semanas, no en el rendimiento.

El error más caro es dejar que el modelo de persistencia se filtre hacia fuera. Cuando se serializan directamente las entidades JPA, cualquier cambio en el modelo de datos se convierte en un cambio de contrato, y renombrar una columna pasa a ser una negociación con tres equipos. El trabajo consiste en poner una capa propia de representación en medio, aunque al principio parezca duplicación. Es la misma frontera que hace posible mover la aplicación a Spring Boot por partes.

La segunda decisión que marca el resto es qué se hace cuando algo va mal. Una API que responde 200 con un mensaje de error dentro del cuerpo obliga a cada consumidor a inventarse su propia lógica de detección, y esa lógica siempre acaba siendo distinta en cada cliente.

Las decisiones que conviene tomar antes de escribir código

No hay una respuesta correcta universal, pero sí hay una respuesta correcta para cada contexto. Lo que no funciona es decidirlo sobre la marcha en cada endpoint.

Decisiones de diseño habituales y cuándo encaja cada opción
DecisiónOpcionesCuándo encaja cada una
VersionadoEn la ruta o mediante cabeceraEn la ruta cuando hay consumidores externos que necesitan verlo; por cabecera cuando el consumo es interno y controlado
PaginaciónPor desplazamiento o por cursorPor desplazamiento en catálogos estables; por cursor cuando los datos cambian mientras se recorren
ErroresCuerpo propio o formato estándar de detalle de problemaEl formato estándar salvo que ya exista un contrato previo que no se pueda cambiar
IdentificadoresSecuencia de base de datos o identificador opacoOpaco siempre que salga al exterior: una secuencia expone volumen de negocio y facilita enumerar registros
Operaciones de escrituraIdempotentes o noIdempotentes siempre que el cliente pueda reintentar, que en la práctica es casi siempre

Síntomas de que una API se va a volver difícil de cambiar

Ninguno es un fallo en ejecución. Todos son decisiones que se pagan más adelante.

Renombrar un campo de una entidad rompe a un consumidor

Causa: El modelo de persistencia se está serializando tal cual. La base de datos y el contrato público son la misma cosa.

Qué se hace: Introducir objetos de representación propios en la frontera. Se puede hacer de forma progresiva, empezando por los recursos que más cambian.

Cada cliente interpreta los errores de una forma distinta

Causa: La API responde con códigos de estado inconsistentes, o mete el error dentro de una respuesta correcta.

Qué se hace: Unificar el formato de error y los códigos de estado, y documentarlos. Un error debe poder tratarse sin leer el cuerpo para saber si lo es.

Un reintento del cliente duplica un pedido

Causa: La operación de creación no es idempotente y el cliente no tiene forma de saber si la primera llamada llegó.

Qué se hace: Aceptar una clave de idempotencia proporcionada por el cliente y devolver la misma respuesta ante una repetición. Evita la mayoría de incidencias de duplicados.

Nadie sabe quién consume un endpoint

Causa: No hay registro de consumidores ni trazas que lo permitan deducir, así que ningún cambio se considera seguro.

Qué se hace: Instrumentar por consumidor antes de tocar nada. Saber quién llama y con qué frecuencia convierte una retirada arriesgada en un plan con fechas.

Lo que debería estar resuelto antes de exponer una API

  • Contrato documentado y generado desde el código, no escrito aparte.
  • Formato de error único, con códigos de estado coherentes.
  • Estrategia de versionado decidida, aunque solo exista la versión uno.
  • Autenticación y autorización definidas por recurso, no solo a la entrada.
  • Límites de tamaño y de frecuencia, para que un cliente mal programado no tumbe al resto.
  • Pruebas de contrato que fallen si la respuesta cambia de forma.
  • Trazas que permitan saber qué consumidor hizo qué llamada.

Sobre una aplicación que ya está en producción, esto rara vez se hace de golpe: se introduce recurso a recurso, con el sistema funcionando, dentro del trabajo de modernización de la aplicación.

Cómo se cambia un contrato que ya tiene consumidores

El objetivo es que ningún consumidor tenga que coordinarse con vosotros para seguir funcionando.

  1. Saber quién consume qué

    Instrumentar por consumidor y por recurso antes de plantear ningún cambio. Sin ese dato, retirar un campo es una decisión a ciegas y cualquier fecha es inventada.

  2. Añadir antes de quitar

    El campo o el recurso nuevo convive con el antiguo. Añadir no rompe a nadie; quitar sí. Esa asimetría es la que permite avanzar sin reuniones de coordinación.

  3. Marcar lo antiguo como obsoleto donde se vea

    En la documentación generada y en las trazas. Un aviso que solo existe en un correo enviado hace ocho meses no lo ha leído nadie.

  4. Esperar a que el uso llegue a cero

    Con datos, no con una fecha pactada. Cuando las llamadas al recurso antiguo caen a cero y se mantienen así, la retirada deja de ser una negociación.

  5. Retirar y limpiar

    Se elimina el código, no se deja desactivado por si acaso. Un camino muerto que sigue ahí vuelve a estar vivo en cuanto alguien lo encuentra.

Preguntas frecuentes

Dudas frecuentes sobre el desarrollo de APIs

¿Trabajáis sobre una API existente o solo con desarrollos nuevos?

Las dos cosas, y lo más habitual es lo primero: evolucionar una API que ya tiene consumidores. Eso exige saber antes quién la usa y con qué contrato, porque el margen de maniobra lo marcan ellos.

¿Hace falta rehacer la API para poder cambiarla?

Casi nunca. Lo habitual es introducir una capa de representación propia en los recursos que más cambian y dejar el resto como está. Rehacer la API entera obliga a migrar a todos los consumidores a la vez, que es justo lo que se intenta evitar.

¿Qué pasa con los consumidores que no se pueden actualizar?

Se mantiene la versión antigua mientras haga falta y se instrumenta para saber cuándo deja de usarse. Retirar un endpoint sin datos de uso es una decisión a ciegas; con datos, es una fecha.

¿Documentáis la API?

Sí, y generada desde el propio código. Una documentación escrita aparte deja de ser cierta en la primera entrega, y una documentación que miente es peor que no tenerla.

¿Esto vale para APIs internas entre sistemas?

Sí, y muchas veces importa más. Una API interna suele tener más consumidores de los que nadie recuerda, y como no hay contrato formal, cualquier cambio se descubre en producción.

JavaEvolve

¿API nueva o una que ya tiene consumidores?

Cuéntame quién la consume hoy y qué necesitáis cambiar. El margen de maniobra lo marcan los consumidores, así que es lo primero que se mira.

Pedir diagnóstico gratuito

Llamada de 30 minutos, sin coste. Respondo en menos de 48 horas.

En la llamada: versión de Java, framework, servidor y qué duele hoy. Si no puedo ayudarte, te lo digo. Qué incluye el diagnóstico