Lección 3 de 411 min
Contratos de API: OpenAPI, AsyncAPI y Federation
Describir una API en un documento que entienden personas y máquinas, y unir varias APIs GraphQL en una.
El equipo de frontend necesita saber qué devolverá GET /posts/58 para maquetar la pantalla, pero el backend todavía no existe. Puedes escribirlo en un documento de texto, con el riesgo de que nadie lo lea y de que quede desfasado a la semana. O puedes escribir un contrato: un documento con un formato estándar que entienden las personas y también las máquinas.
De un contrato salen la documentación, el código cliente, un servidor de mentira para trabajar en paralelo y las pruebas que comprueban que la API cumple lo prometido. En esta lección ves cómo se escribe para APIs HTTP con OpenAPI, para APIs de eventos con AsyncAPI, y cómo GraphQL Federation une los esquemas de varios equipos en una sola API.
El contrato antes que el código
Diseño API-first
Pactar la API en un documento antes de escribir una sola línea de implementación.
- Alternativa:
- Code-first
En el enfoque API-first, el primer entregable es el contrato: qué endpoints hay, qué aceptan, qué devuelven y qué errores pueden dar. Se revisa en un pull request como cualquier código, y a partir de él frontend y backend trabajan en paralelo: uno contra un servidor de mentira (mock) generado del contrato, el otro implementándolo.
La alternativa es code-first: escribes el código y generas el documento a partir de él, con anotaciones o con los tipos del framework. Muchos frameworks, como FastAPI o NestJS, lo hacen casi solos. Los dos enfoques son válidos; lo importante es que haya una sola fuente de verdad y que el documento no se escriba a mano por separado.
Úsalo cuando
Cuando la API la consumen otros equipos, apps móviles que no puedes actualizar a la fuerza o empresas externas: ahí un cambio mal pensado sale caro.
Ten en cuenta
Un contrato que nadie comprueba acaba mintiendo. Valida en CI que la implementación responde lo que dice el documento; si no, en unos meses tendrás dos versiones de la verdad.
OpenAPI
El formato estándar para describir APIs HTTP en YAML o JSON.
- Formato:
- YAML o JSON
- Mantiene:
- OpenAPI Initiative
- Antes:
- Swagger
Un documento OpenAPI describe una API HTTP, normalmente REST: sus paths (cada ruta con sus métodos, parámetros, cuerpo y respuestas por código de estado) y sus components, las piezas reutilizables, sobre todo los schemas de los datos, que se referencian con $ref. Se escribe en YAML o en JSON.
Antes se llamaba Swagger Specification; en 2015 se donó a la OpenAPI Initiative, bajo la Linux Foundation, y pasó a llamarse OpenAPI. «Swagger» es hoy el nombre de unas herramientas concretas, que verás en la próxima lección. La versión 3.1 alineó sus esquemas con JSON Schema, así que puedes reutilizar esquemas y validadores de JSON Schema, que viste en el módulo de formularios.
De un mismo documento salen documentación interactiva (Swagger UI, Redoc), clientes y servidores generados en muchos lenguajes (OpenAPI Generator), servidores de mentira (Prism) y pruebas que verifican cada respuesta.
Úsalo cuando
En casi cualquier API HTTP que vaya a usar alguien más que tú, sobre todo si es pública o la consumen varios equipos.
Ten en cuenta
Describir bien cada respuesta, incluidos los errores, lleva trabajo; un documento que solo cubre el caso feliz da una falsa seguridad. Guárdalo en el repositorio junto al código y revísalo como tal.
openapi: 3.1.0
info: { title: API del blog, version: 1.0.0 }
paths:
/posts/{id}:
get:
parameters:
- { name: id, in: path, required: true, schema: { type: integer } }
responses:
"200":
description: El post pedido
content:
application/json:
schema: { $ref: "#/components/schemas/Post" }
"404":
description: No existe ningún post con ese id
components:
schemas:
Post:
type: object
required: [id, titulo]
properties:
id: { type: integer }
titulo: { type: string }Cambios compatibles e incompatibles
Qué puedes cambiar en una API sin romper a quien ya la usa.
Añadir un endpoint, o un campo nuevo y opcional a una respuesta, es compatible: los clientes antiguos lo ignoran. Quitar o renombrar un campo, cambiar su tipo o convertir un parámetro opcional en obligatorio es incompatible: rompe a quien dependía de lo anterior.
Con el contrato en el repositorio puedes comparar dos versiones y hacer que el CI falle si detecta un cambio incompatible. Cuando uno es inevitable, márcalo antes como obsoleto (deprecated: true en OpenAPI, @deprecated en GraphQL), da tiempo a los clientes y publica la versión nueva por separado, por ejemplo en /v2/.
Úsalo cuando
Cada vez que tocas una API publicada, y sobre todo si la usan apps móviles: hay usuarios que tardan meses en actualizar.
Ten en cuenta
Algunos cambios rompen sin que lo parezca: añadir un valor nuevo a un enum puede hacer fallar a un cliente que no sabe qué hacer con él, y cambiar el orden por defecto de una lista también es un cambio de contrato.
# Compatible: un campo nuevo y opcional
properties:
titulo: { type: string }
portada: { type: string } # los clientes antiguos lo ignoran
# Incompatible: renombrar rompe a quien leía «titulo»
properties:
nombre: { type: string } # antes se llamaba tituloNota
OpenAPI no es solo para REST «puro»: describe cualquier API sobre HTTP con peticiones y respuestas, incluida una que use JSON:API o una que llame a sus endpoints como acciones.
Contratos para APIs de eventos: AsyncAPI
OpenAPI describe una conversación de petición y respuesta. Pero en una arquitectura por eventos nadie pregunta: un servicio publica «pedido creado» en un tema de Kafka o de MQTT y otros reaccionan. Ese intercambio también necesita un contrato.
AsyncAPI
La idea de OpenAPI aplicada a canales y mensajes: Kafka, MQTT, WebSocket…
- Formato:
- YAML o JSON
- Protocolos:
- Kafka, MQTT, AMQP, WebSocket…
- Mantiene:
- Linux Foundation
AsyncAPI describe una API dirigida por eventos: los servidores y su protocolo (Kafka, MQTT, AMQP, WebSocket…), los canales por los que circulan mensajes, como un tema de Kafka o una ruta de WebSocket, los mensajes que viajan por cada uno, con el esquema de su contenido, y las operaciones: qué envía y qué recibe tu aplicación.
Se parece a propósito a OpenAPI: YAML o JSON, info, components reutilizables y esquemas compatibles con JSON Schema. Y su propósito es el mismo: generar documentación y código, y que los equipos sepan qué temas existen y qué forma tiene cada mensaje sin leer el código de otros. Es un proyecto de la Linux Foundation.
Úsalo cuando
Cuando varios servicios se comunican por mensajes y los equipos nuevos no saben qué eventos existen ni qué contienen, o para documentar los mensajes de un WebSocket.
Ten en cuenta
La versión 3 cambió bastante la estructura respecto a la 2 (por ejemplo, separó las operaciones de los canales): comprueba qué versión admiten tus herramientas. Y el contrato describe los mensajes, no garantiza su entrega: eso depende del broker.
asyncapi: 3.0.0
info: { title: Eventos de pedidos, version: 1.0.0 }
servers:
produccion: { host: broker.ejemplo.com:8883, protocol: mqtt }
channels:
estadoPedido:
address: pedidos/{pedidoId}/estado
parameters:
pedidoId: { description: Identificador del pedido }
messages:
cambioDeEstado:
payload:
type: object
properties:
estado: { type: string, enum: [preparando, en-reparto, entregado] }
operations:
avisarEstado:
action: send
channel: { $ref: "#/channels/estadoPedido" }Una API GraphQL hecha de muchas: Federation
En GraphQL el contrato es el esquema. Mientras un solo equipo lo mantiene, todo va bien; pero cuando usuarios, catálogo, pedidos y reseñas son de equipos distintos, un único servidor con un único esquema se convierte en un cuello de botella.
GraphQL Federation
Varios servicios GraphQL, cada uno de un equipo, unidos en un solo supergrafo.
- Piezas:
- Router y subgrafos
- Más extendida:
- Apollo Federation
Con Federation, cada equipo mantiene un subgrafo: un servicio GraphQL normal con su parte del esquema. Un router (o gateway) compone todos los subgrafos en un supergrafo y lo expone como una sola API. El cliente consulta el supergrafo sin saber que detrás hay varios servicios; el router planifica la consulta y pide a cada subgrafo su parte.
La clave son las entidades: tipos que varios subgrafos comparten, marcados con @key. El subgrafo de catálogo define Producto con sus campos y el de reseñas añade al mismo Producto el campo resenas. Cuando una consulta pide ambos, el router usa la clave (id) para unir los datos, y cada subgrafo sabe encontrar un producto a partir de ella con un resolver de referencia.
La implementación más extendida es Apollo Federation, con su router; existen otros routers compatibles con la misma especificación.
Úsalo cuando
Cuando varios equipos con dominios distintos quieren publicar su parte del grafo de forma independiente y los clientes necesitan una sola API.
Ten en cuenta
Añade piezas que operar: el router es crítico y la composición de esquemas debe comprobarse en CI antes de desplegar un subgrafo. Para una app con un solo equipo, un servidor GraphQL normal es mucho más sencillo.
# Subgrafo «catálogo»
type Producto @key(fields: "id") {
id: ID!
nombre: String!
precio: Float!
}
# Subgrafo «reseñas»: añade un campo a la misma entidad
type Producto @key(fields: "id") {
id: ID!
resenas: [Resena!]!
}
type Resena { nota: Int! texto: String }
# El cliente consulta el supergrafo como si fuera una sola API
query { producto(id: "p1") { nombre precio resenas { nota } } }Atención
Los nombres de GraphQL solo admiten letras sin tilde, números y guiones bajos: por eso el ejemplo usa resenas y no «reseñas». En Federation 2, cada subgrafo además importa @key con una directiva @link, que se ha omitido para abreviar.
Interactivo
Formatos de contrato
Cada paradigma de API tiene su forma de escribir el contrato. Activa los que quieras comparar.
| Característica | OpenAPI | AsyncAPI | Esquema GraphQL |
|---|---|---|---|
| Describe | APIs HTTP de petición y respuesta | APIs dirigidas por eventos | Una API GraphQL |
| Formato | YAML o JSON | YAML o JSON | SDL de GraphQL |
| Unidad principal | Rutas y métodos (paths) | Canales, mensajes y operaciones | Tipos y campos |
| Qué genera | Documentación, clientes, servidores y mocks | Documentación y código | Tipos para el cliente; con Federation, el supergrafo |
| Uso típico | APIs REST públicas e internas | Kafka, MQTT, AMQP, WebSocket | APIs GraphQL, de uno o de varios equipos |
Ponlo en práctica
Saber qué es cada cosa es la mitad del trabajo. La otra mitad es reconocer cuándo usarla.
Interactivo
¿Qué usarías?
Lee cada situación y elige la opción que mejor la resuelve. Fallar aquí no cuenta: es para practicar.
Tu API REST tiene cuarenta endpoints. Quieres generar desde un único documento la documentación interactiva, un cliente TypeScript y un servidor de mentira para que el frontend trabaje sin esperar al backend.
Para recordar
- Un contrato de API describe en un documento lo que la API acepta y devuelve, y lo entienden tanto las personas como las máquinas.
- OpenAPI describe APIs HTTP en YAML o JSON, y de él salen documentación, clientes, mocks y pruebas; desde la 3.1, sus esquemas son JSON Schema.
- AsyncAPI aplica la misma idea a las APIs de eventos: servidores, canales, mensajes y operaciones sobre Kafka, MQTT o WebSocket.
- GraphQL Federation une los subgrafos de varios equipos en un supergrafo que un router expone como una sola API; las entidades con
@keyson el punto de unión. - Un contrato solo sirve si se comprueba: valida la implementación y detecta los cambios incompatibles en CI.
Comprueba lo aprendido
Cuestionario
Responde las 5 preguntas para completar la lección.
1.¿Qué diferencia a OpenAPI de AsyncAPI?
2.¿Qué cambio importante trajo OpenAPI 3.1?
3.En GraphQL Federation, ¿qué hace
@key(fields: "id")en un tipo?4.¿Cuál de estos cambios en una API publicada es compatible con los clientes que ya la usan?
5.Escribes a mano el documento OpenAPI, pero nadie comprueba que el servidor lo cumpla. ¿Qué es lo más probable con el tiempo?