Desarrollo basado en pruebas para las API Spring Boot

Con el aumento de la adopción de teléfonos inteligentes en el mundo actual, ha habido una afluencia de aplicaciones móviles para lograr una amplia variedad de tareas. Algunas de las...

Introducción

Con el aumento de la adopción de teléfonos inteligentes en el mundo actual, ha habido una afluencia de aplicaciones móviles para lograr una amplia variedad de tareas. Algunas de las aplicaciones que usamos a diario se comunican con otros sistemas para brindarnos una experiencia perfecta en múltiples dispositivos y plataformas.

¿Cómo es esto posible? Interfaces de programación de aplicaciones (API) son responsables de esta conectividad extendida. Hacen posible que las aplicaciones móviles y web interactúen y faciliten la transferencia de datos entre ellas y otros sistemas.

En este artículo, discutiremos las API, las mejores prácticas al construirlas y también construiremos una API utilizando el enfoque de desarrollo basado en pruebas y [Bota de primavera] (https://spring.io/projects/spring-boot) estructura.

El auge de las API

Una API define un conjunto de rutinas y protocolos para la interacción entre sistemas de software. Muchas aplicaciones móviles y web interactúan con servidores que manejan solicitudes y responden a ellas, denominados clientes.

A medida que los sistemas aumentan de tamaño, se vuelven robustos y pueden volverse difíciles de mantener y realizar actualizaciones. Al desacoplar un sistema en varias API específicas, se logra flexibilidad y las partes del sistema robusto ahora se pueden actualizar o implementar en partes fácilmente sin afectar el tiempo de actividad o el rendimiento del resto del sistema.

Esto da como resultado una arquitectura de microservicios, que depende en gran medida del desarrollo de API. En un sistema de este tipo, las API proporcionan un modo de comunicación dentro del sistema y las diversas partes del sistema aún pueden interactuar y compartir la carga de trabajo.

Los teléfonos inteligentes nos permitieron mantenernos conectados y, con su poder cada vez mayor, podemos lograr mucho más. El acceso a Internet también se ha vuelto más común, por lo que la mayoría de los teléfonos inteligentes están constantemente conectados a Internet. Estos dos factores impulsan el uso de aplicaciones móviles que interactúan con servidores web donde las API entran en escena.

Las API facilitan la comunicación entre las aplicaciones móviles y los servidores y el aumento en el uso de aplicaciones móviles ha impulsado el aumento de las API.

Las aplicaciones web también han evolucionado con el tiempo y la complejidad ha aumentado. Esto, a su vez, ha llevado a la separación de las capas de presentación y lógica de una aplicación web normal. Inicialmente, tendría ambas capas de una aplicación web creadas juntas e implementadas como una sola para uso masivo. Ahora, la sección de frontend está desacoplada del backend para facilitar la separación de preocupaciones.

Las API también permiten a las empresas una configuración de back-end única para servir aplicaciones móviles y aplicaciones web al mismo tiempo. Esto ahorra tiempo de desarrollo y deuda técnica, ya que el sistema backend solo se modifica en un punto.

Los teléfonos inteligentes también son diversos y las empresas ahora tienen que atender múltiples tipos de teléfonos inteligentes al mismo tiempo para brindar una experiencia uniforme a sus usuarios. Las API hacen posible que las aplicaciones móviles que se ejecutan en diferentes plataformas interactúen de manera uniforme con un solo sistema de back-end o API.

Es muy importante mencionar que las API también hacen posible que otros desarrolladores que usan diferentes lenguajes de programación accedan a nuestro sistema para obtener información. Esto facilita la integración de sistemas que utilizan diferentes lenguajes de programación.

Esto, nuevamente, nos permite crear aplicaciones modulares, utilizando varios lenguajes, herramientas y marcos para sacar lo mejor de cada uno.

Creación de mejores API

Las API también actúan como un punto de contacto con el trabajo de otros desarrolladores, ya que pueden permitir que otros desarrolladores las consuman para su propio uso.

Por ejemplo, Twitter ha expuesto algunas de sus API para que las usen otros desarrolladores para construir otros clientes de Twitter y usar la plataforma de otras maneras únicas. Algunos han construido bots en plataformas como Telegram para enviar tweets o buscar tweets, todo lo cual se logra a través de API.

Esto hace que las API sean importantes en los ecosistemas de software actuales y futuros, ya que nos permiten integrarnos con otros sistemas de manera flexible. No solo API, sino buenas API.

Es primordial que nuestra API esté bien construida y documentada para que cualquier otra persona que la consuma tenga un tiempo más fácil. La documentación es el aspecto más importante de una API, permite que otros desarrolladores sepan lo que logra y lo que se requiere para aprovechar esa funcionalidad. También ayuda a los mantenedores a saber con qué están tratando y asegurarse de que sus cambios no afecten o rompan la funcionalidad existente.

Códigos de estado HTTP se definieron para identificar diversas situaciones que pueden ocurrir cuando una aplicación interactúa con una API.

Se dividen en cinco categorías que incluyen códigos para:

  • Respuestas informativas: 1xx estados, como 100 Continuar, 101 Protocolos de conmutación, etc.
  • Éxito: 2xx estados, como 200 OK, 202 Aceptado, etc.
  • Redireccionamiento: 3xx estados, como 300 Opciones múltiples, 301 Movido permanentemente, etc.
  • Errores de cliente: 4xx estados, como 400 Solicitud incorrecta, 403 Prohibido, 404 No encontrado, etc.
  • Errores del servidor: 5xx estados, como 500 Error interno del servidor, 502 Bad Gateway, 503 Servicio no disponible, etc.

Estos códigos ayudan al sistema ya las personas que interactúan con él a identificar y comprender la naturaleza de los eventos que ocurren y las causas de cualquier error.

Al adherirnos a los Códigos de estado HTTP en nuestras API, podemos hacer que nuestras API sean fáciles de interactuar e integrar. Además de estos, también podemos definir nuestros propios códigos de error para nuestras API, pero es importante que los documentemos claramente para que sea más fácil para los consumidores y mantenedores de las API.

Antes de que los automóviles, los teléfonos o los dispositivos electrónicos se entreguen a sus usuarios, se prueban exhaustivamente para garantizar que no funcionen mal cuando están en uso. Las API se han vuelto más comunes e importantes, por lo tanto, también necesitan la misma atención a los detalles.

Deben probarse exhaustivamente antes de su lanzamiento para evitar un mal funcionamiento durante la producción.

Creación de una API

Arquitectura del proyecto {#arquitectura del proyecto}

Supongamos que estamos creando una aplicación que ayuda a los usuarios a mantener una lista de sus automóviles. Podrán agregar autos nuevos, actualizar autos existentes e incluso eliminar autos que ya no poseen. Esta aplicación estará disponible tanto para dispositivos Android como iOS y también como aplicación web.

Con Spring Boot Framework, podemos crear una sola API que pueda servir a las tres aplicaciones, o clientes, simultáneamente.

Nuestro viaje comienza en la herramienta Inicializador de primavera que nos ayuda a arrancar rápidamente nuestra API Spring Boot en cuestión de minutos. Hay muchas dependencias y paquetes que nos ayudan a lograr diversas funcionalidades en nuestras API y la herramienta Spring Initializer ayuda a integrarlos en nuestro proyecto de inicio.

Esto tiene como objetivo facilitar nuestro proceso de desarrollo y permitirnos dirigir nuestra atención a la lógica de nuestra aplicación:

Spring Initializr{.img-responsive}

La herramienta nos permite elegir entre Experto y gradle, que son herramientas que nos ayudan a automatizar algunos aspectos de nuestro flujo de trabajo de compilación, como las pruebas. , ejecutar y empaquetar nuestra aplicación Java. También tenemos la opción de elegir entre usar Java o Kotlin al construir nuestra API usando Spring Boot, para lo cual podemos especificar la versión.

Cuando hacemos clic en "Cambiar a la versión completa", obtenemos más opciones para incluir en nuestra API. Muchas de estas opciones resultan útiles al crear microservicios como las secciones "Cloud Config" y "Cloud Discovery".

Para nuestra API, elegiremos las siguientes dependencias:

  • Web para ayudarnos a desarrollar una API basada en web
  • MySQL que nos ayudará a conectarnos a nuestra base de datos MySQL,
  • JPA, que es la API de persistencia de Java para satisfacer nuestras necesidades de interacción con la base de datos, y
  • Actuador para ayudarnos a mantener y monitorear nuestra aplicación web.

Con las dependencias establecidas, hacemos clic en el botón "Generar proyecto" para obtener un zip que contiene nuestro código repetitivo.

Identifiquemos lo que viene en el paquete usando el comando tree:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ tree .
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pbcopy
├── pom.xml
└── src
    ├── main
       ├── java
          └── com
              └── example
                  └── cars
                      └── CarsApplication.java
       └── resources
           ├── application.properties
           ├── static
           └── templates
    └── test
        └── java
            └── com
                └── example
                    └── cars
                        └── CarsApplicationTests.java

En la carpeta raíz, hay un archivo pom.xml que contiene la configuración del proyecto para nuestra API Spring Boot. Si usáramos Gradle, tendríamos un archivo build.gradle en su lugar. Incluye información como los detalles de nuestra nueva API y todas sus dependencias.

Principalmente trabajaremos en las carpetas main y test dentro de la carpeta fuente (src). Aquí es donde colocaremos nuestros controladores, modelos, clases de utilidad, entre otros.

Comencemos creando nuestra base de datos y configurando nuestra API para usarla. Siga esta guía para instalar y verificar que MySQL se está ejecutando.

Una vez listo, vamos a crear nuestra base de datos de la siguiente manera:

1
2
3
4
$ mysql -u root -p

mysql> CREATE DATABASE cars_database;
Query OK, 1 row affected (0.08 sec)

Algunos detalles de nuestro servicio serán diferentes de un entorno a otro. Por ejemplo, la base de datos que usamos durante el desarrollo no será la misma que usarán los usuarios finales para almacenar su información.

Los archivos de configuración nos facilitan cambiar dichos detalles, lo que hace que nuestra API sea fácil de migrar y modificar. Esto se logra a través del archivo de configuración, que en una API Spring Boot es el archivo application.properties que se encuentra en la carpeta src/main/resources.

Para habilitar nuestra dependencia JPA para acceder y modificar nuestra base de datos, modificamos el archivo de configuración agregando las propiedades:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Database Properties
spring.datasource.url = jdbc:mysql://localhost:3306/cars_database?useSSL=false
spring.datasource.username = root
spring.datasource.password = password

# Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update

Ahora necesitamos una clase de entidad para definir los recursos de nuestra API y sus detalles, ya que se guardarán en nuestra base de datos. Un Car es nuestro recurso en esta API y lo que esto significa es que representa nuestro objeto o elemento de la vida real sobre cuya información realizaremos acciones. Dichas acciones incluyen Crear, Leer, Actualizar y Eliminar, simplemente como operaciones CRUD.

Estas operaciones están detrás de los métodos HTTP o verbos que se refieren a varias operaciones que puede exponer una API. Incluyen:

  • GET, que es una operación de lectura que solo obtiene los datos especificados,
  • ‘POST’ que permite la creación de ‘recursos’ proporcionando su información como parte de la solicitud,
  • PUT que nos permite modificar un recurso, y
  • DELETE que usamos para eliminar un recurso y su información de nuestra API.

Para organizar mejor nuestro código, introduciremos algunas carpetas más en nuestro proyecto en el nivel src/main/java/com/example/cars/. Agregaremos una carpeta llamada modelos para albergar las clases que definen nuestros objetos.

Las otras carpetas que se agregarán incluyen una carpeta de “controladores” que contiene nuestros controladores, una carpeta de “repositorio” para las clases de administración de la base de datos y una carpeta de “utils” para cualquier clase auxiliar que necesitemos agregar a nuestro proyecto. La estructura de carpetas resultante será:

 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
$ tree .
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pbcopy
├── pom.xml
└── src
    ├── main
       ├── java
          └── com
              └── example
                  └── cars
                      ├── CarsApplication.java
                      ├── controllers
                      ├── models
                      ├── repository
                      └── utils
       └── resources
           ├── application.properties
           ├── static
           └── templates
    └── test
        └── java
            └── com
                └── example
                    └── cars
                        └── CarsApplicationTests.java

Modelo de dominio

Definamos nuestra clase Car en la carpeta models:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/**
* This class will represent our car and its attributes
*/
@Entity
@Table(name="cars") // the table in the database tht will contain our cars data
@EntityListeners(AuditingEntityListener.class)
public class Car {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private long id; // Each car will be given an auto-generated unique identifier when stored

    @Column(name="car_name", nullable=false)
    private String carName; // We will also save the name of the car

    @Column(name="doors", nullable=false)
    private int doors; // We will also save the number of doors that a car has

    // getters and setters
}

Nota: Eliminé las importaciones para acortar el fragmento de código. Consulte el repositorio de Github adjunto al final del artículo para obtener el código completo.

DAO

Con nuestro modelo de automóvil listo, ahora creemos el archivo CarRepository que se usará en la interacción con la base de datos:

1
public interface CarRepository extends JpaRepository<Car, Long> { }

Exámenes de escritura {#tests de escritura}

Ahora podemos exponer la funcionalidad de nuestra API a través de nuestro controlador, pero en el espíritu de Desarrollo basado en pruebas (TDD), escribamos las pruebas primero en el archivo CarsApplicationTests:

 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
// These are a subset of the tests, the full test file is available on the Github repo attached at the end of this article
....

    /**
     * Here we test that we can get all the cars in the database
     * using the GET method
     */
    @Test
    public void testGetAllCars() {
        HttpHeaders headers = new HttpHeaders();
        HttpEntity<String> entity = new HttpEntity<String>(null, headers);

        ResponseEntity<String> response = restTemplate.exchange(getRootUrl() + "/cars",
            HttpMethod.GET, entity, String.class);

        Assert.assertNotNull(response.getBody());
    }

    /**
     * Here we test that we can fetch a single car using its id
     */
    @Test
    public void testGetCarById() {
        Car car = restTemplate.getForObject(getRootUrl() + "/cars/1", Car.class);
        System.out.println(car.getCarName());
        Assert.assertNotNull(car);
    }

    /**
     * Here we test that we can create a car using the POST method
     */
    @Test
    public void testCreateCar() {
        Car car = new Car();
        car.setCarName("Prius");
        car.setDoors(4);

        ResponseEntity<Car> postResponse = restTemplate.postForEntity(getRootUrl() + "/cars", car, Car.class);
        Assert.assertNotNull(postResponse);
        Assert.assertNotNull(postResponse.getBody());
    }

    /**
     * Here we test that we can update a car's information using the PUT method
     */
    @Test
    public void testUpdateCar() {
        int id = 1;
        Car car = restTemplate.getForObject(getRootUrl() + "/cars/" + id, Car.class);
        car.setCarName("Tesla");
        car.setDoors(2);

        restTemplate.put(getRootUrl() + "/cars/" + id, car);

        Car updatedCar = restTemplate.getForObject(getRootUrl() + "/cars/" + id, Car.class);
        Assert.assertNotNull(updatedCar);
    }

Las pruebas simulan varias acciones que son posibles en nuestra API y esta es nuestra forma de verificar que la API funciona como se espera. Si se hiciera un cambio mañana, las pruebas ayudarán a determinar si alguna de las funciones de la API está rota y, al hacerlo, evitará que rompamos la funcionalidad al realizar cambios.

Piense en las pruebas como una lista de compras cuando vaya al supermercado. Sin él, podríamos terminar eligiendo casi todo lo que encontramos que creemos que podría ser útil. Puede llevarnos mucho tiempo obtener todo lo que necesitamos. Si tuviéramos una lista de compras, podríamos comprar exactamente lo que necesitamos y terminar de comprar más rápido. Las pruebas hacen lo mismo con nuestras API, nos ayudan a definir el alcance de la API para que no implementemos una funcionalidad que no estaba en los planes o que no se necesitaba.

Cuando ejecutamos nuestras pruebas usando el comando mvn test, veremos que surgen errores y esto se debe a que aún no hemos implementado la funcionalidad que satisface nuestros casos de prueba.

En TDD, primero escribimos las pruebas, las ejecutamos para asegurarnos de que fallan inicialmente y luego implementamos la funcionalidad para que las pruebas pasen.

TDD es un proceso iterativo de escribir pruebas e implementar la funcionalidad para que las pruebas pasen. Si introducimos algún cambio en el futuro, primero escribiremos las pruebas y luego implementaremos los cambios para que pasen las nuevas pruebas.

Controlador

Ahora implementemos nuestra funcionalidad API en un CarController que va a la carpeta controllers:

 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
@RestController
@RequestMapping("/api/v1")
public class CarController {

    @Autowired
    private CarRepository carRepository;

    // GET Method for reading operation
    @GetMapping("/cars")
    public List<Car> getAllCars() {
        return carRepository.findAll();
    }

    // GET Method for Read operation
    @GetMapping("/cars/{id}")
    public ResponseEntity<Car> getCarsById(@PathVariable(value = "id") Long carId)
        throws ResourceNotFoundException {

        Car car = carRepository
                  .findById(carId)
                  .orElseThrow(() -> new ResourceNotFoundException("Car not found on :: " + carId));
        return ResponseEntity.ok().body(car);
    }

    // POST Method for Create operation
    @PostMapping("/cars")
    public Car createCar(@Valid @RequestBody Car car) {
        return carRepository.save(car);
    }

    // PUT Method for Update operation
    @PutMapping("/cars/{id}")
    public ResponseEntity<Car> updateCar(
        @PathVariable(value = "id") Long carId, @Valid @RequestBody Car carDetails)
        throws ResourceNotFoundException {
            Car car = carRepository
                      .findById(carId)
                      .orElseThrow(() -> new ResourceNotFoundException("Car " + carId + " not found"));

        car.setCarName(carDetails.getCarName());
        car.setDoors(carDetails.getDoors());

        final Car updatedCar = carRepository.save(car);
        return ResponseEntity.ok(updatedCar);
    }

    // DELETE Method for Delete operation
    @DeleteMapping("/car/{id}")
    public Map<String, Boolean> deleteCar(@PathVariable(value = "id") Long carId) throws Exception {
        Car car = carRepository
                  .findById(carId)
                  .orElseThrow(() -> new ResourceNotFoundException("Car " + carId + " not found"));

        carRepository.delete(car);
        Map<String, Boolean> response = new HashMap<>();
        response.put("deleted", Boolean.TRUE);
        return response;
    }
}

En la parte superior, tenemos la anotación @RestController para definir nuestra clase CarController como el controlador de nuestra API Spring Boot. Lo que sigue es @RequestMapping donde especificamos la ruta base de nuestra URL API como /api/v1. Esto también incluye la versión.

El control de versiones es una buena práctica en una API para mejorar la compatibilidad con versiones anteriores. Si la funcionalidad cambia y ya tenemos otras personas que consumen nuestras API, podemos crear una nueva versión y hacer que ambas se ejecuten simultáneamente para darles tiempo suficiente para migrar a la nueva API.

Anteriormente, aprendimos sobre las operaciones Crear, Leer, Actualizar y Eliminar en una API y cómo se asignan a los métodos HTTP. Estos métodos se acomodan en el marco Spring como anotaciones PostMapping, GetMapping, PutMapping y DeleteMapping, respectivamente. Cada una de estas anotaciones nos ayuda a exponer puntos finales que solo realizan la operación CRUD especificada.

También podemos tener un punto final único que maneje varios métodos HTTP:

1
@RequestMapping(value="/cars", method = { RequestMethod.GET, RequestMethod.POST })

Ahora que hemos implementado la funcionalidad, ejecutemos nuestras pruebas:

Test results{.img-responsive}

Las pruebas aprobadas nos muestran que hemos implementado la funcionalidad deseada al escribir las pruebas y nuestra API funciona.

Interactuemos con nuestra API a través de Cartero, que es una herramienta que ayuda a interactuar con las API al desarrollarlas o consumirlas.

Comenzamos por buscar todos los autos que tenemos almacenados en nuestra base de datos:

Vaciar carros{.img-responsive}

Al principio, no tenemos autos almacenados. Añadamos nuestro primer coche:

Post first car{.img-responsive}

La respuesta es el id y los detalles del coche que acabamos de añadir. Si añadimos algunos coches más y buscamos todos los coches que hemos guardado:

Get all cars{.img-responsive}

Estos son los autos que hemos creado usando nuestra API Spring Boot. Una verificación rápida en la base de datos devuelve la misma lista:

Database all cars{.img-responsive}

Interfaz de usuario de Swagger

Hemos construido y probado nuestra API usando TDD y ahora para mejorar nuestra API, vamos a documentarla usando Interfaz de usuario de Swagger, que permite nosotros para crear una interfaz generada automáticamente para que otros usuarios interactúen y aprendan sobre nuestra API.

Primero, agreguemos las siguientes dependencias en nuestro pom.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.7.0</version>
</dependency>

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

A continuación, crearemos un SwaggerConfig.java en la misma carpeta que CarsApplication.java, que es el punto de entrada a nuestra API.

El archivo SwaggerConfig.java también permite agregar información sobre nuestra API:

 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
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.example.cars"))
            .paths(PathSelectors.any())
            .build()
            .apiInfo(metadata());
    }

    /**
     * Adds metadata to Swagger
     *
     * @return
     */
    private ApiInfo metadata() {
        return new ApiInfoBuilder()
            .title("Cars API")
            .description("An API to store car details built using Spring Boot")
            .build();
    }
}

Ahora anotamos nuestros puntos finales para que aparezcan en la interfaz de usuario de Swagger que se generará. Esto se consigue de la siguiente manera:

1
2
3
4
5
6
// Add this import in our controller file...
import io.swagger.annotations.ApiOperation;

// ...then annotate our HTTP Methods
@ApiOperation(value="Fetches all cars in the database", response=Car.class)
@PostMapping("/...") // Our endpoint

Hemos especificado nuestra clase de respuesta como la clase Car ya que es la que se usará para completar los detalles de nuestras respuestas. Hicimos esto porque la interfaz de usuario de Swagger nos permite agregar información sobre las cargas útiles de la solicitud y los detalles de la respuesta. Esto ayudará a proporcionar más información sobre las cargas útiles, como el tipo de valores que requiere nuestra API y el tipo de respuesta que se devolverá. También podemos especificar campos obligatorios en la documentación.

En nuestro caso, también usaremos la clase Car para formatear y validar nuestros parámetros de solicitud. Por lo tanto, anotamos sus "captadores" de la siguiente manera:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    @ApiModelProperty(name="id",
                      value="The id of the car",
                      example="1")
    public long getId() {
        return id;
    }

    @ApiModelProperty(name="carName",
                      value="The name of the car to be saved",
                      example="Bugatti",
                      required=true)
    public String getCarName() {
        return carName;
    }

    @ApiModelProperty(name="doors",
                      value="The number of doors that the car has",
                      example="2",
                      required=true)
    public int getDoors() {
        return doors;
    }

¡Eso es todo! Nuestra documentación está lista. Cuando ejecutamos nuestra API usando mvn spring-boot:run y navegamos a http://localhost:8080/swagger-ui.html podemos ver la documentación de nuestra API:

Swagger UI final{.img-responsive}

Swagger UI ha documentado todos nuestros puntos finales e incluso ha proporcionado funcionalidad para interactuar con nuestra API directamente desde la documentación. Como se puede ver en la sección inferior derecha de la captura de pantalla, nuestros valores de ejemplo se han rellenado previamente para que podamos probar rápidamente la API sin tener que volver a escribir los valores.

Conclusión

Java es un lenguaje poderoso y hemos aprovechado su poder para construir una interfaz de programación de aplicaciones, o API, utilizando el marco Spring Boot. Pudimos implementar cuatro de los métodos HTTP para manejar las diversas operaciones de creación, lectura, actualización y eliminación de los detalles de nuestros automóviles.

Swagger UI también nos ha permitido documentar nuestra API de una manera simple pero detallada y tener esta documentación expuesta como un punto final en nuestro servicio. Habiendo notado las ventajas del desarrollo basado en pruebas, continuamos y escribimos pruebas para nuestros puntos finales y nos aseguramos de que nuestra funcionalidad y pruebas estén alineadas.

El código fuente de este proyecto está disponible aquí en Github.