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.

Implementar un servicio Rest con Spring

Tenia pensado que este post fuera un POC con Swagger, pero dado que primero necesitamos tener implementado un servicio Rest, voy a dedicar este post a eso: implementar, con la última versión de Spring, un servicio Rest básico.

Para ahorrar tiempo lo haremos desde una aplicación con Spring Boot, como la que montamos en el post pasado. Si no lo habéis leído, es el momento: POC con Spring Boot. Si queréis ahorrar tiempo, podéis descargaros de mi github el poc de Spring Boot, y seguir los pasos de este post directamente sobre él.

Concepto:
Si queréis saber más del concepto REST, os dejo varios links, no me voy a enrollar mucho en explicarlo.
http://en.wikipedia.org/wiki/Representational_state_transfer
http://martinfowler.com/articles/richardsonMaturityModel.html
Es un estilo de diseño de arquitectura de servicios sencillo, que, entre otras cosas, esta basado en el uso de los «verbos» http (POST, PUT, GET…), con los que accedemos directamente a los recursos.

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

Pasos:

1. Como hemos dicho, tomamos como base un proyecto Spring Boot como el montado en el anterior post.
Esta vez, en lugar de tener nuestro HelloWorldController, vamos a crear un controller con un poco más de chicha (lo más RESTful posible). Crearemos un servicio REST básico de usuarios, con formato json para la entrada y salida. A por ello!

2. Comenzamos creando una clase entidad ‘User’ para nuestro servicio REST, dentro de un nuevo subpackage: ‘entities’. Le añadimos varios campos, de distintos tipos, quedaría tal que así:

package com.edwise.pocs.springrest.entities;

import org.joda.time.LocalDate;

public class User {

    private Long id;
    private String name;
    private Integer type;
    private String phone;

    private LocalDate birthDate;

    public Long getId() {
        return id;
    }

    public User setId(Long id) {
        this.id = id;
        return this;
    }

    public String getName() {
        return name;
    }

    public User setName(String name) {
        this.name = name;
        return this;
    }

    public Integer getType() {
        return type;
    }

    public User setType(Integer type) {
        this.type = type;
        return this;
    }

    public String getPhone() {
        return phone;
    }

    public User setPhone(String phone) {
        this.phone = phone;
        return this;
    }

    public LocalDate getBirthDate() {
        return birthDate;
    }

    public User setBirthDate(LocalDate birthDate) {
        this.birthDate = birthDate;
        return this;
    }

    public User copyFrom(User user) {
        if (user.name != null) {
            this.name = user.name;
        }
        if (user.type != null) {
            this.type = user.type;
        }
        if (user.phone != null) {
            this.phone = user.phone;
        }
        if (user.birthDate != null) {
            this.birthDate = user.birthDate;
        }
        return this;
    }

    ...

}

El metodo copyFrom lo necesitaremos a la hora de actualizar un User. La idea al actualizar es obtener primero el ya existente, y sobre él llamar a su método copyFrom pasandole el objeto User con los campos modificados, de manera que solo ‘machaque’ los campos que vienen en el json (que no estén como nulos).
Entre otros, tendremos un campo birthDate de tipo LocalDate (librería joda-time). He preferido usar esta librería a la nueva date de Java 8, por si queréis montar el proyecto con Java 7. Necesitamos por ello añadir las siguientes dependencias a nuestro pom.xml:

...

         <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.4</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-joda</artifactId>
            <version>2.4.2</version>
        </dependency>

...

La segunda dependencia la necesitaremos para el parseo de/a json de ese campo. Lo veremos un poco más adelante.

3. Antes de liarnos con el controller, vamos a crear primero un Service, que sea el que nos de las funcionalidades de añadir, buscar, etc Users. (para que quede bonito el ejemplo ;), y por si más adelante lo queremos conectar con una base de datos o similar) Crearemos primero una interfaz, bajo el subpackage services:

package com.edwise.pocs.springrest.services;

// imports...

public interface UserService {

    User findById(Long id);

    List<User> findAll();

    User save(User user);

    User update(User user);

    void delete(Long id);
}

 

4. Y luego un ‘mock’ de servicio, que la implemente, bajo ‘services.impl’:

package com.edwise.pocs.springrest.services.impl;

// imports...

@Service
public class UserServiceMock implements UserService {
    private static final long USER_ID_12 = 12L;
    private static final long USER_ID_140 = 140L;
    private static final long USER_ID_453321 = 45332L;
    private static final String NAME_GANDALF = "Gandalf";
    private static final String NAME_ARAGORN = "Aragorn";
    private static final String NAME_FRODO = "Frodo";
    private static final int TYPE_1 = 1;
    private static final int TYPE_2 = 2;
    private static final String PHONE_666554433 = "666554433";
    private static final String PHONE_661534411 = "661534411";
    private static final String PHONE_666222211 = "666222211";
    private static final String STRING_DATE_19110102 = "1911-01-02";
    private static final String STRING_DATE_19230716 = "1923-07-16";
    private static final String STRING_DATE_19511124 = "1951-11-24";


    @Override
    public User findById(Long id) {
        return new User()
                .setId(id)
                .setName(NAME_GANDALF)
                .setPhone(PHONE_666554433)
                .setType(TYPE_1)
                .setBirthDate(LocalDate.parse(STRING_DATE_19110102));
    }

    @Override
    public List<User> findAll() {
        return Arrays.asList(
                new User()
                        .setId(USER_ID_12)
                        .setName(NAME_GANDALF)
                        .setPhone(PHONE_666554433)
                        .setType(TYPE_1)
                        .setBirthDate(LocalDate.parse(STRING_DATE_19110102)),
                new User()
                        .setId(USER_ID_140)
                        .setName(NAME_ARAGORN)
                        .setPhone(PHONE_661534411)
                        .setType(TYPE_1)
                        .setBirthDate(LocalDate.parse(STRING_DATE_19230716)),
                new User()
                        .setId(USER_ID_453321)
                        .setName(NAME_FRODO)
                        .setPhone(PHONE_666222211)
                        .setType(TYPE_2)
                        .setBirthDate(LocalDate.parse(STRING_DATE_19511124))
        );
    }

    @Override
    public User save(User user) {
        return user.setId(USER_ID_453321);
    }

    @Override
    public User update(User user) {
        return user;
    }

    @Override
    public void delete(Long id) {
        // delete user by id
    }
}

 

5. Vamos ya con nuestro controller, nuestro servicio REST en si:

package com.edwise.pocs.springrest.controllers;

// imports...

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

    @Autowired
    private UserService userService;

    @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public List<User> getAllUsers() {
        return userService.findAll();
    }

    @RequestMapping(value = "/{userId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<User> getUser(@PathVariable long userId) {
        User user = userService.findById(userId);
        ResponseEntity<User> 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)
    public void insertUser(@RequestBody User user) {
        userService.save(user);
    }

    @RequestMapping(value = "/{userId}", method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.NO_CONTENT)
    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)
    public void deleteUser(@PathVariable long userId) {
        userService.delete(userId);
    }
}

Vamos a explicar un poquito el código de nuestro controller:

– La anotación @RestController es igual que la clásica de @Controller, lo único que ya marca la clase con @ResponseBody (aparte que mola más que nuestro controller sea un RestController 😉 )
– Inyectamos el service con Autowired en la linea 22, para poder acceder al recurso User.
– El @RequestMaping lo hemos puesto a nivel de clase, con la url ‘/api/users’, con la que accedemos a todos los métodos.
– Los métodos son los típicos CRUD (Create, Read, Update y Delete), más un ‘READ ALL’. Se diferencian entre sí por el método http y/o si aceptan un parámetro en la url. Cada uno llama a su correspondiente método del service.
– En el método updateUser, obtenemos el User existente, y sobre él, copiamos los campos modificados (por eso el método copyFrom de nuestra entity User)
– En el método getUser, devolvemos un ResponseEntity. Para poder devolver tanto un User si lo encuentra, como solo un NOT_FOUND (404) si no es así.

6. Si arrancamos nuestra aplicación con Spring Boot (con ‘mvn spring-boot:run’, o desde la tool window de maven en Intellij), podemos probar a acceder, por ejemplo, a http://localhost:8080/api/users/. Nos devolverá algo como esto:

[
  {
    "id": 12,
    "name": "Gandalf",
    "type": 1,
    "phone": "666554433",
    "birthDate": [1911, 1, 2]
  },
  {
    "id": 140,
    "name": "Aragorn",
    "type": 1,
    "phone": "661534411",
    "birthDate": [1923, 7, 16]
  },
  {
    "id": 45332,
    "name": "Frodo",
    "type": 2,
    "phone": "666222211",
    "birthDate": [1951, 11, 24]
  }
]

Lo normal es acceder mediante algún cliente REST (por ejemplo RESTClient para Firefox), para, por un lado, que nos lo muestre un poco más legible (pretty json), y por otro, para poder hacer peticiones PUT, DELETE y POST.

7. Ejemplos de peticiones contra nuestro REST:

– Para crear un User, haríamos un POST contra http://localhost:8080/api/users/ pasándole como body:

{
    "name": "Pippin",
    "type": 2,
    "phone": "666554433",
    "birthDate": "1968-12-25"
}

Y añadiendole de header: ‘Content-Type: application/json’. Devolverá un 201.

– Para actualizar un User, haríamos un PUT contra http://localhost:8080/api/users/12/ (por ejemplo) pasándole como body:

{
    "name": "Gandalf the White",
    "type": 10
}

Y añadiéndole también de header: ‘Content-Type: application/json’. Devolverá un 204.

– Para borrar un User, haríamos un DELETE contra http://localhost:8080/api/users/12/ (por ejemplo), sin body ni header. Devolverá un 204.

– Para obtener un User en concreto, haríamos un GET contra http://localhost:8080/api/users/12/ (por ejemplo), sin body ni header. Devolverá un 200, y el json con el user.

 

Nota sobre la librería joda-time: para que la serialización y deserialización del campo birthDate sea correcta, tenemos que añadir, por un lado la dependencia en maven de ‘jackson-datatype-joda’ (ya la añadimos más arriba), y por otro lado, añadir el siguiente bean, por ejemplo, en nuestra clase Application (u otra clase de configuración de spring que tengamos):

...

    @Bean
    public Module jodaModule() {
        return new JodaModule();
    }

...

Si no hacemos esto, veréis que el json que devuelve para el campo birthDate es bastante tocho (aparte de que siempre os dará error al intentar hacer post o put con ese campo. Añadiendo esto, hacemos que el LocalDate de joda-time se serialice y desserialice con los serializadores por defecto para esa clase de ‘jackson-datatype-joda’. (el cual, al pasar a json devuelve la fecha en un array de tres campos. Y a la hora de pasarlo de json a la clase, se le puede pasar de esa manera, o con formato ‘YYYY-MM-DD’)

 

Con esto ya tenemos nuestro servicio REST. Podéis descargar el código completo de este ejemplo aquí.

¿Lo documentamos ahora con Swagger? Lo dejamos para el próximo post 😉 Gracias a los que habéis llegado hasta aquí, cualquier comentario, duda, corrección, lo podéis poner en los comentarios.

ACTUALIZADO: He actualizado el proyecto en github con los cambios que he hecho para el post Cómo devolver el resultado de un POST en un servicio REST.