MicroProfile: OpenAPI

Tabla de Contenidos

  1. @Operation
  2. @Parameter
  3. @APIResponse, @Content y @Schema
  4. Conclusión

En este artículo les escribiré sobre cómo podemos exponer la documentación de nuestros API a través de OpenAPI en MicroProfile 3.0

En mi experiencia, uno de los principales problemas que se tienen con un API Rest es la creación de su documentación empleado Swagger. En varios casos aprender a escribir la documentación resulta laborioso y tiene una curva de aprendizaje que debe ser considerada en los desarrollos. Y el segundo problema que he percibido es que una vez que se tiene la documentación del API, esta tiende a desfasarse respecto al código que respalda al API.

¿A qué me refiero? La documentación no representa la realidad de la función del API, posiblemente los desarrolladores extendieron el API debido a la necesidad del negocio pero olvidaron ir llevando al día la documentación.

Dicho lo anterior, si hacemos uso de OpenAPI con MicroProfile veremos que es mucho más sencillo y simple crear y mantener nuestros API debidamente documentados. En este artículo usaré Quarkus para ejemplificar su uso.

En primer lugar debemos agregar la dependencia adecuada, que en este caso sería:

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-openapi</artifactId>
 </dependency>

Imaginemos entonces que tenemos un servicio como el siguiente:

@Path("/paises")
public class PaisesResource {

    @Inject
    PaisesService service;

    @GET
    @Path("{codigo}")
    @Produces(MediaType.APPLICATION_JSON)
    public InfoPais getInfo(@PathParam("codigo") String codigo) {

        return service.getInformacion(codigo);
    }
}

Por simplicidad recibe un código y retorna la información de un país, cuya clase es la siguiente:

public class InfoPais {
    String codigo, nombre, capital;
    Long area;
    Long poblacion;

    public InfoPais() {
    }

    public InfoPais(String codigo, String nombre, String capital, Long area, Long poblacion) {
        this.codigo = codigo;
        this.nombre = nombre;
        this.capital = capital;
        this.area = area;
        this.poblacion = poblacion;
    }
....
}    

Si verificamos nuestro API obtendriamos una respuesta similar a esta:

{"area":51100,
"capital":"San José",
"codigo":"CR",
"nombre":"Costa Rica",
"poblacion":4857274}

Y si usamos el url http://localhost:8080/openapi tendríamos esto:

openapi: 3.0.1
info:
  title: Generated API
  version: "1.0"
paths:
  /paises/{codigo}:
    get:
      parameters:
      - name: codigo
        in: path
        required: true
        schema:
          $ref: '#/components/schemas/String'
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InfoPais'
components:
  schemas:
    String:
      type: string
    InfoPais:
      type: object
      properties:
        area:
          format: int64
          type: integer
        capital:
          type: string
        codigo:
          type: string
        nombre:
          type: string
        poblacion:
          format: int64
          type: integer

Observen que es la documentación mínima de nuestro API en Swagger. Por supuesto, resulta más fácil de leerla usando Swagger UI y que en nuestro caso -si usamos Quarkus- podemos acceder en: http://localhost:8080/swagger-ui/ y nos presenta en primer instancia el detalle general de nuestro API.

Ejemplo

Si revisamos el detalle del GET podemos visualizar lo siguiente:

Ejemplo

Observen que indica los parámetros y datos de retorno de nuestro servicio. Incluyendo un ejemplo y la posibilidad de probar directamente el servicio.

Y finalmente un detalle de los schemas de entrada y salida.

Ejemplo

Lo anterior no está nada mal como punto de inicio y sin haber escrito una línea de documentación. Pero veamos como podemos enriquecer nuestra documentación empleando algunas de las anotaciones básicas de OpenAPI.

@Operation

Esta anotación nos permite describir una operación de nuestro API. Y se usa de la siguiente manera:

   @GET
   @Path("{codigo}")
   @Produces(MediaType.APPLICATION_JSON)
   @Operation(summary = "Obtiene información general de un país",
             description = "Obtiene información de un país: código, nombre, capital, área en Km cuadrados y población según censo más reciente.")
   public InfoPais getInfo(@PathParam("codigo") String codigo) {

       return service.getInformacion(codigo);
   }

y si observamos el resultado tenemos lo siguiente:

openapi: 3.0.1
info:
  title: Generated API
  version: "1.0"
paths:
/paises/{codigo}:
    get:
      summary: Obtiene información general de un país
      description: 'Obtiene información de un país: código, nombre, capital, área
        en Km cuadrados y población según censo màs reciente.'
      parameters:
      - name: codigo
        in: path
        required: true
        schema:
          $ref: '#/components/schemas/String'

@Parameter

Esta anotación nos permite describir los parámetros que recibe nuestro servicio. Consideremos el siguiente ejemplo:

public InfoPais getInfo(
            @Parameter(
                    description = "El código del país que se desea consultar.",
                    required = true,
                    example = "CR",
                    schema = @Schema(type = SchemaType.STRING))
            @PathParam("codigo") String codigo) {

y su resultado sería:

/paises/{codigo}:
   get:
     summary: Obtiene información general de un país
     description: 'Obtiene información de un país: código, nombre, capital, área
       en Km cuadrados y población según censo màs reciente.'
     parameters:
     - name: codigo
       in: path
       description: El código del país que se desea consultar.
       required: true
       schema:
         type: string
       example: CR

@APIResponse, @Content y @Schema

La primera de estas anotaciones nos permite describir una respuesta de una operación de nuestro API, la segunda nos permite brindar un schema y ejemplo para un mediaType específico y la última define el tipo de datos de entrada o de salida. Ilustremos esto:

@APIResponse(
  responseCode = "200",
  description = "Detalle con la información del país seleccionado",
  content = @Content(
          mediaType = "application/json",
          schema = @Schema(
                  type = SchemaType.OBJECT,
                  implementation = InfoPais.class)))                  

y en la clase InfoPais ponemos lo siguiente:

@Schema(name="InfoPais", description="POJO que representa la información de un país.")
public class InfoPais {

y el resultado sería:

responses:
    200:
      description: Detalle con la información del país seleccionado
      content:
        application/json:
          schema:
            description: POJO que representa la información de un país.
            type: object
            properties:

Ahora bien, también podemos agregar mayor descripción a los atributos que tenemos en nuestra respuesta; esto de la siguiente manera:

@Schema(name="InfoPais", description="POJO que representa la información de un país.")
public class InfoPais {

    String codigo, nombre, capital;
    @Schema(description = "Corresponde al área del país en kilometros cuadrados.")
    Long area;
    @Schema(description = "Población del país de acuerdo al último censo.")
    Long poblacion;

y el resultado sería:

application/json:
              schema:
                description: POJO que representa la información de un país.
                type: object
                properties:
                  area:
                    format: int64
                    description: Corresponde al área del país en kilometros cuadrados.
                    type: integer
                  capital:
                    type: string
                  codigo:
                    type: string
                  nombre:
                    type: string
                  poblacion:
                    format: int64
                    description: Población del país de acuerdo al último censo.
                    type: integer

Y si quisieramos podemos agregar ejemplos del schema o de los parámetros. Si queremos ilustrar la respuesta podemos hacerlo así:

@Schema(description = "Código del País.", example = "CR")
String codigo;

@Schema(description = "Nombre del país.", example = "CR")
String nombre;

@Schema(description = "Capital del País.", example = "San José")
String capital;

@Schema(description = "Corresponde al área del país en kilometros cuadrados.", example = "51100")
Long area;
@Schema(description = "Población del país de acuerdo al último censo.", example = "431765123")
Long poblacion;

y eso nos permite obtener esto:

properties:
   area:
     format: int64
     description: Corresponde al área del país en kilometros cuadrados.
     type: integer
     example: "51100"
   capital:
     description: Capital del País.
     type: string
     example: San José
   codigo:
     description: Código del País.
     type: string
     example: CR
   nombre:
     description: Nombre del país.
     type: string
     example: CR
   poblacion:
     format: int64
     description: Población del país de acuerdo al último censo.
     type: integer

Finalmente, si visualizamos esta documentación se tendría esto:

Ejemplo

y observemos la parte de la respuesta que ejemplifica el schema que documentamos.

Ejemplo

Conclusión


Espero que este artículo les permita ver los beneficios que se obtienen de usar OpenAPI, los beneficios inherentes de generar nuestra documentación de una manera sencilla y sin tener que aprender Swagger.

Written by

Gerardo Arroyo Arce

Arquitecto de Soluciones AWS certificado x10 con pasión por compartir conocimiento. Como miembro activo de AWS Community Builders, ex-AWS Ambassador y AWS User Group Leader, me dedico a construir puentes entre la tecnología y las personas. Desarrollador Java de corazón y consultor independiente, llevo la arquitectura cloud más allá de la teoría a través de conferencias internacionales y soluciones del mundo real. Mi curiosidad insaciable por aprender y compartir me mantiene en constante evolución junto a la comunidad tech.

Inicia la conversación