¿Qué aprenderá?

Al finalizar este tutorial el estudiante estará en capacidad de realizar la implementación de la capa de API junto con las pruebas de integración.

¿Qué necesita?

Para realizar este taller Ud. debe:

  1. Aprovisionar su máquina virtual
  2. Contar con el modelo de entidades
  3. Tener clonado el proyecto back

El proyecto del curso está dividido en varias capas. En este tutorial abordaremos la capa correspondiente a los servicios REST.

El proyecto, en su vista de desarrollo está organizado en varios paquetes. El paquete controllers incluye las clases que representan los endpoints para el API REST. El paquete services contiene las clases encargadas de la lógica. Finalmente, el paquete repositories contiene las clases encargadas de la persistencia.

El siguiente diagrama presenta las clases involucradas en la implementación del API. A continuación se detallan los pasos para la implementación.

Tenga en cuenta que las entidades son clases que representan al modelo de datos, o que se mapean directamente contra una tabla de la base de datos. Dicho esto, las entidades son clases que fueron diseñadas para mapear contra la base de datos, pero no para ser una vista para una pantalla o servicio determinado. Por tanto, se debe crear un objeto plano (también conocido como POJO) con una serie de atributos que puedan ser enviados o recuperados del servidor.

Para este ejemplo se tiene el concepto Student, por tanto se debe crear una clase StudentDTO. Esta clase contiene los atributos de la clase StudentEntity junto con el id. La clase se anota con @Data para agregar los getters y setters necesarios.

Esta clase se debe agregar en el paquete dto.

package co.edu.uniandes.dse.bookstore.dto;

import lombok.Data;

@Data
public class StudentDTO {
        private Long id;
        private String name;
}

Para este ejemplo se tiene el concepto Student, por tanto la clase que define el controlador tendrá la convención de nombramiento concepto + Controller, en este caso, StudentController.

Vaya al paquete controllers y con clic derecho seleccione New > Class. Ingrese el nombre de la clase y haga clic en Finish.

La clase debe incluir varias anotaciones:

@RestController. Anotación que convierte la clase en un controlador y permite serializar la respuesta de los métodos.

@RequestMapping. Indica la ruta mediante la cual se accede a ese controlador.

Luego, en la implementación de la clase se añaden dos inyecciones de dependencia, una para el servicio (StudentService) y otro para el mapper (ModelMapper). Dado que la capa de lógica solo conoce de entidades (Entities) y que el API solo recibe objetos planos (DTO) se requiere hacer una conversión entre estos dos tipos de datos; este es el trabajo del mapper.

Posteriormente se agregan los métodos en la clase. En este ejemplo se incluye el método findAll que se encargará de devolver todos los estudiantes almacenados en la base de datos. El método se anota con @GetMapping lo que significa que el verbo http que se usará para llamar al método es GET. La anotación @ResponseStatus(code = HttpStatus.OK) indica que el código HTTP que se enviará como respuesta será un 200. El método devuelve una colección de StudentDTO.

Con ayuda del servicio se llama al método getStudents. Este método devuelve una lista de StudentEntity. Esta lista se pasa como parámetro al mapper para convertirla en una lista de StudentDTO y retonarla.

package co.edu.uniandes.dse.bookstore.controllers;

import org.modelmapper.ModelMapper;
import org.modelmapper.TypeToken;


/**
 * Clase que implementa el recurso "students"
 *
 * @author ISIS2603
 */

@RestController
@RequestMapping("/students")
public class BookController {

        @Autowired
        private StudentService studentService;

        @Autowired
        private ModelMapper modelMapper;

        /**
         * Busca y devuelve todos los estudiantes que existen en la aplicacion.
         *
         * @return JSONArray {@link StudentDTO} - Los libros encontrados en la
         *         aplicación. Si no hay ninguno retorna una lista vacía.
         */
        @GetMapping
        @ResponseStatus(code = HttpStatus.OK)
        public List<StudentDTO> findAll() {
                List<StudentEntity> students = studentService.getStudents();
                return modelMapper.map(students, new TypeToken<List<StudentDTO>>() {
                }.getType());
        }
}

Desde el BootDashboard ejecute el back. Luego, en un navegador abra la consola de H2 ingresando la URL http://localhost:8080/api/h2-console.

En los parámetros de conexión asegúrese de que la JDBC URL sea "jdbc:h2:mem:bookstore". En User Name ingrese "sa" y en Password "password".

Luego ubique la tabla STUDENT_ENTITY y agregue dos instrucciones SQL para ingresar dos registros.

INSERT INTO STUDENT_ENTITY (ID, NAME) VALUES (100, 'Fernanda Gomez');

INSERT INTO STUDENT_ENTITY (ID, NAME) VALUES (101, 'María Rodríguez');

Haga clic en el botón Run y luego verifique con esta instrucción SQL si los datos quedaron correctamente almacenados:

SELECT * FROM STUDENT_ENTITY;

Abra Postman y haga un nuevo request de tipo GET a la URL http://localhost:8080/api/students.

Luego de click en SEND.

La salida deberá ser un arreglo JSON con los dos objetos que creamos en la base de datos cada uno representan un estudiante. Cada objeto JSON que representa un estudiante tiene dos atributos y su valor.

[

{

"id": 101,

"name": "Fernanda Gomez"

},

{

"id": 102,

"name": "Maria Rodriguez"

}

]

Para automatizar la prueba postman de tal forma que se puede sistemáticamente ejecutar desde el IDE y desde la herramienta de integración continua, debemos hacer un par de cambios.

Parametrizar la URL

En la instrucción anterior la URL es http://localhost:8080/api/students

En este caso, la intención es ejecutar en nuestra máquina local (localhost) la aplicación que fue desplegada desde el IDE en el puerto 8080.

Sin embargo, la aplicación puede desplegarse en otra máquina distinta y en otro puerto distinto. De hecho Jenkins ejecuta en otro puerto. Por esta razón debemos utilizar unas variables que reemplacen su valor.

La siguiente figura muestra un "environment" llamado Environment IT que contiene dos variables ip y puerto cuyos valores son localhost y 7070 respectivamente:

La siguiente figura muestra un "environment" llamado Environment Colecciones que contiene dos variables ip y puerto cuyos valores son localhost y 8080 respectivamente:

Cargar los environments en postman

Para tener acceso a la definición de estas variables, se deben cargar en postman los ambientes. Para esto: File / Import / Folder

En la raíz del proyecto está la carpeta collections que debemos seleccionar.

Las pruebas

Un test individual prueba un request, es decir un método GET, POST, PUT o DELETE con una url específica y un propósito específico.

Las pruebas se agrupan en collections postman.

Crear la colección

Debemos dar un nombre a la colección que debe indicar claramente el recurso o asociación entre recursos que se está probando. En la siguiente imagen, la colección la hemos llamado Student Tests ya que contendrá las pruebas del recurso Student.

Crear un test

Primero debemos seleccionar la colección donde queremos agregar el test y Add Request.

Luego, le damos un nombre a la prueba que indique claramente el objetivo. EN nuestro ejemplo la prueba se llama Obtener todos los estudiantes

Vamos a probar un GET y la url ahora tiene las variables que tenemos definidas en el ambiente "Entorno Colecciones".

Falta definir qué queremos probar. En este caso vamos a probar que después de la ejecución del GET el código de retorno Http es 200. Para esto, vamos a la pestaña tests y recuperamos lo que devuelve el request (responseBody). Tenemos que saber que allí viene un atributo llamado responseCode.code cuyo valor debe ser 200:

Ejecutar la colección

Para ejecutar en postman todas las pruebas en una colección, la seleccionamos y damos Run:

Exportar una colección

Para guardar la colección en nuestro proyecto, la seleccionamos, click derecho y export. Allí ubicamos la carpeta collections de nuestro proyecto y guardamos el archivo: