Los niveles de madurez REST, ejemplo con HATEOAS y documentación con Swagger de un servicio con Spring Boot

Escrito por el .
java planeta-codigo
Enlace permanente Comentarios

Los niveles de madurez de una API implementada con las convenciones REST trata de aplicar los conceptos y semántica de la web y el protocolo HTTP a un servicio web. Muchas APIs que dicen ser REST no cumplen con todos los niveles de madurez para ser considerada RESTful que incluyen HATEOAS para crear enlaces entre los recursos y HAL para codificar los datos. Muchas se quedan en el nivel 2 al hacer uso únicamente de recursos y verbos, llegar a cumplir el nivel 3 para incluir controles hypermedia tiene algunas ventajas adicionales. Spring Boot proporciona soporte para crear una API que soporte el nivel de madurez 3 de REST y Springdoc permite generar la documentación de la API con Swagger.

Java

Spring

Utilizar REST para implementar un servicio es muy común por ser fácil de construir y consumir, se ha convertido en un estándar para los servicios web. REST proporciona acciones para las operaciones, cacheo, redirecciones y delegación, seguridad tanto para cifrado como para autenticación, compatibilidad hacia atrás y evolución de las APIs, escalabilidad y servicios sin estado. A pesar de su amplio uso en realidad no define ninguna especificación, es simplemente una aproximación, estilo y restricciones para construir servicios escalables basados en la web.

Cómo alternativa a los servicios REST están gRPC y GraphQL que también son capaces de utilizar como medio de transporte el protocolo HTTP pero se basan en aproximaciones diferentes.

Utilizar el protocolo HTTP no es suficiente para que en servicio se considere que implementa REST de forma completa, al implementar un servicio basado en la semántica del protocolo HTTP y la web hay varios niveles de madurez. Spring Boot ofrece soporte para implementar servicios web que cumplan con todos los niveles de madurez de REST y Springdoc crear la documentación a partir de las anotaciones de Swagger.

Los niveles de madurez REST

REST se basa en los mismos estándares que se utilizan para las páginas web, estos son el protocolo HTTP y los hiperenlaces que construyen la web. El protocolo HTTP tiene una semántica para cada una de sus operaciones que incluyen las diferentes operaciones básicas de CRUD (crear, leer, actualizar y eliminar), códigos de estado para el resultado de la operación y direcciones de los recursos. Las páginas web devuelven HTML, los servicios REST como formato de datos suelen emplear JSON. Los servicios REST son la aplicación de los mismos conceptos de la web a integración de servicios para computadoras, en vez de a humanos o navegadores web.

Los niveles de madurez de REST son la aplicación de la semántica del protocolo HTTP y la web a los servicios web. Cada uno de estos niveles incluye una aplicación del protocolo HTTP y la web que el servicio REST debe seguir.

Muchos servicios que se denominan REST no cumplen con todos los niveles de madurez de REST, no es suficiente utilizar HTTP como transporte, utilizar URLs bonitas para los recursos y usar verbos HTTP. No son pocos los servicios que se denominan como REST pero que no implementan todos los niveles de madurez.

Nivel 0, transporte HTTP

En este nivel simplemente se usa HTTP como medio de transporte para hacer llamadas remotas sin usar la semántica de la web. Cada petición tiene su propia dirección de endpoint, estas URLs puede que sigan algunas convenciones como utilizar guiones medios para mejorar legibilidad de las URLs, preferiblemente letras en minúsculas y sin extensiones en las URLs, un endpoint puede devolver los datos en el formato solicitado según la cabecera Accept de modo que la extensión es redundante o no es necesaria.

En este nivel de madurez las URLs suelen incluir verbos que es una mala práctica, como en los siguientes ejemplos.

1
2
3
/addMessage
/deleteMessage
/getMessage
rest-0.txt

Nivel 1, recursos

Los recursos son una parte fundamental del protocolo HTTP, cada recurso tiene su propia dirección web, endpoint o URL. Normalmente en una aplicación los modelos corresponden con su propio recurso junto a su propio  endpoint o URL.

En este nivel se aplican varias convenciones como las URLs no incluyen una / al final de la dirección, una / representa una relación jerárquica entre diferentes recursos, es posible usar singular o plural para los nombres según se prefiera pero de forma consistente.

Los endpoints en este nivel de madurez son de la siguiente forma.

1
2
/messsage

rest-1.txt

Nivel 2, verbos

Las operaciones que se realizan sobre los recursos son las operaciones de creación, obtención, actualización y eliminación o CRUD. Usando los diferentes verbos del protocolo HTTP es posible asignar a cada uno de ellos las diferentes operaciones básicas de manipulación de datos.

Si se quiere obtener un elemento concreto de un recurso se realiza una petición al recurso con el verbo GET indicando el identificativo del modelo, si se quieren obtener todos los elementos del recurso se realiza una petición con el verbo GET sin especificar ningún identificativo, si se quiere crear un nuevo elemento en el recurso se utilizar el verbo POST, si se quiere modificar el verbo PUT y si se quiere eliminar el verbo DELETE.

  • POST: verbo utiliza para realizar operaciones de creación sobre un recurso.
  • GET: verbo utiliza para obtener un elemento de la colección o varios elementos de la colección.
  • PUT: verbo utilizado para realizar operaciones de modificación.
  • DELETE: verbo utilizado para realizar operaciones de eliminación.

Las cabeceras que son parte del protocolo HTTP son metadatos utilizados con diferentes propósitos como indicar en qué formato se quieren los datos en la respuesta o añadir seguridad.

Los parámetros de las URLs son otra parte del protocolo HTTP que permiten proporcionar argumentos y datos en la propia URL después del símbolo ? en vez de como datos en el cuerpo de la petición. Los parámetros de las consultas son utilizados con diferentes propósitos como especificar los criterios de una búsqueda o propiedades de los datos que se desean como paginación, filtrado u ordenación.

Otra parte del protocolo HTTP con los códigos de estado, los códigos de estado HTTP son un número que indica el resultado de la operación. Estos son varios de los códigos de estado más comunes:

  • 200: la operación se ha procesado correctamente.
  • 201, CREATED: un nuevo recurso ha sido creado.
  • 204, NO CONTENT: el recurso ha sido eliminado, no se devuelven datos en el cuerpo de la respuesta.
  • 304, NOT MODIFIED: los datos retornados no han cambiado y provienen de una caché.
  • 400, BAD REQUEST: la respuesta es inválida y no puede ser procesada. La descripción del mensaje de error puede ser devuelta en lo datos retornados.
  • 401, UNAUTHORIZED: acceder o manipular el recurso requiere autenticación.
  • 403, FORBIDDEN: el servidor entiende la petición pero las credenciales proporcionadas no permiten el acceso.
  • 404, NOT FOUND: el recurso de la URL no existe.
  • 500, INTERNAL SERVER ERROR: se ha producido un error interno al procesar la petición por un fallo de programación. En la respuesta no se siempre se devuelve una descripción del error, sin embargo en las trazas del servidor debería haber información detallada del error.

Tanto para enviar datos como obtener datos el formato utilizado es JSON por ser un formato de texto plano y manipulable desde JavaScript en un navegador web.

Aunque hasta este nivel puede ser suficiente para implementar un servicio y proporcionar la funcionalidad, no es suficiente para considerarlo RESTful, es necesario el siguiente nivel de madurez con los controles hypermedia.

Nivel 3, controles hypermedia

Este nivel se divide en dos aspectos, negociación de contenido y descubrimiento de enlaces del recurso. Este es el nivel al que muchas implementaciones de servicios REST no implementan por mayor sencillez aún sin las ventajas que proporcionan los controles hypermedia o por los problemas de los controles hypermedia que si son ignorados ni utilizados no proporcionan ninguna de sus ventajas.

La negociación del contenido permite al cliente especificar el formato de los datos en los que quiere el resultado. Se solicita con la cabecera Accept en la petición. Por ejemplo, un cliente del servicio REST que desee los datos en formato JSON debe proporcionar una cabecera Accept: application/json y si los desea en formato XML una cabecera Accept: application/xml. En caso de enviar datos en el cuerpo de la petición el formato de los datos proporcionados se especifica con la cabecera Content-Type. En caso de que el servicio no soporte el tipo de datos proporcionado o no sea capaz de proporcionar en el formato solicitado devuelve el código de estado 415 que indica formato de tipo de datos no soportado.

La web es una colección de páginas web relacionadas a través de enlaces. HATEOAS es el principio que aplica enlaces en los datos de las entidades que permite navegar entre ellas y descubrir las acciones disponibles, un cliente de un servicio REST que implemente HATEOAS no necesita conocer las URLs para interaccionar con las diferentes acciones, estas son devueltas en los datos de la respuesta como metadatos.

Para obtener los enlaces que ofrece el recurso es necesario hacer una petición y obtener datos, esto es un problema ya que si el cliente ha de conocer de antemano los enlaces o hacer una petición para obtenerlos se anulan parte de las ventajas de HATEOAS, el cliente ha de harcodearlos en su código. Esta acción index permite obtener todos los enlaces que se ofrece en el recurso que el cliente puede utilizar.

1
2
#!/usr/bin/env bash
curl -v http://localhost:8080/message/index
curl-get-index.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
    "_links": {
      "self": {
        "href": "http://localhost:8080/message/index"
      },
      "getAll": {
        "href": "http://localhost:8080/message"
      },
      "getById": {
        "href": "http://localhost:8080/message/{id}",
        "templated": true
      },
      "add": {
        "href": "http://localhost:8080/message"
      },
      "deleteById": {
        "href": "http://localhost:8080/message/{id}",
        "templated": true
      }
    }
  }
curl-get-index.json

Al realizar la siguiente llamada al servicio del ejemplo cuando se devuelve una entidad Message el JSON de sus datos incluye una propiedad _links con los enlaces de sus acciones, en este caso realizar la operación de eliminar. La propiedad links es un array de enlaces que tienen la URL y un nombre o identificativo asociado.

1
2
#!/usr/bin/env bash
curl -v http://localhost:8080/message/1
curl-get.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "id": 1,
  "text": "Hello World!",
  "_links": {
    "self": {
      "href": "http://localhost:8080/message/1"
    },
    "deleteById": {
      "href": "http://localhost:8080/message/1"
    }
  }
}
curl-get.json

Con HATEOAS en vez de que los clientes construyen las URLs de los recursos para hacer peticiones las obtienen de los datos de la respuesta, al mismo tiempo en la respuesta se especifica las acciones posibles de modo que el cliente no necesita implementar lógica para determinar si una operación es posible. La aplicación tampoco necesita construir URLs con interpolación de cadenas para incluir el identificativo de una entidad, el enlace completo es devuelto en los datos. Esto permite a los clientes no tener que implementarlo reduciendo el riesgo de que la lógica de operaciones posibles del servidor y el cliente quede desincronizadas.

HAL es un formato de tipos de datos que permite codificar no sólo datos sino también controles hypermedia, indicando a los consumidores otras partes de la API a las que llamar. El enlace self indica al propio recurso, el enlace root indica el recurso de la colección, los enlaces add y delete indican dos operaciones posibles.

Ventajas y problemas de HATEOAS

Al cambiar la estructura de las URLs se rompe la compatibilidad de la API con versiones anteriores, uno de los beneficios de HATEOAS es que si la estructura de la URL de la API puede cambiar sin afectar a los clientes al describir estos las URLs de forma dinámica.

Los enlaces devueltos proporcionan al cliente la lista de operaciones que es posible llamar según el estado de la aplicación o la entidad. Esto es útil para los desarrolladores de los clientes dado que no han de duplicar lógica de cuando es posible realizar una operación. En los casos de varias operaciones encadenadas realizadas en varios pasos con HATEOAS la API guía a los clientes hacia el siguiente paso en el flujo proporcionando únicamente los enlaces que son relevantes según el estado de la aplicación.

La documentación de la API sigue siendo requerida para describir la semántica de cada enlace junto con información como la estructura de los tipos y tipo de contenido.

En la parte negativa está que HATEOAS añade complejidad a la API, que afecta tanto al desarrollador de la API como al consumidor de la misma. Hay que realizar un trabajo adicional para añadir los enlaces apropiados en cada respuesta según el estado de la entidad. Esto provoca que la API sea más compleja de construir que una API que no implementa HATEOAS.

Los clientes de la API también tienen complejidad añadida para entender la semántica de cada enlace además de tener y procesar cada respuesta para obtener los enlaces. Los beneficios pueden compensar esta complejidad añadida pero hay que tenerla en cuenta.

Si la API es pública seguramente algún cliente la use de forma que la usa incorrectamente sin usar el hypermedia, haciendo a HATEOAS inútil.

Ejemplo de recurso REST con HATEOAS y ejemplo de código

En el artículo Cómo documentar una API REST con Swagger implementada con Spring Boot incluía como ejemplo un servicio REST que únicamente implementa hasta el nivel de madurez 2 de REST, esta es la revisión del servicio para implementar hasta el nivel 3 incluyendo hypermedia con HATEOAS y HAL.

Spring HATEOAS proporciona métodos y clases para incluir los enlaces de hypermedia de las entidades que se devuelven como resultado en el servicio. La clase RepresentationModel es una clase base que incluye métodos para añadir los controles hpermedia, la clase EntityModel es utilizada cuando el resultado es para una única entidad, CollectionModel cuando el resultado es una colección de entidades y PagedModel cuando el resultado es paginado.

Este es la implementación de servicio REST de ejemplo que trata mensajes, permite obtener una lista de mensajes, crear nuevos y eliminar además de una acción para descubrir todos los enlaces del recurso. Para crear los enlaces de hypermedia de HAL que se devuelven en el JSON como respuesta del servicio se delegan en una clase RepresentationModelAssembler.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package io.github.picodotdev.blogbitix.springresthateoas;

...

@RestController
@ExposesResourceFor(Message.class)
public class MessageController implements MessageApi {

    private MessageModelAssembler assembler;
    private Map<Long, Message> messages;

    public MessageController(MessageModelAssembler assembler) {
        this.assembler = assembler;

        this.messages = new HashMap<>();
        this.messages.put(1l, new Message(1l, "Hello World!"));
        this.messages.put(2l, new Message(2l, "Welcome to Blog Bitix!"));
    }

    @Override
    public ResponseEntity<CollectionModel<EntityModel<Message>>> index() {
        try {
            Collection<Link> links = List.of(
                Link.of(linkTo(MessageController.class).toUriComponentsBuilder().path(MessageApi.class.getMethod("index").getAnnotation(GetMapping.class).value()[0]).build().toUriString()).withSelfRel(),
                Link.of(linkTo(MessageController.class).toUriComponentsBuilder().path(MessageApi.class.getMethod("getAll").getAnnotation(GetMapping.class).value()[0]).build().toUriString()).withRel("getAll"),
                Link.of(linkTo(MessageController.class).toUriComponentsBuilder().path(MessageApi.class.getMethod("getById", Long.class).getAnnotation(GetMapping.class).value()[0]).build().toUriString()).withRel("getById"),
                Link.of(linkTo(MessageController.class).toUriComponentsBuilder().path(MessageApi.class.getMethod("add", Message.class).getAnnotation(PostMapping.class).value()[0]).build().toUriString()).withRel("add"),
                Link.of(linkTo(MessageController.class).toUriComponentsBuilder().path(MessageApi.class.getMethod("deleteById", Long.class).getAnnotation(DeleteMapping.class).value()[0]).build().toUriString()).withRel("deleteById")
            );
            return ResponseEntity.ok(CollectionModel.empty(links));
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    @Override
    public ResponseEntity<CollectionModel<EntityModel<Message>>> getAll() {
        List<Message> entities = messages.entrySet().stream().map(e -> e.getValue()).collect(Collectors.toList());
        return ResponseEntity.ok(assembler.toCollectionModel(entities));
    }

    @Override
    public ResponseEntity<EntityModel<Message>> getById(Long id) {
        if (!exists(id)) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Message not found");
        }
        return ResponseEntity.ok(assembler.toModel(messages.get(id)));
    }

    @Override
    public ResponseEntity<Void> add(Message message) {
        if (exists(message.getId())) {
            throw new ResponseStatusException(HttpStatus.CONFLICT, "Already exists");
        }
        if (message.isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid data");
        }
        messages.put(message.getId(), message);
        return ResponseEntity.ok().build();
    }

    @Override
    public ResponseEntity<Void> deleteById(Long id) {
        if (!exists(id)) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Message not found");
        }
        messages.remove(id);
        return ResponseEntity.ok().build();
    }

    private boolean exists(Long id) {
        return messages.containsKey(id);
    }
}
MessageController.java

Estos son dos comandos de curl para realizar una petición y obtener datos de una colección de entidades.

1
2
#!/usr/bin/env bash
curl -v http://localhost:8080/message
curl-get-all.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
  "_embedded": {
    "messages": [
      {
        "id": 1,
        "text": "Hello World!",
        "_links": {
          "self": {
            "href": "http://localhost:8080/message/1"
          },
          "deleteById": {
            "href": "http://localhost:8080/message/1"
          }
        }
      },
      {
        "id": 2,
        "text": "Welcome to Blog Bitix!",
        "_links": {
          "self": {
            "href": "http://localhost:8080/message/2"
          },
          "deleteById": {
            "href": "http://localhost:8080/message/2"
          }
        }
      }
    ]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/message/"
    },
    "add": {
      "href": "http://localhost:8080/message"
    }
  }
}
curl-get-all.json

Los enlaces de hypermedia siguiendo la especificación HAL incluidos en el JSON es posible incluirlos directamente con la clase EntityModel, sin embargo, si la misma entidad es devuelta por varios endpoints para no duplicar código es posible delegar la creación de la representación del modelo en una clase dedicada a esta tarea.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.picodotdev.blogbitix.springresthateoas;

...

@Component
class MessageModelAssembler implements RepresentationModelAssembler<Message, EntityModel<Message>> {

    @Override
    public EntityModel<Message> toModel(Message message) {
        return EntityModel.of(message,
                linkTo(methodOn(MessageApi.class).getById(message.getId())).withSelfRel(),
                linkTo(methodOn(MessageApi.class).deleteById(message.getId())).withRel("deleteById"));
    }

    @Override
    public CollectionModel<EntityModel<Message>> toCollectionModel(Iterable<? extends Message> entities) {
        CollectionModel<EntityModel<Message>> model = RepresentationModelAssembler.super.toCollectionModel(entities);
        model.add(linkTo(methodOn(MessageController.class).getAll()).withSelfRel());
        model.add(Link.of(linkTo(MessageController.class).toUriComponentsBuilder().build().toUriString()).withRel("add"));
        return model;
    }
}
MessageModelAssembler.java

En caso de que la API esté detrás de un proxy los enlaces devueltos por las entidades han de ser adaptados, Spring proporciona un filtro que aplicado a la aplicación permite especificar con cabeceras los datos de las URLs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.springresthateoas;

...

@SpringBootApplication
public class Main {

    @Bean
    public ForwardedHeaderFilter forwardedHeaderFilter() {
        return new ForwardedHeaderFilter();
    }

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}
Main.java
1
2
#!/usr/bin/env bash
curl -v -H "X-Forwarded-Host: picodotdev.github.io" http://localhost:8080/message/index
curl-get-index-proxy.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
    "_links": {
      "self": {
        "href": "http://picodotdev.github.io/message/index"
      },
      "getAll": {
        "href": "http://picodotdev.github.io/message"
      },
      "getById": {
        "href": "http://picodotdev.github.io/message/{id}",
        "templated": true
      },
      "add": {
        "href": "http://picodotdev.github.io/message"
      },
      "deleteById": {
        "href": "http://picodotdev.github.io/message/{id}",
        "templated": true
      }
    }
  }
curl-get-index-proxy.json

Para usar las clases que ofrecen el soporte para HATEOAS es necesario incluir la dependencia de Spring Boot en el archivo de construcción de Gradle.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
plugins {
	id 'java'
	id 'application' 
	id 'org.springframework.boot' version '2.5.2'
	id 'com.github.johnrengelman.processes' version '0.5.0'
	id 'org.springdoc.openapi-gradle-plugin' version '1.3.2'
}

application {
	group = 'io.github.picodotdev.blogbitix.springresthateoas'
	version = '0.0.1-SNAPSHOT'
	sourceCompatibility = '11'
	mainClass = 'io.github.picodotdev.blogbitix.springresthateoas.Main'
}

repositories {
	mavenCentral()
}

dependencies {
	implementation platform('org.springframework.boot:spring-boot-dependencies:2.5.2')

	implementation 'org.springframework.boot:spring-boot-starter'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springdoc:springdoc-openapi-webmvc-core:1.5.9'
	implementation 'org.springframework.boot:spring-boot-starter-hateoas'
	implementation 'org.springdoc:springdoc-openapi-ui:1.5.9'
	implementation 'org.springdoc:springdoc-openapi-hateoas:1.5.9'
}
build.gradle

Documentación con Swagger

Swagger permite documentar un servicio REST, también incluye soporte para documentar un servicio que cumpla con el principio de hypermedia HATEOAS. Swagger proporciona varias anotaciones que se incluyen en la interfaz del servicio, al procesarlas genera un esquema de la interfaz del servicio con OpenAPI a partir del cual genera la documentación que incluye los endpoints y argumentos, verbos, códigos de respuesta y datos de los modelos. Swagger también permite hacer llamadas a los servicios y obtener el comando curl para hacer la petición desde la línea de comandos.

La definición de la interfaz del servicio además de las anotaciones de Spring para el servicio REST incluye las anotaciones de Swagger para generar el esquema del servicio en http://localhost:8080/v3/api-docs y generar la documentación en formato HTML accesible en la dirección http://localhost:8080/swagger-ui.html.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package io.github.picodotdev.blogbitix.springresthateoas;

...

@Tag(name = "message", description = "the message API")
@RequestMapping(value = "/message", produces = { "application/hal+json" })
public interface MessageApi {

	@Operation(summary = "Get resource links", description = "Returns resource links", responses = {
			@ApiResponse(responseCode = "200", description = "Successful operation",
				links = {
					@Link(name = "self", operationId = "self"), 
					@Link(name = "getAll", operationId = "getAll"), 
					@Link(name = "getById", operationId = "getById"), 
					@Link(name = "add", operationId = "add"), 
					@Link(name = "deleteById", operationId = "deleteById")
			})
	})
	@GetMapping(value = "/index")
	ResponseEntity<CollectionModel<EntityModel<Message>>> index();

	@Operation(summary = "Get all messages", description = "Returns all messages", responses = {
		@ApiResponse(responseCode = "200", description = "Successful operation",
				links = { @Link(name = "self", operationId = "self"), @Link(name = "add", operationId = "add") })
	})
	@GetMapping(value = "")
	ResponseEntity<CollectionModel<EntityModel<Message>>> getAll();

	@Operation(summary = "Get a message by id", description = "Return a message", responses = {
		@ApiResponse(responseCode = "200", description = "Successful operation",
				links = { @Link(name = "self", operationId = "self"), @Link(name = "deleteById", operationId = "deleteById") }),
		@ApiResponse(responseCode = "400", description = "Invalid id supplied"),
		@ApiResponse(responseCode = "404", description = "Message not found")
	})
	@GetMapping(value = "/{id}")
	ResponseEntity<EntityModel<Message>> getById(@Parameter(description = "Id of message to return", required = true) @PathVariable("id") Long id);

	@Operation(summary = "Adds a message", description = "Add a message")
	@ApiResponses(value = {
			@ApiResponse(responseCode = "200", description = "Successful operation"),
			@ApiResponse(responseCode = "400", description = "Invalid data"),
			@ApiResponse(responseCode = "409", description = "Already exists") })
	@PostMapping(value = "")
	ResponseEntity<Void> add(@Parameter(description = "Message to add", required = true) @RequestBody Message message);

	@Operation(summary = "Deletes a message by id", description = "Delete a message")
	@ApiResponses(value = {
			@ApiResponse(responseCode = "200", description = "Successful operation"),
			@ApiResponse(responseCode = "400", description = "Invalid id supplied"),
			@ApiResponse(responseCode = "404", description = "Message not found") })
	@DeleteMapping(value = "/{id}")
	ResponseEntity<Void> deleteById(@Parameter(description = "Id of message to delete", required = true) @PathVariable("id") Long id);
}
MessageApi.java

Esta es la documentación de Swagger.

Documentación de servicio REST con Swagger UI

Documentación de servicio REST con Swagger UI

Documentación de servicio REST con Swagger UI


Comparte el artículo: