Saltar al contenido principal

OpenAPI Specification

Código Completo
El resultado final del código desarrollado en este documento se puede encontrar en el monorepo de GitHub springboot-demo-projects, bajo el tag openapi-spec.

Dado un Film con la siguiente estructura:

{
"id": 42,
"title": "ACADEMY DINOSAUR",
"description": "A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies",
"releaseYear": 2006,
"rating": "PG",
"lengthMinutes": 86,
"language": "English"
}

Queremos que nuestra aplicación Spring Boot exponga estos endpoints:

  • /api/films lista todas las películas.
  • /api/films/{id} obtiene una película por identificador.
Archivos a Crear/Modificar
File Tree
├── ...
└── src
├── main
│ ├── ...
│ └── resources
│ ├── ...
│ └── openapi.yaml
└── ...

OpenAPI Specification

OpenAPI Specification (OAS) define una interfaz estándar e independiente del lenguaje para las API HTTP, que permite tanto a humanos como a ordenadores descubrir y comprender las capacidades del servicio sin necesidad de acceder al código fuente, la documentación o inspeccionar el tráfico de red. Cuando está correctamente definida, un usuario puede comprender e interactuar con el servicio remoto con una mínima lógica de implementación.

Para una mejor visualización, podés copiar y pegar la especificación OpenAPI YAML del repositorio en Swagger Editor o usar OpenAPI (Swagger) Editor en IntelliJ IDEA.

Preview Browser 53739d4afeda849a502ca156fa53e9fb
  • Todas las respuestas de error se verían así:

    {
    "instance": "/api/something",
    "timestamp": "2026-01-03T17:11:50.826722328Z",
    "trace": "9482c151-b417-43ff-9dbb-ee12b84e5d99",
    "status": 404,
    "title": "Not Found",
    "detail": "No static resource for request '/'."
    }
  • Las respuestas de /api/films se verían así:

    {
    "instance": "/api/films",
    "timestamp": "2026-01-03T17:11:50.826722328Z",
    "trace": "9482c151-b417-43ff-9dbb-ee12b84e5d99",
    "status": 200,
    "data": [
    {
    "id": 42,
    "title": "ACADEMY DINOSAUR",
    "description": "An Epic Drama of a Feminist And a Mad Scientist",
    "releaseYear": 2006,
    "rating": "PG",
    "lengthMinutes": 86,
    "language": "English"
    }
    ]
    }
  • Las respuestas de /api/films/{id} se verían así:

    {
    "instance": "/api/films/42",
    "timestamp": "2026-01-03T17:11:50.826722328Z",
    "trace": "9482c151-b417-43ff-9dbb-ee12b84e5d99",
    "status": 200,
    "data": {
    "id": 42,
    "title": "ACADEMY DINOSAUR",
    "description": "An Epic Drama of a Feminist And a Mad Scientist",
    "releaseYear": 2006,
    "rating": "PG",
    "lengthMinutes": 86,
    "language": "English"
    }
    }

El Envelope Pattern

Tal vez estás mirando los ejemplos de respuesta y pensando, "¿Qué onda con todo ese relleno extra? ¿Por qué envolver los datos reales de la película dentro de un objeto data?"

{
"instance": "/api/films/42",
"timestamp": "...",
"trace": "...",
"status": 200,
"data": {
"id": 42,
"title": "ACADEMY DINOSAUR",
//...
}
}

Esa es una elección deliberada llamada Envelope Pattern. Y una vez que empezás a usarlo, no volvés atrás.

Pensalo como el correo físico. Cada pieza de correo que recibís, ya sea una tarjeta de cumpleaños o una factura, viene en un sobre. El sobre tiene información estándar: una dirección de remitente, una dirección de destino, un sello. El mensaje real está adentro.

Nuestras respuestas de API funcionan igual. El schema ResponseMetadata es nuestro sobre.

# Base metadata for all responses
ResponseMetadata:
type: object
properties:
instance:
# ...
status:
# ...
timestamp:
# ...
trace:
# ...

Cada respuesta de la API, ya sea un éxito (200 OK) o un error (404 Not Found, 500 Internal Server Error, etc.), va a tener esta misma estructura de nivel superior.

  • El payload real va adentro de la propiedad data para respuestas exitosas.
  • Para errores, reemplazamos data por title y detail así seguimos adheridos a Problem Details for HTTP APIs.

¿Para Qué Molestarse?

Consistencia. Ese es todo el juego.

  1. Sanidad del lado del cliente: El desarrollador que está construyendo el frontend o la app mobile puede escribir una pieza de código para manejar todas las respuestas de la API. Siempre sabe dónde encontrar el ID de trace para mostrarle a un usuario que está reportando un bug. Puede construir un manejador de errores genérico que siempre busque title y detail. No está adivinando si el payload es el objeto mismo, un array, o alguna forma rara de error.
  2. A prueba de futuro: ¿Y si necesitamos agregar algún otro metadata? No hay problema. Simplemente agregamos un nuevo campo al nivel del envelope. La parte de data queda intacta, y no rompemos la lógica de parsing del cliente para los datos reales de la película.
  3. Se ve profesional: Una API con una estructura de respuesta consistente se siente sólida y bien pensada. Una inconsistente se siente amateur y es un dolor de cabeza para trabajar.

Paginación

El endpoint /api/films "lista todas las películas". Pero si tomás eso literalmente en el mundo real, te va a ir mal.

Imaginate que tu base de datos tiene un millón de películas. Intentar traer todas de una es una receta para el desastre. Tu base de datos se va a colgar, la memoria de tu servidor se va a evaporar, y el pobre navegador del usuario se va a colgar tratando de renderizar un payload JSON colosal.

Aquí es donde entra la paginación. Es la simple idea de dividir un conjunto de resultados grande en páginas más pequeñas y manejables. En lugar de pedir todas las películas, pedís "página 1 con 20 películas", después "página 2 con 20 películas", y así sucesivamente.

Esto es práctica estándar en todos lados. Pensá en los resultados de búsqueda de Google o en navegar productos en Amazon. Nunca recibís todo de una; recibís una página y un botón de "Siguiente".

Para mantener las cosas simples por ahora, la especificación no incluye parámetros de paginación. Vamos a agregar paginación apropiada más tarde.