Como generar documentación automática con Swagger en Micronaut con Kotlin y Maven

0
168
Logo de Swagger, herramienta para la generación automática de documentación de APIs
Logo de Swagger

Índice

  1. Introducción
  2. Instalación del proyecto
  3. Creación de un controlador
  4. Generación de la documentación
  5. Anotaciones
  6. 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 su id. En este ejemplo, devuelve un coche ficticio con los datos de un Ford Focus.
  • POST /cars: Añade un coche nuevo, aceptando un objeto Car 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.

Referencias

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad