Documentar un servicio REST, con Swagger 2 (SpringFox)

Hace bastante tiempo publiqué un post en el que explicaba como documentar un servicio REST montado con Spring, con Swagger. Desde entonces, la librería usada para Spring ha cambiado bastante (incluso de nombre y de dueños…). En este post veremos como hacerlo con la nueva librería, SpringFox.

Concepto

Desde hace tiempo la antigua librería ‘swagger-springmvc‘, que servia para integrar swagger fácilmente en un proyecto Spring, ha pasado a llamarse SpringFox, y ha cambiado un poco. En sus últimas versiones soporta Swagger 2.0. Ya hablé un poco sobre Swagger en el post que comentaba antes, pero tenéis más info sobre swagger y sobre SpringFox en estos links:
http://swagger.io/
https://github.com/swagger-api
http://springfox.github.io/springfox/
https://github.com/springfox/springfox

En este post voy a documentar un servicio REST Spring con Swagger 2.0, usando SpringFox. Para ello usare como base un proyecto SpringBoot con un servicio REST ‘Infos’ que ya use en otros posts anteriores:
https://anotherdayanotherbug.wordpress.com/2015/03/16/tests-de-integracion-para-un-servicio-rest-con-spring/
https://anotherdayanotherbug.wordpress.com/2015/05/25/tests-de-integracion-usando-rest-assured/

Entorno usado:
Java JDK 1.8
Maven 3.3.9
Git 2.6.3
IDE Intellij 15.0.2 Ultimate version

Pasos

1. Creamos nuestro proyecto SpringBoot como siempre , con la versión 1.3. Lo principal que necesitamos es el pom.xml de Maven, la clase ‘main’ de SpringBoot … y listo.

2. Implementamos nuestro sencillo servicio REST. Un controller, que usa un Service, él cual usa un Repository (mockeado este último). Nuestra entity y el controller son estos:

public class Info {

    private Long id;
    private String infoText;
    private LocalDateTime creationDateTime;

    // getters and setters
}

@RestController
@RequestMapping("/api/infos")
public class InfoController {

    @Autowired
    private InfoService infoService;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Info> getAllInfos() {
        return infoService.findAll();
    }

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Info getInfo(@PathVariable Long id) {
        return infoService.findOne(id);
    }

    @RequestMapping(method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Info> createInfo(@RequestBody Info info) {
        Info infoCreated = infoService.save(info);
        return new ResponseEntity<>(createHeadersWithLocation(infoCreated),
                HttpStatus.CREATED);
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.PUT, value = "{id}")
    public void updateInfo(@PathVariable Long id, @RequestBody Info info) {
        infoService.update(info.setId(id));
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.DELETE, value = "{id}")
    public void deleteInfo(@PathVariable Long id) {
        infoService.delete(id);
    }

    private HttpHeaders createHeadersWithLocation(Info info) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(
                ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(info.getId())
                        .toUri());
        return httpHeaders;
    }
}

3. Vamos a empezar a integrarle Swagger 2 con SpringFox. Primero añadimos las librerías necesarias a nuestro pom.xml:

...
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.3.0</version>
        </dependency>
...

La principal que necesitamos es ‘springfox-swagger2‘.
La de ui es más bien un ‘webjar’ con la ui de swagger de siempre. Lo bueno es que esta todo en el jar, y no tenemos que bajarnos toda la ui en una subcarpeta del proyecto, como hice en el post anterior.

4. Creamos una clase de configuración de Spring, con la anotación @EnableSwagger2 y con el bean básico que necesita Swagger 2 (Docket):

package com.edwise.pocs.swagger2.config;

// imports...

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket newsApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("api-infos")
                .apiInfo(apiInfo())
                .directModelSubstitute(LocalDateTime.class, Date.class)
                .select()
                .paths(regex("/api.*"))
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Infos REST api")
                .description("PoC of a REST api, Infos")
                .termsOfServiceUrl("http://en.wikipedia.org/wiki/Terms_of_service")
                .contact("edwise.null@gmail.com")
                .license("Apache License Version 2.0")
                .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
                .version("2.0")
                .build();
    }
}

Esto es similar a la anterior configuración con la librería swagger-springmvc, pero aquí en lugar de un SwaggerSpringMvcPlugin, tenemos un bean llamado Docket, pero es similar. Tiene alguna mejora interesante. Por ejemplo, aquí estamos diciéndole que, a la hora de mostrar los tipos de dato LocalDateTime en la documentación de la API salgan como String (si no, te sale todos los objetos que contiene LocalDateTime, es un guarreo…)

5. Ya solo falta documentar nuestro api REST. Para eso, documentaremos primero la entity ‘Info’ así:

package com.edwise.pocs.swagger2.entity;

// imports...

@ApiModel(value = "Info entity", description = "Complete data of a entity Info")
public class Info {

    @ApiModelProperty(value = "The id of the info", required = false)
    private Long id;

    @ApiModelProperty(value = "The text of the info", required = true)
    private String infoText;

    @ApiModelProperty(value = "The date of the info", required = true)
    private LocalDateTime creationDateTime;

    // getters and settters
}

Apenas hay cambios respecto a la versión anterior. Usamos las anotaciones @ApiModel y @ApiModelProperty para documentar nuestro pojo.

6. Y ahora documentamos nuestro controller:

package com.edwise.pocs.swagger2.controller;

// imports...

@RestController
@RequestMapping("/api/infos")
@Api(value = "infos", description = "Infos API", produces = "application/json")
public class InfoController {

    @Autowired
    private InfoService infoService;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Get Infos", notes = "Returns all infos")
    @ApiResponses({
            @ApiResponse(code = 200, message = "Exits one info at least")
    })
    public List<Info> getAllInfos() {
        return infoService.findAll();
    }

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Get one Info", notes = "Returns one info")
    @ApiResponses({
            @ApiResponse(code = 200, message = "Exists this info")
    })
    public Info getInfo(@ApiParam(defaultValue = "1", value = "The id of the info to return")
                        @PathVariable Long id) {
        return infoService.findOne(id);
    }

    @RequestMapping(method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Create Info", notes = "Create a info")
    @ApiResponses({
            @ApiResponse(code = 201, message = "Successful create of a info")
    })
    public ResponseEntity<Info> createInfo(@RequestBody Info info) {
        Info infoCreated = infoService.save(info);
        return new ResponseEntity<>(createHeadersWithLocation(infoCreated),
                HttpStatus.CREATED);
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.PUT, value = "{id}")
    @ApiOperation(value = "Update Info", notes = "Update a info")
    @ApiResponses({
            @ApiResponse(code = 204, message = "Successful update of a info")
    })
    public void updateInfo(@ApiParam(defaultValue = "1", value = "The id of the info to update")
                           @PathVariable Long id,
                           @RequestBody Info info) {
        infoService.update(info.setId(id));
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.DELETE, value = "{id}")
    @ApiOperation(value = "Delete Info", notes = "Delete a info")
    @ApiResponses({
            @ApiResponse(code = 204, message = "Successful delete of a info")
    })
    public void deleteInfo(@ApiParam(defaultValue = "1", value = "The id of the info to delete")
                           @PathVariable Long id) {
        infoService.delete(id);
    }

    private HttpHeaders createHeadersWithLocation(Info info) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(
                ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(info.getId())
                        .toUri());
        return httpHeaders;
    }
}

Tampoco cambia nada, seguimos usando las mismas anotaciones para los controlers: @Api, @ApiOperation, @ApiResponses, @ApiParam
En el anterior post sobre Swagger las explico un poco por encima, aunque creo que viendo el código es bastante sencillo entender como funcionan.

7. Arrancamos nuestro proyecto SpringBoot (mvn spring-boot:run). Si accedemos a http://localhost:8080/swagger-ui.html podemos ver la interfaz de Swagger y juguetear con nuestro API.
ScreenSwagger2

Si queremos acceder directamente a la info (en formato json) de Swagger, accedemos a http://localhost:8080/v2/api-docs?group=api-infos. El formato ha cambiado bastante respecto a la versión anterior, echadle un vistazo.

El código de este post está en mi github, proyecto springfox-swagger2-example.

Documentar un servicio REST, con Swagger

ACTUALIZACIÓN 21-12-2015: La librería ‘swagger-springmvc’ ha sido deprecada, ahora ha pasado a llamarse SpringFox. He escrito un nuevo post sobre como documentar un servicio REST con Swagger usando esa nueva librería SpringFox.

 

¡Ahora sí! Esta vez si que vamos a usar Swagger, para documentar un servicio REST implementado con Spring, y sobre Spring Boot. Para ello usaremos el servicio que ha preparamos en el post anterior.

Concepto:
Dado el auge de los servicios REST, se ha convertido en casi una obligación tener una buena documentación de esos mismos servicios, para que el cliente pueda fácilmente usarlo. Entre otras, Swagger parece que está destacando bastante últimamente, como librería para documentar fácilmente servicios REST en java.
Para más información de Swagger, aquí unos links interesantes:
http://swagger.io/
https://github.com/wordnik/swagger-spec
https://github.com/wordnik/swagger-ui
https://github.com/martypitt/swagger-springmvc

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.4
IDE Intellij 13.1.4 Community version

Pasos:

1. Como comenté antes, vamos a usar como base el proyecto que hicimos en el último post, en el que teníamos ya implementado un sencillo servicio REST en Spring, sobre un proyecto Spring Boot. Podéis bajaros el proyecto directamente de github.

2. Añadimos las dependencias maven necesarias a nuestro pom.xml, para poder usar swagger, en concreto swagger-springmvc, que es el que necesitamos para montarlo sobre Spring Boot.

    ...
       <dependency>
           <groupId>com.mangofactory</groupId>
           <artifactId>swagger-springmvc</artifactId>
           <version>0.8.8</version>
       </dependency>
    ...

Con esta dependencia es suficiente para toda la parte ‘back’ de swagger.

3. Para que Swagger funcione, necesitamos definir en nuestro contexto de Spring un bean ‘SwaggerSpringMvcPlugin‘. Para ello, podemos definirlo directamente, en nuestra clase ‘Application.java’ (que la tenemos marcada como @Configuration) o crear una nueva clase de configuración. Haremos esto último, para que todo este un poco más organizado:

package com.edwise.pocs.swagger.config;

import com.mangofactory.swagger.configuration.SpringSwaggerConfig;
import com.mangofactory.swagger.plugin.SwaggerSpringMvcPlugin;
import com.wordnik.swagger.model.ApiInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerSpringMvcConfig {

    private SpringSwaggerConfig springSwaggerConfig;

    @Autowired
    public void setSpringSwaggerConfig(SpringSwaggerConfig springSwaggerConfig) {
        this.springSwaggerConfig = springSwaggerConfig;
    }

    @Bean
    public SwaggerSpringMvcPlugin customImplementation() {
        return new SwaggerSpringMvcPlugin(this.springSwaggerConfig)
                .apiInfo(apiInfo())
                .includePatterns(".*/*.*");
    }

    private ApiInfo apiInfo() {
        return new ApiInfo(
                "Users API",
                "Your user database!",
                "http://userweb.userapi.com/Terms_of_service",
                "userapi.manager@gmail.com",
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0.html"
        );
    }
}

Principalmente lo que hacemos en esta clase de configuración es cargar el bean SwaggerSpringMvcPlugin, en el método ‘customImplementation’, añadiéndole cierta información básica de configuración:
– Con la clase ‘ApiInfo‘ le metemos varios textos generales para nuestra api.
– con el método ‘includePatterns‘ filtramos los servicios que queremos que muestre nuestra documentación Swagger (en este caso mostramos todos).

4. Añadimos a nuestra clase base para Spring Boot ‘Application.java’ la anotación @EnableSwagger. Tambien podriamos añadirla en la clase creada antes. El único requisito es que la clase sea de configuración (@Configuration).

5. Arrancamos nuestra aplicación Spring Boot como siempre (mvn spring-boot:run o desde la tool window de maven en Intellij). Si accedemos a la url http://localhost:8080/api-docs nos devolverá un json con cierta información de nuestros servicios REST (entre otras cosas los datos que le pasamos en ApiInfo en nuestra clase de configuración). Ya tenemos swagger funcionando realmente, pero ahora faltaría documentar un poco nuestro REST, y poder verlo en alguna web un poco en condiciones…

6. Vamos a empezar por bajarnos la interfaz web para swagger. Para ello podemos, o añadir la dependencia maven, o bajarnos toda la interfaz y copiarla en nuestro proyecto. Dado que necesitamos modificarle alguna cosa (ahora lo veremos), lo mejor es lo segundo. Para ello, nos descargamos el repositorio git de aquí y copiamos todo el contenido de la carpeta ‘dist’ a una subcarpeta en nuestro proyecto, dentro de ‘src/main/webapp’ llamada ‘swagger’. Nuestra estructura de carpetas quedaría tal que así:

Estructura carpetas

Estructura de carpetas

7. Vamos a ‘costumizar’ un poco la interfaz, ya que por defecto apunta al swagger de ejemplo que tienen en la web (la petstore esa…). Abrimos el archivo index.html que tenemos directamente en nuestra subcarpeta ‘swagger’ y buscamos la siguiente linea en código javascript:

        ...
	url = "http://petstore.swagger.wordnik.com/v2/swagger.json";
        ...

Y sustituimos esa url por la nuestra, que será así: http://localhost:8080/api-docs . O podemos también no hardcodear la url de vuestro server, y dejarlo un poco más bonito sustituyéndolo por algo así:

        ...
	url = window.location.protocol + "//" + window.location.host + "/api-docs";
        ...

Con esto ya podemos arrancar nuestro server y accediendo a http://localhost:8080/swagger/index.html, ver la interfaz web de swagger funcionando contra nuestro servicio. Veremos algo como esto:

Swagger UI filtrada

Swagger por defecto

Entre otras cosas veremos que a nuestro servicio lo ha llamado ‘user-controller’ y que hay otro servicio más, basic-error-controller. Este último es parte de Spring, no le hagamos mucho caso.
Nos sigue faltando documentar en condiciones nuestro servicio REST (por ahora swagger nos muestra todo con textos por defecto), y ya de paso filtrar los servicios para que muestre solo el nuestro. ¡Vamos con ello!

7. Vamos a filtrar los servicios que mostramos. Podríamos filtrar por el nombre de nuestro servicio, pero para poder añadir otros, vamos a filtrar por la ruta del servicio, que sea ‘/api/loquesea’. Nuestro servicio actual es directamente /user, así que lo primero que vamos a hacer es modificar su url. En nuestra clase UserController modificamos el RequestMapping para dejarlo así:

...
@RestController
@RequestMapping("/api/user")
public class UserController {
...

Y ahora modificamos nuestra clase de configuración de Swagger, SwaggerSpringMvcConfig, para que filtre los servicios por un patrón:

    ...
    @Bean
    public SwaggerSpringMvcPlugin customImplementation() {
        return new SwaggerSpringMvcPlugin(this.springSwaggerConfig)
                .apiInfo(apiInfo())
                .includePatterns(".*api/*.*");
    }
    ...

Si arrancamos el servidor y accedemos otra vez a http://localhost:8080/swagger/index.html veremos que ahora ya no nos muestra nada más que nuestro servicio. (Ojo, la url para acceder al servicio REST ahora es: http://localhost:8080/api/user)

8. Ahora vamos a documentar nuestro servicio en condiciones, directamente en nuestras clases java (nada de xmls, ni historias de esas). Para ello swagger proporciona distintas anotaciones java. Vamos a ver y añadir algunas a nuestro servicio:
Primero documentaremos nuestro modelo, la clase User, quedaría así:

...
import com.wordnik.swagger.annotations.ApiModel;
import com.wordnik.swagger.annotations.ApiModelProperty;
import org.joda.time.LocalDate;

@ApiModel(value = "User entity", description = "Complete info of a entity user")
public class User {

    @ApiModelProperty(value = "The id of the user")
    private Long id;

    @ApiModelProperty(value = "The name of the user", required = true)
    private String name;

    @ApiModelProperty(value = "The type of the user", required = true)
    private Integer type;

    @ApiModelProperty(value = "The phone of the user", required = false)
    private String phone;

    @ApiModelProperty(value = "The birthDay of the user", required = false)
    private LocalDate birthDate;

    // getters, setters, etc
}

Las anotaciones son bastante ‘autoexplicativas’, pero por aclarar:
@ApiModel documenta la entity, con una descripción corta (value) y una descripción más larga (description).
@ApiModelProperty documenta las propiedades, con una descripción (value) y si es obligatoria (required). También contiene otros parámetros de posibles valores (allowableValues), ocultarlo a swagger (hidden) y otros.

Ahora documentamos nuestro controller y cada uno de sus métodos, quedando así:

...
// imports...

@RestController
@RequestMapping("/api/user")
@Api(value = "users", description = "Users API")
public class UserController {

    private static final int RESPONSE_CODE_OK = 200;
    private static final int RESPONSE_CODE_CREATED = 201;
    private static final int RESPONSE_CODE_NOCONTENT = 204;
    private static final int RESPONSE_CODE_NOTFOUND = 404;

    @Autowired
    private UserService userService;

    @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Get Users", response = List.class, notes = "Returns all users")
    @ApiResponses({
            @ApiResponse(code = RESPONSE_CODE_OK, message = "All users in db")
    })
    public List getAllUsers() {
        return userService.findAll();
    }

    @RequestMapping(value = "/{userId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Get one User", response = ResponseEntity.class, notes = "Returns one user")
    @ApiResponses({
            @ApiResponse(code = RESPONSE_CODE_OK, message = "Exists this user"),
            @ApiResponse(code = RESPONSE_CODE_NOTFOUND, message = "No exists this user")
    })
    public ResponseEntity getUser(@PathVariable long userId) {
        User user = userService.findById(userId);
        ResponseEntity response;
        if (user == null) {
            response = new ResponseEntity<>(HttpStatus.NOT_FOUND);
        } else {
            response = new ResponseEntity<>(user, HttpStatus.OK);
        }
        return response;
    }

    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    @ApiOperation(value = "Insert User", notes = "Insert a complete new User")
    @ApiResponses({
            @ApiResponse(code = RESPONSE_CODE_CREATED, message = "User inserted")
    })
    public void insertUser(@RequestBody User user) {
        userService.save(user);
    }

    @RequestMapping(value = "/{userId}", method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.CREATED)
    @ApiOperation(value = "Update User", notes = "Updates a user")
    @ApiResponses({
            @ApiResponse(code = RESPONSE_CODE_CREATED, message = "User updated")
    })
    public void updateUser(@PathVariable long userId, @RequestBody User user) {
        User userOld = userService.findById(userId);
        if (userOld != null) {
            userService.update(userOld.copyFrom(user));
        }
    }

    @RequestMapping(value = "/{userId}", method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus.NO_CONTENT)
    @ApiOperation(value = "Delete User", notes = "Deletes a user")
    @ApiResponses({
            @ApiResponse(code = RESPONSE_CODE_NOCONTENT, message = "Deleted user")
    })
    public void deleteUser(@PathVariable long userId) {
        userService.delete(userId);
    }
}

Aunque aquí también son bastante legibles las anotaciones, explico un poco:
@Api documenta el servicio REST en si. Va a ser la descripción que salga en el listado, entre otras cosas.
@ApiOperation documenta cada método del servicio.
@ApiResponses documenta las posibles respuestas del método, con mensaje explicativo.
(Hay alguna anotación más, para los parámetros, etc, pero estas son las que me parecen más interesantes)

9. Arrancamos el servidor, con mvn spring-boot:run o desde la tool window de maven en Intellij. Accedemos a http://localhost:8080/swagger/index.html y veremos algo como antes, pero mucho más detallado y documentado 🙂

Swagger UI por defecto

Swagger UI filtrada y documentada

Swagger permite probar el servicio desde su propia interfaz, consultar el modelo a enviar o recibido, etc. Jugad un poco con él 🙂

Si quieres bajarte el proyecto completo, lo he subido a mi github 🙂
Cualquier duda o sugerencia, podéis dejarme un comentario.