Curso de Spring Boot | Implementación de servicios 1

Curso de Spring Boot
Implementación de servicios

Curso de Spring Boot | Implementación de servicios 2

Aunque no es indispensable, la implementación de servicios en un proyecto de SpringBoot es habitual y tiene las siguientes ventajas:

  • Separación de responsabilidades: Mantener la lógica de negocio separada de la capa de controladores.
  • Reutilización: Facilitar la reutilización de la lógica de negocio en diferentes partes de la aplicación.
  • Mantenimiento: Hacer que el código sea más fácil de mantener y probar.

En SpringBoot lo servicios son clases que generalmente son llamadas desde los controladores, aunque también pueden ser llamadas desde otros servicios o componentes.

A menudo, los servicios se encargan de mapear los DTO a nuestras entidades del modelo y viceversa.

package com.app.controllers;

import com.app.services.BookServices;

@RestController
@CrossOrigin
@RequestMapping("/locations")
public class BookController {
    @Autowired
    LocationServices locationServices;

 @PostMapping
    public ResponseEntity<?> saveBook(@RequestBody LocationTagDTO locationTagDTO) {
        try {
            BookDTO saveLocation = bookServices.createBook(bookDTO);
            return ResponseEntity.status(200).body(saveLocation);
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseEntity.status(500).body("Error al crear la ubicación " + e.getMessage());
        }
    }
package com.app.services.impl
...
@Service
public class BookServicesImpl implements BookServices {

    @Autowired
    private BookRepository bookRepository;

    @Override
    public BookDTO createBook(BookDTO bookDTO) {
        Book book = new Book();
        book.setTitle(bookDTO.getTitle());

        bookRepository.save(location);

        // Mapear la entidad de vuelta a un DTO para devolverlo (best practices)
        BookDTO bookDTO = new BookDTO();
        bookDTO.setTitle(book.getTitle());
        return bookDTO;
    }
}

Usamos la siguiente interfaz para definir un contrato que la clase BookServicesImpl debe cumplir. Esto permite:

  1. Abstracción: Ocultar los detalles de implementación y exponer solo los métodos necesarios.
  2. Flexibilidad: Facilitar el cambio de implementación sin afectar a las clases que dependen de la interfaz.
  3. Testabilidad: Permitir el uso de mocks o stubs en pruebas unitarias.

com.pablomonteserin.prueba.services

public interface BookServices {
    BookDTO createLocation (BookDTO bookDTO);
}

Mapeos automáticos

Ten en cuenta que si las propiedades del objeto usuario que enviamos para guardar en la base de datos no coinciden con el objeto Usuario que tenemos como parámetro de entrada de la función de registro, nos dará un error 401 (acceso no autorizado), a pesar de que el problema es que los datos mandados no coinciden y no que la url esté protegida.

Para evitar el problema anterior, podemos usar las clases DTO, que son clases con propiedades análogas a las del modelo (UserDTO es análoga a User) pero que tienen únicamente las propiedades que vamos a enviar o recibir del front.

Luego para mapear las propiedades del DTO a una Entity usaremos los siguientes servicios, que utilizan las siguientes dependencias que habría que añadir al pon.xml.

<!-- MapStruct-->
<dependency>
	<groupId>org.mapstruct</groupId>
	<artifactId>mapstruct</artifactId>
	<version>1.6.3</version>
</dependency>
<dependency>
	<groupId>org.mapstruct</groupId>
	<artifactId>mapstruct-processor</artifactId>
	<version>1.6.3</version>
</dependency>
...
 <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.11.0</version>
      <configuration>
        <source>21</source>
        <target>21</target>
        <annotationProcessorPaths>
          <path>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.6.2</version>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
package com.app.mapper;
...
@Mapper(componentModel = "spring")
public abstract class UserMapper {

    @Autowired
    protected PasswordEncoder passwordEncoder;

    @Mapping(target = "password", expression = "java(passwordEncoder.encode(source.getPassword()))")
    public abstract  User fromUserDTO(UserDTO source);

    @Mapping(target = "password", ignore = true)
    @Mapping(source = "owner.id", target = "ownerId")
    public abstract  UserDTO fromUser(User source);
}