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.
Si revisamos el detalle del GET podemos visualizar lo siguiente:
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.
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:
y observemos la parte de la respuesta que ejemplifica el schema
que documentamos.
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.
Comentarios