Saltar a contenido

Semana 9 - Servicios en Spring Boot (Conceptos Básicos)

Resumen ejecutivo

Los servicios en Spring Boot contienen la lógica de negocio de la aplicación. Actúan como una capa intermedia entre controladores y repositorios, implementando reglas de negocio y validaciones.


1. ¿Qué es un servicio?

Definición

Un servicio es una clase anotada con @Service que encapsula la lógica de negocio. Procesa datos, aplica reglas de negocio y prepara información para los controladores.

1.1 Responsabilidades principales

Responsabilidad Descripción Ejemplo
Lógica de negocio Reglas específicas del dominio Calcular descuentos
Transformación Convierte DTOs ↔ Entidades Mapeo de datos
Validación Validaciones de negocio Verificar stock
Transacciones Consistencia de datos Operaciones atómicas

1.2 Ventajas

  • Separación de responsabilidades: Controladores manejan HTTP, servicios la lógica
  • Reutilización: Un servicio puede usarse en múltiples controladores
  • Testabilidad: Fácil de probar con mocks
  • Mantenibilidad: Cambios centralizados

2. Estructura básica

package com.ejemplo.demo.service;

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

@Service                    // Marca la clase como servicio
@Transactional             // Manejo automático de transacciones
public class ProductoService {

    @Autowired             // Inyección de dependencias
    private ProductoRepository productoRepository;

    // Métodos con lógica de negocio
    public ProductoDTO crearProducto(CrearProductoDTO dto) {
        // Lógica aquí
    }
}

2.1 Anotaciones importantes

@Service

  • Marca la clase como servicio
  • Spring la detecta automáticamente
  • Especialización de @Component

@Transactional

  • Manejo automático de transacciones
  • Rollback automático en caso de error
  • Se puede usar a nivel de clase o método

3. Implementación práctica

3.1 Entidades y DTOs

// Entidad
@Entity
@Table(name = "productos")
@NoArgsConstructor
@AllArgsConstructor
public class Producto {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String nombre;

    private String descripcion;

    @Column(nullable = false)
    private BigDecimal precio;

    @Column(nullable = false)
    private Integer stock;

    @Column(nullable = false)
    private Boolean activo = true;

    // getters y setters...
}

// DTO para transferencia
@Data
public class ProductoDTO {
    private Long id;
    private String nombre;
    private String descripcion;
    private BigDecimal precio;
    private Integer stock;
    private Boolean activo;
}

// DTO para creación
@Data
public class CrearProductoDTO {
    @NotBlank(message = "El nombre es obligatorio")
    private String nombre;

    private String descripcion;

    @NotNull(message = "El precio es obligatorio")
    @DecimalMin(value = "0.01", message = "El precio debe ser mayor a 0")
    private BigDecimal precio;

    @NotNull(message = "El stock es obligatorio")
    @Min(value = 0, message = "El stock no puede ser negativo")
    private Integer stock;
}

3.2 Repositorio

@Repository
public interface ProductoRepository extends JpaRepository<Producto, Long> {
    List<Producto> findByActivo(Boolean activo);
    List<Producto> findByNombreContainingIgnoreCase(String nombre);
}

3.3 Servicio básico

@Service
@Transactional
public class ProductoService {

    @Autowired
    private ProductoRepository productoRepository;

    // Crear producto
    public ProductoDTO crearProducto(CrearProductoDTO dto) {
        // 1. Validar datos
        if (dto.getNombre() == null || dto.getNombre().trim().isEmpty()) {
            throw new IllegalArgumentException("El nombre es obligatorio");
        }

        // 2. Convertir DTO a entidad
        Producto producto = new Producto();
        producto.setNombre(dto.getNombre());
        producto.setDescripcion(dto.getDescripcion());
        producto.setPrecio(dto.getPrecio());
        producto.setStock(dto.getStock());
        producto.setActivo(true);

        // 3. Guardar
        Producto guardado = productoRepository.save(producto);

        // 4. Convertir a DTO y retornar
        return convertirADTO(guardado);
    }

    // Obtener por ID
    @Transactional(readOnly = true)
    public ProductoDTO obtenerPorId(Long id) {
        Producto producto = productoRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Producto no encontrado"));
        return convertirADTO(producto);
    }

    // Listar productos activos
    @Transactional(readOnly = true)
    public List<ProductoDTO> obtenerActivos() {
        List<Producto> productos = productoRepository.findByActivo(true);
        return productos.stream()
                .map(this::convertirADTO)
                .collect(Collectors.toList());
    }

    // Buscar por nombre
    @Transactional(readOnly = true)
    public List<ProductoDTO> buscarPorNombre(String nombre) {
        List<Producto> productos = productoRepository.findByNombreContainingIgnoreCase(nombre);
        return productos.stream()
                .map(this::convertirADTO)
                .collect(Collectors.toList());
    }

    // Método auxiliar para convertir entidad a DTO
    private ProductoDTO convertirADTO(Producto producto) {
        ProductoDTO dto = new ProductoDTO();
        dto.setId(producto.getId());
        dto.setNombre(producto.getNombre());
        dto.setDescripcion(producto.getDescripcion());
        dto.setPrecio(producto.getPrecio());
        dto.setStock(producto.getStock());
        dto.setActivo(producto.getActivo());
        return dto;
    }
}

```


4. Ejercicios prácticos

Ejercicio 1: Crear un servicio básico

Crea un servicio UsuarioService con métodos para: - Crear usuario - Buscar por email - Listar usuarios activos

Ejercicio 2: Agregar validaciones

Añade validaciones al ProductoService para: - Verificar que el precio sea positivo - Validar que el stock no sea negativo - Comprobar que el nombre no esté vacío

Ejercicio 3: Método de negocio

Implementa un método calcularDescuento() que: - Aplique 10% de descuento si el stock > 100 - Aplique 5% de descuento si el stock > 50 - Sin descuento si el stock ≤ 50


5. Puntos clave

Recuerda

  • Los servicios contienen la lógica de negocio
  • Usa @Service para marcar la clase
  • @Transactional maneja las transacciones automáticamente
  • Convierte siempre entre DTOs y entidades
  • Valida los datos antes de procesarlos