Índice
- Introducción
- Instalación del proyecto
- Creación de un controlador
- Generación de la documentación
- Anotaciones
- Configuración del plugin
Introducción
OpenAPI, también conocido como Swagger, es una especificación clave que permite comunicar de manera estandarizada y eficiente las interfaces de APIs. Esta herramienta es esencial para facilitar desarrollos paralelos, asegurando que los contratos entre las distintas partes del sistema encajen a la perfección.
La documentación generada con OpenAPI puede estar en formato JSON o YAML, aunque la especificación oficial recomienda YAML por su simplicidad y legibilidad. Una de las grandes ventajas de Swagger es su interfaz visual, que hace que la documentación sea accesible tanto para desarrolladores como para terceros que necesiten consultar información sobre la API.
Sin embargo, una documentación desactualizada o incorrecta puede ser más perjudicial que no tener ninguna. El verdadero reto radica en mantenerla actualizada, ya que hacerlo manualmente puede ser propenso a errores y suele ser un proceso largo al que no siempre se le dedica el tiempo necesario.
En este tutorial, explicaremos cómo generar la documentación Swagger en Micronaut de forma automática utilizando Kotlin y Maven, facilitando el proceso y garantizando que siempre esté alineada con la implementación de nuestra API.
1. Instalación del proyecto
El primer paso para generar documentación automática es crear un proyecto Micronaut con Kotlin y Maven. Para ello, utilizamos el siguiente comando utilizando la Micronaut Command Line Interface:
$ mn create-app autogenerated-openapi-app --build=maven --lang=kotlin --features=openapi
Este comando generará la estructura del proyecto con las configuraciones necesarias para trabajar con OpenAPI. Al crear el proyecto, se añaden dos configuraciones clave en el archivo pom.xml
que vale la pena destacar.
En primer lugar, dentro de la configuración de plugins, encontramos el kotlin-maven-plugin
. Este incluye un annotationProcessorPath
que apunta al procesador de anotaciones de OpenAPI, responsable de generar la documentación de la API automáticamente durante la compilación, basándose en las anotaciones presentes en los controladores.
<annotationProcessorPath>
<groupId>io.micronaut.openapi</groupId>
<artifactId>micronaut-openapi</artifactId>
<version>${micronaut.openapi.version}</version>
</annotationProcessorPath>
En Maven, un annotation processor (procesador de anotaciones) es una herramienta que se ejecuta durante el proceso de compilación para escanear, procesar y generar código basado en las anotaciones definidas en el código fuente. Esto asegura que la documentación esté siempre actualizada con los cambios en el código.
Por otro lado, se incluye la dependencia micronaut-openapi-annotations
, que nos proporciona anotaciones específicas de Micronaut. Estas nos permiten añadir información adicional a la documentación que se genera automáticamente, completando y enriqueciendo la descripción de nuestra API.
<dependency>
<groupId>io.micronaut.openapi</groupId>
<artifactId>micronaut-openapi-annotations</artifactId>
<scope>provided</scope>
</dependency>
2. Creación de un controlador
Para generar documentación automáticamente con Swagger, es esencial contar con un controlador que gestione las peticiones y respuestas de nuestra API. En este paso, crearemos un controlador sencillo que manejará una entidad llamada Car
.
Primero, navegamos al paquete src/main/kotlin/my.openapi.app
dentro de nuestro proyecto. Allí crearemos una clase de datos (data class) que representará los coches que nuestro controlador gestionará. La clase Car
será la siguiente:
data class Car(
val id: Int,
val brand: String,
val model: String,
val km: Int,
val year: Int
)
Esta clase define los atributos de un coche, incluyendo su id
, la marca (brand
), el modelo (model
), el kilometraje (km
), y el año de fabricación (year
). A continuación, creamos el controlador que gestionará las solicitudes relacionadas con coches, llamado CarsController
.
@Controller("/cars")
class CarsController {
@Get
fun getCars(): List {
return listOf()
}
@Get("/{id}")
fun getCar(id: Int): Car {
return Car(1, "Ford", "Focus", 1000, 2021)
}
@Post
fun addCar(car: Car): Car {
return car
}
}
En este controlador hemos definido tres rutas principales:
GET /cars
: Devuelve una lista vacía de coches por ahora. Posteriormente, podrías añadir lógica para obtener coches desde una base de datos o cualquier otra fuente de datos.GET /cars/{id}
: Devuelve un coche específico basado en suid
. En este ejemplo, devuelve un coche ficticio con los datos de un Ford Focus.POST /cars
: Añade un coche nuevo, aceptando un objetoCar
en la petición.
Este controlador, al estar anotado con las etiquetas de Micronaut como @Controller
, @Get
y @Post
, será automáticamente procesado por el plugin de OpenAPI para generar la documentación. Cada uno de los métodos expone un endpoint de la API que será documentado, mostrando los detalles sobre los parámetros de entrada y los tipos de respuesta.
Gracias a esto, cada vez que compiles tu proyecto, se generará una documentación Swagger actualizada basada en este controlador.
3. Generación de la documentación
Con todo listo, es momento de comprobar si lo que hemos hecho genera la documentación Swagger de forma correcta. Para ello, simplemente debemos compilar nuestro proyecto. Ejecuta el siguiente comando:
$ mvn compile
Al finalizar la compilación, la documentación generada se ubicará en el directorio target/classes/META-INF/swagger
. Allí encontrarás un archivo YAML con el nombre de tu aplicación, en este caso: my-openapi-app-0.0.yml
.
Este archivo contiene la especificación OpenAPI (en este caso, la versión 3.0.1) con toda la documentación generada automáticamente a partir de nuestro controlador y la clase Car
. El contenido básico del archivo YAML podría verse así:
openapi: 3.0.1
info:
title: my-openapi-app
version: "0.0"
paths:
/cars:
get:
operationId: getCars
responses:
"200":
description: getCars 200 response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Car'
post:
operationId: addCar
requestBody:
content:
application/json:
schema:
required:
- car
type: object
properties:
car:
$ref: '#/components/schemas/Car'
required: true
responses:
"200":
description: addCar 200 response
content:
application/json:
schema:
$ref: '#/components/schemas/Car'
/cars/{id}:
get:
operationId: getCar
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int32
responses:
"200":
description: getCar 200 response
content:
application/json:
schema:
$ref: '#/components/schemas/Car'
components:
schemas:
Car:
required:
- brand
- id
- km
- model
- year
type: object
properties:
id:
type: integer
format: int32
brand:
type: string
model:
type: string
km:
type: integer
format: int32
year:
type: integer
format: int32
Como puedes ver, el esquema de la clase Car
se ha generado automáticamente bajo la sección components/schemas
, y el archivo ha identificado correctamente los métodos HTTP (GET
y POST
) de nuestros endpoints /cars
y /cars/{id}
. También ha añadido los parámetros correspondientes, como el id
para el endpoint de obtención de un coche específico, junto con sus tipos de datos.
No obstante, aunque Swagger ha generado automáticamente una gran parte de la documentación, siempre es posible refinarla y agregar detalles adicionales mediante el uso de anotaciones. Esto nos permitirá personalizar aún más los detalles de la API, como las descripciones de los parámetros y respuestas.
4. Anotaciones
Ahora que ya hemos generado la documentación básica, vamos a enriquecerla añadiendo algunas anotaciones. Esto nos permitirá personalizar aún más los detalles de nuestra API y ofrecer una documentación más completa. Si deseas explorar todas las anotaciones disponibles, puedes encontrarlas en la documentación oficial de OpenAPI.
Información general del proyecto
Para comenzar, vamos a añadir información general sobre nuestro proyecto en la clase principal Application.kt
. Esto se hace mediante la anotación @OpenAPIDefinition
:
@OpenAPIDefinition(
info = Info(
title = "Api de coches",
version = "1.0.0",
description = "Servicio de coches",
license = License(name = "Apache 2.0", url = "https://foo.bar"),
contact = Contact(url = "https://foo.bar/contact", name = "Victor", email = "victor@test.com")
)
)
object Api {
fun main(args: Array) {
run(*args)
}
}
Con estas anotaciones, la información general del proyecto aparecerá en el archivo YAML generado de la siguiente manera:
openapi: 3.0.1
info:
title: Api de coches
description: Servicio de coches
contact:
name: Victor
url: https://foo.bar/contact
email: victor@test.com
license:
name: Apache 2.0
url: https://foo.bar
version: 1.0.0
Además, como podemos observar, el nombre del archivo de la documentación ha cambiado a api-de-coches-1.0.0.yml
(${title}-{version}.yml
), tomando como referencia el título y la versión definidos en las anotaciones.
Anotaciones en los endpoints
Ahora, vamos a añadir más detalles a los endpoints de nuestro controlador CarsController
. Por ejemplo, podemos modificar el método POST
para indicar que puede producirse un error 400 si los datos enviados no son válidos.
Añadimos las siguientes anotaciones:
@Post
@Operation(
summary = "Adds a Car to the collection",
responses = [
ApiResponse(
responseCode = "400",
description = "When the Car is not valid",
content = [Content(mediaType = "application/pdf", schema = Schema(implementation = Error::class))],
),
],
)
fun addCar(car: Car): Car {
return car
}
El resultado en la documentación será el siguiente:
post:
summary: Adds a Car to the collection
operationId: addCar
requestBody:
content:
application/json:
schema:
required:
- car
type: object
properties:
car:
$ref: '#/components/schemas/Car'
required: true
responses:
"400":
description: When the Car is not valid
content:
application/pdf:
schema:
$ref: '#/components/schemas/Error'
"200":
description: addCar 200 response
content:
application/json:
schema:
$ref: '#/components/schemas/Car'
Como puedes ver, aunque no hemos especificado un estado 200
, Swagger lo ha mantenido por defecto. La anotación adicional de respuesta 400
ahora está incluida en la documentación, proporcionando más detalles sobre el comportamiento del endpoint cuando ocurre un error.
Anotaciones en los esquemas
Finalmente, podemos mejorar la definición del esquema de nuestro objeto Car
añadiendo anotaciones que describan mejor su propósito. Modificamos la clase Car
de la siguiente manera:
@Schema(name = "CarDTO", description = "Car data transfer object")
data class Car(
val id: Int,
val brand: String,
val model: String,
val km: Int,
val year: Int
)
El resultado en la documentación será el siguiente:
components:
schemas:
CarDTO:
required:
- brand
- id
- km
- model
- year
type: object
properties:
id:
type: integer
format: int32
brand:
type: string
model:
type: string
km:
type: integer
format: int32
year:
type: integer
format: int32
description: Car data transfer object
Gracias a estas anotaciones, el esquema del objeto Car
ha sido renombrado a CarDTO
en la documentación, y ahora incluye una descripción clara de su propósito.
5. Configuración del plugin
En esta sección, exploraremos cómo modificar la configuración del plugin OpenAPI para adaptarlo a nuestras necesidades. Puedes consultar todos los parámetros configurables en este enlace.
Para realizar modificaciones en la configuración, primero debemos crear un archivo de configuración llamado openapi.properties
en el directorio raíz de nuestro proyecto. Este archivo nos permitirá personalizar varios aspectos de la generación de la documentación.
En nuestro caso, vamos a modificar la ruta de exportación de la documentación generada para evitar tener que buscarla en el directorio target
. Para hacerlo, abrimos el archivo openapi.properties
y añadimos la siguiente línea de código:
micronaut.openapi.target.file=cars-openapi.yml
Con esta configuración, hemos especificado que el archivo de documentación se exporte con el nombre cars-openapi.yml
en el directorio raíz de nuestro proyecto.
Ahora, ejecutamos nuevamente la compilación del proyecto con el siguiente comando:
$ mvn compile
Al finalizar la compilación, podrás ver que el archivo de documentación ahora se ha generado en el directorio raíz con el nombre cars-openapi.yml
. Esto facilita el acceso a la documentación sin necesidad de navegar por el directorio target
, simplificando su uso y distribución.