¿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

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 recurso Book, por tanto se debe crear una clase BookDTO. Esta clase contiene los atributos de la clase BookEntity 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 java.util.Date;

import lombok.Getter;
import lombok.Setter;

@Data
public class BookDTO {
        private Long id;
        private String name;
        private String isbn;
        private String image;
        private Date publishingDate;
        private String description;
        private EditorialDTO editorial;
}

Tenga en cuenta que en la clase BookEntity se tienen asociaciones con cardinalidad muchos a las entidades ReviewEntity y AuthorEntity. Por tanto, en el API se debe crear una clase que incluya la información de esas asociaciones. En este caso la clase se denominará BookDetailDTO. Es una clase que hereda de BookDetail e incluye las listas que representan las asociaciones.

El siguiente es el contenido de la clase BookDetailDTO.java:

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

import java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import lombok.Data;

@Data
public class BookDetailDTO extends BookDTO{
        private List<ReviewDTO> reviews = new ArrayList<>();
        private List<AuthorDTO> authors = new ArrayList<>();
}

Los controladores son las clases que se encargan de capturar las peticiones del cliente y de enviar las respuestas. Los controladores tendrán el nombre del recurso junto con el sufijo Controller.

Todos los controladores estarán en la carpeta controllers.

A continuación tenemos el código de la clase BookController.java:

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

        @Autowired
        private BookService bookService;

        @Autowired
        private ModelMapper modelMapper;
... 

La clase incluye varias anotaciones:

Luego, en la implementación de la clase se añaden dos inyecciones de dependencia, una para el servicio (BookService) 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.

Este método se encarga de devolver todos los libros 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 BookDTO.

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

@GetMapping
@ResponseStatus(code = HttpStatus.OK)
public List<BookDetailDTO> findAll() {
        List<BookEntity> books = bookService.getBooks();
        return modelMapper.map(books, new TypeToken<List<BookDetailDTO>>() {
        }.getType());
}

Este método se encarga de devolver un libro en particular. El método se anota con @GetMapping(value="/{id}"), lo que significa que el verbo http que se usará para llamar al método es GET. También se indica que se capturará como parte de la URL de la petición el id del libro que se quiere consultar. 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 instancia de BookDetailDTO.

El id del libro pasa como parámetro a la variable id del método findOne. Con ayuda del servicio se llama al método getBook. Este método devuelve una instancia de BookEntity. Este objeto se pasa como parámetro al mapper para convertirlo en una instancia de BookDetailDTO y retonarla.

@GetMapping(value = "/{id}")
@ResponseStatus(code = HttpStatus.OK)
public BookDetailDTO findOne(@PathVariable Long id) throws EntityNotFoundException {
        BookEntity bookEntity = bookService.getBook(id);
        return modelMapper.map(bookEntity, BookDetailDTO.class);
}

Este método se encarga de crear un libro. El método se anota con @PostMapping, lo que significa que el verbo http que se usará para llamar al método es POST. La anotación @ResponseStatus(code = HttpStatus.CREATED) indica que el código HTTP que se enviará como respuesta será un 201. El método devuelve una instancia de BookDTO.

Los datos del nuevo libro se pasan como parámetro a la variable bookDTO del método create. Con ayuda del servicio se llama al método createBook. Este método guarda el libro y retorna la entidad a la que se le ha agregado el id generado por la base de datos. Finalmente el mapper convierte la entidad en DTO para ser retornada al cliente.

@PostMapping
@ResponseStatus(code = HttpStatus.CREATED)
public BookDTO create(@RequestBody BookDTO bookDTO) throws IllegalOperationException, EntityNotFoundException {
        BookEntity bookEntity = bookService.createBook(modelMapper.map(bookDTO, BookEntity.class));
        return modelMapper.map(bookEntity, BookDTO.class);
}

Este método se encarga de actualizar la información de un libro. El método se anota con @PutMapping, lo que significa que el verbo http que se usará para llamar al método es PUT. En la URL de la petición se captura el id del libro que se quiere actualizar.

La anotación @ResponseStatus(code = HttpStatus.OK) indica que el código HTTP que se enviará como respuesta será un 202. El método devuelve una instancia de BookDTO.

Los datos del libro se pasan como parámetro a la variable bookDTO del método update junto con el id del libro. Con ayuda del servicio se llama al método updateBook. Este método actualiza el libro y retorna la entidad con los datos actualizados. Finalmente el mapper convierte la entidad en DTO para ser retornada al cliente.

@PutMapping(value = "/{id}")
@ResponseStatus(code = HttpStatus.OK)
public BookDTO update(@PathVariable Long id, @RequestBody BookDTO bookDTO)
                        throws EntityNotFoundException, IllegalOperationException {
        BookEntity bookEntity = bookService.updateBook(id, modelMapper.map(bookDTO, BookEntity.class));
        return modelMapper.map(bookEntity, BookDTO.class);
}

Este método se encarga de borrar un libro. El método se anota con @DeleteMapping, lo que significa que el verbo http que se usará para llamar al método es DELETE. En la URL de la petición se captura el id del libro que se quiere eliminar.

La anotación @ResponseStatus(code = HttpStatus.NO_CONTENT) indica que el código HTTP que se enviará como respuesta será un 204. El método no tiene retorno.

El id del libro se pasa como parámetro del método update. Con ayuda del servicio se llama al método deleteBook. Este método borra el libro.

@DeleteMapping(value = "/{id}")
@ResponseStatus(code = HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) throws EntityNotFoundException, IllegalOperationException {
        bookService.deleteBook(id);
}