El presente post se centra en el uso de Spring REST Docs, un componente de la suite de Spring, que tiene como objetivo facilitar la generación de documentación de servicios web tipo REST.
Esta parte de Spring se integra con Spring MVC Test para que, desde nuestros tests de unidad o de integración, podamos, a la vez que se testean los servicios web, generar una documentación en formato HTML completa, consistente y moderna con la que describir el uso de los mismos.
Qué cocinamos
El uso de servicios web es una buena manera de compartir funcionalidades entre aplicaciones, crear interacciones entre ellas e integrar diferentes usos. Es una manera sencilla y poco invasiva de compartir información y realizar peticiones de ejecución de todo tipo de servicios. Además los servicios web tipo REST son sencillos de implementar, siguen estándares bien definidos y son compatibles entre multitud de aplicaciones y tipos de tecnologías.
Uno de los principales problemas a la hora de compartir o publicar un conjunto de servicios web tipo REST para que puedan ser usados por terceros es documentarlos debidamente para que esas terceras partes conozcan como usarlos, que invocar, que entradas de datos deben usar o que salidas deben esperar. Si otros tipos de servicios similares, como los servicios web tipo SOAP tienen documentos más o menos formales y estandarizados como los WSDL, que pueden ser usados por aplicaciones de terceros para crear clientes que invoquen a nuestros servicios, practicamente de forma automática, en el caso de los servicios tipo REST, su simplicidad y fundamentos basados en tecnologías más genéricas impide o dificulta la presencia de ese tipo de formalismos (aunque existen intentos para definir formalmente este tipo de servicios de forma similar usando también XML).
Al final, lo que parece ser una ventaja del mundo de los WS tipo REST, en relación a su simplicidad, puede verse como un problema a la hora de transmitir su uso a terceros. Por este motivo, se recomiendan, además de usar una serie de estándares o buenas prácticas a la hora de desarrollar los servicios, unas buenas prácticas asimismo a la hora de documentarlos. En este sentido, Spring REST Docs es una herramienta de gran utilidad ya que permite, al mismo tiempo que se testean esos mismos servicios web, generar una documentación de calidad. Además si se siguen esas recomendaciones y buenas prácticas, tendremos una documentación útil y sencilla de transmitir a terceros para que puedan hacer uso de nuestros servicios.
Lo que hace Spring REST Docs es generar, en función de la implementación que establezcamos en su ejecución, que puede realizarse integramente dentros de los tests de unidad o integración de nuestros servicios, una documentación en texto plano que posteriormente se transformará en HTML (u otros formatos) usando un programa externo llamado Asciidoctor. Para facilitar esto, existen plug-ins para Maven que permiten ejecutar los tests y generar la documentación de forma simultánea, de tal manera que incluso si la generación de documentación falla, el test también fallará, asegurándonos así que existe una referencia consistente para ese servicio.
Ingredientes
Vamos a usar una aplicación web de prueba que implementa una serie de servicios web tipo REST para pobar los Spring REST Docs, testeando y documentando dichos servicios de forma simultanea. Para ello necesitaremos:
- Spring Boot
- Spring MVC
- Spring Data
- Spring REST Docs
- HSQLDB
- Log4j
Para más información acerca de las dependencias usadas (versiones, paquetes específicos etc.) se puede echar un vistazo al pom.xml que se incluye con el proyecto.
Paso a paso
Lo de siempre, una vez creado nuestro proyecto, se maveniza. Este es el aspecto del pom.xml que incluye las dependencias a las librerias de los componentes anteriores:
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>SpringRestDocs</groupId> <artifactId>SpringRestDocs</artifactId> <version>1.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.1.RELEASE</version> </parent> <dependencies> <!-- Add typical dependencies for a web application --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Add typical dependencies for a spring JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- REST docs and MockMVC dependencies --> <dependency> <groupId>org.springframework.restdocs</groupId> <artifactId>spring-restdocs-mockmvc</artifactId> <version>1.0.1.RELEASE</version><!--$NO-MVN-MAN-VER$--> </dependency> <!-- Add typical dependencies for HSQLDB --> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> </dependency> <!-- Use Log4j instead of default logging --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> </dependency> </dependencies> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <!-- Package as an executable jar --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>net.luisalbertogh.springrestdocs.SpringRestDocs</mainClass> </configuration> </plugin> <!-- Surfire plugin - Plugin to execute Tests --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <include>**/*Test.java</include> </includes> </configuration> </plugin> <!-- Asciidoctor plugin - Plugin for REST documentation --> <plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <version>1.5.2</version> <executions> <execution> <id>generate-docs</id> <!-- Configured to include documentation within the executable JAR file --> <phase>prepare-package</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <sourceDirectory>src/docs/asciidoc</sourceDirectory> <outputDirectory>${project.build.directory}/generated-docs/${project.version}</outputDirectory> <backend>html</backend> <doctype>book</doctype> <attributes> <snippets>${project.build.directory}/generated-snippets</snippets> </attributes> </configuration> </execution> </executions> </plugin> <!-- Resources plugin - Copy generated REST docs into output location to be included within the release --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>2.7</version><!--$NO-MVN-MAN-VER$--> <executions> <execution> <id>copy-resources</id> <phase>prepare-package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory> ${project.build.outputDirectory}/static/docs/${project.version} </outputDirectory> <resources> <resource> <directory> ${project.build.directory}/generated-docs/${project.version} </directory> </resource> </resources> </configuration> </execution> </executions> </plugin> </plugins> </build> <!-- Add Spring repositories --> <!-- (you don't need this if you are using a .RELEASE version) --> <repositories> <repository> <id>spring-snapshots</id> <url>http://repo.spring.io/snapshot</url> <snapshots><enabled>true</enabled></snapshots> </repository> <repository> <id>spring-milestones</id> <url>http://repo.spring.io/milestone</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <url>http://repo.spring.io/snapshot</url> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <url>http://repo.spring.io/milestone</url> </pluginRepository> </pluginRepositories> </project> |
En este caso, como en otros expuestos en este blog, para facilitar la implementación y ejecución de la prueba, se usa Spring Boot. Para implementar una pequeña aplicacion de prueba que desarrolle una serie de servicios web con acceso a datos, se usa la integración de Spring MVC y Spring Data con Spring Boot, de esta manera podremos implementar una sencilla aplicacion de forma rápida y simple. Se usa HSQLDB como base de datos relacional embebida, que nos permitirá ejecutar las operaciones CRUD clásicas de persistencia de datos.
El resto del pom incluye, además, los plugin que se usarán para, desde Maven, ejecutar los tests y la generación de la documentación (con Asciidoctor).
1. Desarrollar la aplicación de prueba que implementa los servicios web
Vamos a desarrollar una aplicación muy sencilla que nos permita realizar las clásicas operaciones CRUD sobre una entidad concreta y poder acceder a esas operaciones mediante servicios web tipo REST. Nuestra aplicación va a realizar lo siguiente:
- Insertar o actualizar un superheroe
- Borrar un superheroe
- Mostrar un superheroe
- Mostrar la lista de superheroes
Para ello desarrollamos una aplicación por capas más o menos clásica que implemente los diferentes niveles de acceso a los datos:
- Un POJO que representa la entidad “Superheroe”
- Un DAO que por simplicidad solo implementará las operaciones CRUD anteriores
- Un Controlador que implementará los servicios web
En este caso, como no hay lógica de negocio, y por simplicidad y porque el post está centrado en la generación de la documentación de los servicios web y no en el desarrollo de la aplicación en sí, nos saltaremos la capa de negocio o de servicios que debería tener este tipo de aplicaciones por capas.
2. POJO y DAO
El POJO representa la entidad que usaremos para la prueba, que como se ha mencionado antes, modela los datos de un superheroe cualquiera, con sus superpoderes, debilidades y demás características. Además de usarlo para la capa de persistencia, también usaremos la misma clase como DTO para transferir datos entre capas de negocio.
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
/** * @author lagarcia * */ @Entity public final class Superhero { /** The name */ @NotNull private String name; /** The super power */ @NotNull private String power; /** The evil actitude */ @NotNull private Boolean evil; /** The creation date and time */ private LocalDateTime createdDate; /** The creator name */ private String creator; /** The superhero weakness */ @NotNull private String weakness; /** * Default constructor. */ public Superhero() { /* Empty */ } /** * Constructor with args. * * @param nameArg * @param powerArg * @param evilArg */ public Superhero(String nameArg, String powerArg, Boolean evilArg) { name = nameArg; power = powerArg; evil = evilArg; } /** * @return the name */ @Id public String getName() { return name; } /** * @param nameArg * the name to set */ public void setName(String nameArg) { name = nameArg; } /** * @return the power */ public String getPower() { return power; } /** * @param powerArg * the power to set */ public void setPower(String powerArg) { power = powerArg; } /** * @return the isEvil */ public Boolean isEvil() { return evil; } /** * @param evilArg * the isEvil to set */ public void setEvil(Boolean evilArg) { evil = evilArg; } /** * @return the createdDate */ @CreatedDate public LocalDateTime getCreatedDate() { return createdDate; } /** * @param createdDateArg * the createdDate to set */ public void setCreatedDate(LocalDateTime createdDateArg) { createdDate = createdDateArg; } /** * @return the creator */ @CreatedBy public String getCreator() { return creator; } /** * @param creatorArg * the creator to set */ public void setCreator(String creatorArg) { creator = creatorArg; } /** * @return the weakness */ public String getWeakness() { return weakness; } /** * @param weaknessArg * the weakness to set */ public void setWeakness(String weaknessArg) { weakness = weaknessArg; } @Override public String toString() { /* Evil */ String evilStr = "good"; if (evil) { evilStr = "evil"; } /* Creation date */ String dateStr = ""; if (createdDate != null) { dateStr = createdDate.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); } return "[" + dateStr + "][" + creator + "] My name is <b>" + name + "</b> and my super power is <b>" + power + "</b>. Do not panic, I am " + evilStr + ". I am afraid to " + weakness; } } |
El DAO, por motivos de simplicidad, es una interfaz que se exitende del CrudRepository de Spring Data. De esta manera se implementan automaticamente las operaciones CRUD clásicas necesarias para nuestro ejemplo. Usaremos el nombre del superheroe como clave primaria de la entidad.
1 2 3 4 5 6 |
/** * @author lagarcia * */ public interface SuperheroDAO extends CrudRepository<Superhero, String> { } |
3. Servicios web
Para implementar los servicios web en este caso usamos Spring MVC. Vamos a seguir en la medida de lo posible las recomendaciones y buenas prácticas en la implementación de servicios de este tipo. Estas recomendaciones afectan a varias características de los servicios, a saber:
- URL del servicio
- Método HTTP
- Códigos HTTP de respuesta
A parte de esto, en nuestro caso, como vamos a invocar los servicios desde una página web, usaremos JSON para enviar y recibir los datos desde los mismos (se podría usar XML en su lugar).
Los servicios que vamos a implementar y sus características son los siguientes:
Inserta o modifica un superheroe
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * Add a new superhero. * * @param sh * - The new superhero * @return The response entity */ @RequestMapping(value = "/superhero", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Void> saveSuperhero(@RequestBody Superhero sh) { logger.info("Save a superhero"); if (sh != null) { sh.setCreator("ADMIN"); sh.setCreatedDate(LocalDateTime.now()); this.getHeroDAO().save(sh); return new ResponseEntity<Void>(HttpStatus.CREATED); } return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST); } |
Inserta un nuevo superheroe, si éste no existe, o lo modifica con los datos enviados, si existe en función de su clave primaria (nombre).
Obtiene los datos de un superheroe concreto en función de su nombre
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * Get a superhero. * * @param name * - Superhero name * * @return A superhero PO */ @RequestMapping(value = "/superhero/{name}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Superhero> getSuperhero( @PathVariable("name") String name) { logger.info("Get " + name + " superhero"); Superhero sh = this.getHeroDAO().findOne(name); if (sh != null) { return new ResponseEntity<Superhero>(sh, HttpStatus.FOUND); } return new ResponseEntity<Superhero>(HttpStatus.NOT_FOUND); } |
Borra un superheroe en función de su nombre
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * Delete a superhero. * * @param name * - Superhero name * @return The response entity * */ @RequestMapping(value = "/superhero/{name}/delete", method = RequestMethod.DELETE) public ResponseEntity<Void> deleteSuperhero( @PathVariable("name") String name) { logger.info("Delete a superhero"); if (name != null) { this.getHeroDAO().delete(name); return new ResponseEntity<Void>(HttpStatus.OK); } return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST); } |
Muestra la lista completa de superheroes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/** * Find all superheros. * * @return The superheros list. */ @RequestMapping(value = "/superhero/find", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<List<Superhero>> findSuperhero() { List<Superhero> shList = (List<Superhero>) this.getHeroDAO().findAll(); if (shList != null && !shList.isEmpty()) { return new ResponseEntity<List<Superhero>>(shList, HttpStatus.FOUND); } return new ResponseEntity<List<Superhero>>(HttpStatus.NOT_FOUND); } |
4. Tests de servicios y generación de documentación
El último paso es implementar los tests de integración de los servicios que además de testear la funcionalidad completa de los mismos, nos permitirá generar la documentación asociada según los criterios que indiquemos en dicha implementación.
Para testear los servicios web usamos MockMVC de Spring, que implementa un cliente HTTP que nos permite invocar dichos servicios y validar los códigos de respuesta HTTP recibidos así como el contenido de la respuesta.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/* Test & document - insert or update service */ this.mockMvc.perform( post("/superhero").contentType(MediaType.APPLICATION_JSON) .content(this.objectMapper.writeValueAsString(sh))) .andExpect(status().isCreated()); ... /* Test & document - get a superhero service */ this.mockMvc.perform( get("/superhero/Spiderman").accept( MediaType.APPLICATION_JSON)).andExpect( status().isFound()); |
Al mismo tiempo inicializamos la generación de la documentación usando la API de Spring REST Docs y especificamos los campos obligatorios de entrada de datos o los campos de los datos de retorno para los servicios relacionados.
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 |
/** REST docs configuration */ @Rule public final RestDocumentation restDocumentation = new RestDocumentation( "target/generated-snippets"); /** REST document handler */ private RestDocumentationResultHandler document; .... /* Document constraints and request fields */ ConstrainedFields fields = new ConstrainedFields(Superhero.class); this.document .snippets(requestFields( fields.withPath("name").description( "The superhero name"), fields.withPath("power").description( "The superhero power"), fields.withPath("creator").description( "The creator name"), fields.withPath("createdDate").description( "The creation date"), fields.withPath("evil").description( "Is the superhero evil?"), fields.withPath("weakness").description( "The superhero weakness"))); |
5. Asciidoctor
Definimos en un fichero de texto plano el contenido y el formato de la página HTML que se generará como índice de nuestra documentación. Para ello usamos la sintaxis de Asciidoctor. Además seguimos recomendaciones de buenas prácticas relacionadas con la redacción de este tipo de documentación, a saber:
- Indicar en un apartado diferente los distintos tipos de códigos de respuesta HTTP y su significado
- Indicar en un apartado diferente los tipos de métodos HTTP usados y su significado
- Indicar el tipo de respuesta recibida cuando se generar un error
- Para cada servicio web documentado, indicar un ejemplo de invocación de la URL usando curl, ejemplos de petición y respuesta HTTP y si fuera necesario, el significado de los campos de entrada y salida de los datos usados. Esta información es la que genera de forma automática Spring REST Docs en cualquier caso, salvo la descripción de los campos de entrada y/o salida, que tendríamos que especificar en cada test si queremos que se genere
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
= Spring REST Docs API luisalbertogh <luisalbertogh@hotmail.com> :doctype: book :toc: :sectanchors: :sectlinks: :toclevels: 4 :source-highlighter: highlightjs [[overview]] = Overview [[overview-http-verbs]] == HTTP verbs RESTful SpringRestDocs tries to adhere as closely as possible to standard HTTP and REST conventions in its use of HTTP verbs. |=== | Verb | Usage | `GET` | Used to retrieve a resource | `POST` | Used to create (add or update) a new resource | `DELETE` | Used to delete an existing resource |=== [[overview-http-status-codes]] == HTTP status codes RESTful SpringRestDocs tries to adhere as closely as possible to standard HTTP and REST conventions in its use of HTTP status codes. |=== | Status code | Usage | `200 OK` | The request completed successfully | `201 Created` | A new resource has been saved (created or updated) successfully. // The resource's URI is available from the response's `Location` header | `302 Found` | The searched resource was successfully found | `400 Bad Request` | The request was malformed. The response body will include an error providing further information | `404 Not Found` | The requested resource did not exist |=== [[overview-errors]] == Errors Whenever an error response (status code >= 400) is returned, the body will contain a JSON object that describes the problem. The error object has the following structure: For example, a request that attempts to save a malformed superhero will produce a `400 Bad Request` response: [[overview-hypermedia]] == Hypermedia RESTful Notes uses hypermedia and resources include links to other resources in their responses. Responses are in http://stateless.co/hal_specification.html[Hypertext Application Language (HAL)] format. Links can be found benath the `_links` key. Users of the API should not created URIs themselves, instead they should use the above-described links to navigate from resource to resource. [[resources]] = Resources [[resources-save-superhero]] == Save (add or update) a superhero === Example include::{snippets}/test-save-superhero/curl-request.adoc[] === HTTP request include::{snippets}/test-save-superhero/http-request.adoc[] === HTTP response include::{snippets}/test-save-superhero/http-response.adoc[] === Request fields include::{snippets}/test-save-superhero/request-fields.adoc[] [[resources-get-superhero]] == Get a superhero === Example include::{snippets}/test-get-superhero/curl-request.adoc[] === HTTP request include::{snippets}/test-get-superhero/http-request.adoc[] === HTTP response include::{snippets}/test-get-superhero/http-response.adoc[] === Response fields include::{snippets}/test-get-superhero/response-fields.adoc[] [[resources-find-superheros]] == Find superheros === Example include::{snippets}/test-find-superheros/curl-request.adoc[] === HTTP request include::{snippets}/test-find-superheros/http-request.adoc[] === HTTP response include::{snippets}/test-find-superheros/http-response.adoc[] [[resources-delete-superhero]] == Delete a superhero === Example include::{snippets}/test-delete-superhero/curl-request.adoc[] === HTTP request include::{snippets}/test-delete-superhero/http-request.adoc[] === HTTP response include::{snippets}/test-delete-superhero/http-response.adoc[] |
La ruta al fichero se incluye en el plugin de Asciidoctor para Maven, en el pom.xml.
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 |
<!-- Asciidoctor plugin - Plugin for REST documentation --> <plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <version>1.5.2</version> <executions> <execution> <id>generate-docs</id> <!-- Configured to include documentation within the executable JAR file --> <phase>prepare-package</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <strong><sourceDirectory>src/docs/asciidoc</sourceDirectory></strong> <strong><outputDirectory>${project.build.directory}/generated-docs/${project.version}</outputDirectory></strong> <backend>html</backend> <doctype>book</doctype> <attributes> <snippets>${project.build.directory}/generated-snippets</snippets> </attributes> </configuration> </execution> </executions> </plugin> |
6. Ejecución de tests y generación de documentación
Configuramos los plugin de Maven en el pom.xml para que se ejecute la generación de la documentación al generar un nuevo “release” de la aplicación, es decir, cuando se invoque el goal “package” de Maven. Previamente se ejecutarán los tests de forma automática. Si falla el test o la generación de la documentación para un servicio dado, se indicará en la salida por consola y no se generará ni el release ni la documentación de ninguno de los servicios.
7. Visualización de la documentación generada
La documentación se genera en la ruta indicada en el plugin de Maven (ver arriba). Por un lado se generan los “snippets” que es la documentación que corresponde a cada servicio, y por otro una página principal o índice HTML que enlaza a esso “snippets” según hayamos indicado en el fichero de texto plano que define la página y que se describe en el punto 5.