La documentación es esencial para crear API REST. En este tutorial, analizaremos SpringDoc, que simplifica la generación y el mantenimiento de documentación de API según la especificación OpenAPI 3 para aplicaciones Spring Boot 3.x.
Configuración de springdoc-openapi
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.5</version>
</dependency>
Además, la versión OpenAPI de Springdoc debe ser compatible con la versión de Spring Boot según la matriz de compatibilidad.
Ruta de descripción de OpenAPI
Después de configurar la dependencia correctamente, podemos ejecutar nuestra aplicación y encontrar las descripciones de OpenAPI en /v3/api-docs, que es la ruta predeterminada:
http://localhost:8080/v3/api-docs
Además, podemos personalizar la ruta en application.properties mediante la propiedad springdoc.api-docs. Por ejemplo, podemos establecer la ruta en /api-docs:
springdoc.api-docs.path=/api-docs
Luego podremos acceder a los documentos en:
http://localhost:8080/api-docs
Por defecto, las definiciones de OpenAPI están en formato JSON. Para el formato YAML, podemos obtener las definiciones en:
http://localhost:8080/api-docs.yaml
Integration With Swagger UI
Además de generar la especificación OpenAPI 3, podemos integrar springdoc-openapi con Swagger UI para interactuar con nuestra especificación de API y ejecutar los endpoints.
La dependencia springdoc-openapi ya incluye Swagger UI, por lo que podemos acceder a la documentación de la API en:
http://localhost:8080/swagger-ui/index.html
Compatibilidad con propiedades de swagger-ui
La biblioteca springdoc-openapi también admite propiedades swagger-ui. Estas pueden usarse como propiedades de Spring Boot con el prefijo springdoc.swagger-ui.
Por ejemplo, podemos personalizar la ruta de la documentación de nuestra API modificando la propiedad springdoc.swagger-ui.path
dentro del archivo application.properties
:
springdoc.swagger-ui.path=/swagger-ui-custom.html
Ahora la documentación de nuestra API estará disponible en http://localhost:8080/swagger-ui-custom.html
.
Como otro ejemplo, podemos ordenar las rutas de la API según sus métodos HTTP con la propiedad springdoc.swagger-ui.operationsSorter
:
springdoc.swagger-ui.operationsSorter=method
API de muestra
Supongamos que nuestra aplicación tiene un controlador para administrar pedidos:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderRepository repository;
@GetMapping("/{id}")
public Order findById(@PathVariable long id) {
return repository.findById(id)
.orElseThrow(() -> new OrderNotFoundException());
}
@GetMapping("/")
public Collection<Order> findOrders() {
return repository.getOrders();
}
@PutMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
public Order updateOrder(
@PathVariable("id") final String id, @RequestBody final Order order) {
return order;
}
}
Luego, cuando ejecutamos nuestra aplicación, podemos ver la documentación en:
http://localhost:8080/swagger-ui-custom.html
Integración de springdoc-openapi con Spring WebFlux
También podemos habilitar las propiedades springdoc.swagger-ui en una aplicación Spring WebFlux. Esto facilita la integración entre springdoc-openapi y Swagger UI en nuestra aplicación Spring WebFlux. Para ello, añadimos la dependencia springdoc-openapi-webflux-ui en nuestro archivo pom.xml
:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.8.5</version>
</dependency>
Exponer la información de paginación
Spring Data JPA se integra con Spring MVC a la perfección. Un ejemplo de esta integración es la compatibilidad con Pageable:
@GetMapping("/filter")
public Page<Order> filterOrders(@ParameterObject Pageable pageable) {
return repository.getOrders(pageable);
}
La compatibilidad con Pageable está disponible de fábrica desde springdoc-openapi v1.6.0. Los parámetros de consulta de página, tamaño y ordenación se añaden a la documentación generada.
Uso del complemento Maven springdoc-openapi
La biblioteca springdoc-openapi proporciona un plugin de Maven, springdoc-openapi-maven-plugin
, que genera descripciones de OpenAPI en formatos JSON y YAML.
El plugin springdoc-openapi-maven-plugin funciona con el plugin spring-boot-maven
. Maven ejecuta el plugin openapi durante la fase de pruebas de integración.
Veamos cómo podemos configurar el plugin en nuestro pom.xml
:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.4.3</version>
<executions>
<execution>
<id>pre-integration-test</id>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>post-integration-test</id>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
También podemos configurar el complemento para utilizar valores personalizados:
<plugin>
<executions>
.........
</executions>
<configuration>
<apiDocsUrl>http://localhost:8080/v3/api-docs</apiDocsUrl>
<outputFileName>openapi.json</outputFileName>
<outputDir>${project.build.directory}</outputDir>
</configuration>
</plugin>
Analicemos con más detalle los parámetros que podemos configurar para el plugin:
- apiDocsUrl: URL donde se puede acceder a la documentación en formato JSON; el valor predeterminado es http://localhost:8080/v3/api-docs
- outputFileName: Nombre del archivo donde se almacenan las definiciones; el valor predeterminado es openapi.json
- outputDir: Ruta absoluta del directorio donde se almacena la documentación; el valor predeterminado es ${project.build.directory}
Generación automática de documentos mediante la validación de beans JSR-303
Cuando nuestro modelo incluye anotaciones de validación de beans JSR-303, como @NotNull, @NotBlank, @Size, @Min y @Max, la biblioteca springdoc-openapi las utiliza para generar documentación de esquema adicional para las restricciones correspondientes.
Veamos un ejemplo con nuestro bean Order:
public class Order {
private long id;
@NotBlank
@Size(min = 0, max = 20)
private String customerName;
@NotBlank
@Size(min = 0, max = 30)
private String product;
}
Ahora, la documentación generada para el bean Order es un poco más informativa.
Generar documentación usando @ControllerAdvice y @ResponseStatus
El uso de @ResponseStatus
en los métodos de una clase @RestControllerAdvice
generará automáticamente la documentación de los códigos de respuesta. En esta clase @RestControllerAdvice
, los dos métodos están anotados con @ResponseStatus
:
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(ConversionFailedException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<String> handleConversion(RuntimeException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(OrderNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> handleOrderNotFound(RuntimeException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
Como resultado, ahora podemos ver la documentación para los códigos de respuesta 400 y 404.
Generar documentación usando @Operation y @ApiResponses
A continuación, veamos cómo podemos añadir una descripción a nuestra API mediante un par de anotaciones específicas de OpenAPI.
Para ello, anotaremos el punto final /api/orders/{id}
de nuestro controlador con @Operation
y @ApiResponses
:
@Operation(summary = "Get an order by its id")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Found the order",
content = { @Content(mediaType = "application/json",
schema = @Schema(implementation = Order.class)) }),
@ApiResponse(responseCode = "400", description = "Invalid id supplied",
content = @Content),
@ApiResponse(responseCode = "404", description = "Order not found",
content = @Content) })
@GetMapping("/{id}")
public Order findById(@Parameter(description = "id of order to be searched")
@PathVariable long id) {
return repository.findById(id)
.orElseThrow(() -> new OrderNotFoundException());
}
El texto que añadimos a @Operation
se ubica en el nivel de operación de la API. De igual forma, la descripción añadida a varios elementos @ApiResponse
en la anotación del contenedor @ApiResponses también es visible aquí, lo que añade significado a nuestras respuestas de la API.
No obtenemos ningún esquema para las respuestas 400 y 404 anteriores. Como definimos un @Content
vacío para ellas, solo se muestran sus descripciones.
Descripción del cuerpo de la solicitud
Al definir puntos finales que aceptan el cuerpo de una solicitud, podemos describir el contenido esperado del cuerpo en la documentación de nuestra API usando @RequestBody de las anotaciones de OpenAPI.
Por ejemplo, si pasamos un objeto Order a nuestra API para crear o actualizar pedidos, podemos anotar el cuerpo de la solicitud para documentarlo:
@Operation(summary = "Create a new order")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Order created successfully",
content = { @Content(mediaType = "application/json",
schema = @Schema(implementation = Order.class)) }),
@ApiResponse(responseCode = "400", description = "Invalid input provided") })
@PostMapping("/")
public Order createOrder(@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "Order to create", required = true,
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Order.class),
examples = @ExampleObject(value = "{ \"customerName\": \"John Doe\", \"product\": \"Laptop\" }")))
@RequestBody Order order) {
return repository.save(order);
}
Esta configuración proporciona documentación detallada del cuerpo de la solicitud, incluyendo ejemplos y validación del esquema, y permite a los usuarios de la API comprender mejor los datos que deben enviar. Además, observamos que se utilizan dos anotaciones @RequestBody
en este código:
- @RequestBody de Spring: Es necesario para que el punto final funcione en una aplicación Spring Boot, ya que vincula el cuerpo de la solicitud HTTP con nuestro parámetro de método.
- @RequestBody de OpenAPI: Documenta el cuerpo de la solicitud en nuestra definición de OpenAPI, proporcionando descripciones detalladas, validación del esquema y ejemplos para los usuarios de la API.
Compatibilidad con Kotlin
Spring Boot 2.x ofrece compatibilidad de primera clase con Kotlin. Dado que usamos la versión 3.x de Spring Boot, SpringDoc es compatible con aplicaciones escritas en Kotlin.
Crearemos una API Foo sencilla en Kotlin para comprobarlo en acción.
Tras la configuración inicial, añadiremos una clase de datos y un controlador. Los incluiremos en un subpaquete de nuestra aplicación Boot para que, al ejecutarse, seleccione nuestro FooController junto con el anterior OrderController.
@Entity
data class Foo(
@Id
val id: Long = 0,
@NotBlank
@Size(min = 0, max = 50)
val name: String = ""
)
@RestController
@RequestMapping("/")
class FooController() {
val fooList: List = listOf(Foo(1, "one"), Foo(2, "two"))
@Operation(summary = "Get all foos")
@ApiResponses(value = [
ApiResponse(responseCode = "200", description = "Found Foos", content = [
(Content(mediaType = "application/json", array = (
ArraySchema(schema = Schema(implementation = Foo::class)))))]),
ApiResponse(responseCode = "400", description = "Bad request", content = [Content()]),
ApiResponse(responseCode = "404", description = "Did not find any Foos", content = [Content()])]
)
@GetMapping("/foo")
fun getAllFoos(): List = fooList
}
Para mejorar el soporte de los tipos de Kotlin, podemos agregar esta dependencia:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId
<version>2.8.5</version>
</dependency>
Después de eso, nuestro esquema Foo se verá más informativo, como lo hizo cuando agregamos la validación de beans JSR-303.