CRUD Spring Boot - Parte 2: Service Layer

⚙️ Parte 2: Service Layer

La lógica de negocio de nuestra aplicación

🎯 ¡Bienvenido a la Capa de Servicios!

En esta parte construiremos la lógica de negocio de nuestra aplicación. El Service Layer es el cerebro que procesa las peticiones del Controller y comunica con el Repository para manejar los datos.

🏗️ Enfoque en la Capa de Servicios

El Service Layer es el corazón de nuestra arquitectura, donde reside toda la lógica de negocio:

🎮 Controller

HTTP Requests

⚙️ Service

Lógica de Negocio

🗃️ Repository

Acceso a Datos

💾 H2 Database

Almacenamiento

📋Interface

¿Qué es? Un contrato que define QUÉ métodos debe tener nuestro servicio.

  • Define la firma de los métodos
  • No contiene implementación
  • Permite múltiples implementaciones
  • Facilita el testing con mocks

🔧Implementation

¿Qué es? La clase que implementa la interface y contiene la lógica real.

  • Contiene la lógica de negocio
  • Implementa todos los métodos de la interface
  • Se inyecta como dependencia
  • Usa @Service para ser detectada por Spring
📋 MascotaService.java - Interface
public interface MascotaService {
// Obtener todas las mascotas
List<MascotaEntity> getAllMascotas();
// Guardar una nueva mascota
MascotaEntity guardarMascota(MascotaEntity entity);
// Buscar mascota por ID
MascotaEntity findById(Long id);
// Eliminar mascota
void deleteMascota(Long id);
}
🔧 MascotaServiceImpl.java - Implementación
@Service
@AllArgsConstructor
@NoArgsConstructor(force = true)
@Data
public class MascotaServiceImpl implements MascotaService {
@Autowired
private final MascotaRepository repository;
@Override
public List<MascotaEntity> getAllMascotas() {
return repository.findAll();
}
@Override
public MascotaEntity guardarMascota(MascotaEntity entity) {
return repository.save(entity);
}
@Override
public MascotaEntity findById(Long id) {
return repository.findById(id).orElse(null);
}
@Override
public void deleteMascota(Long id) {
repository.deleteById(id);
}
}

1Anotaciones de Lombok

@AllArgsConstructor: Genera un constructor con todos los campos como parámetros.

@NoArgsConstructor(force = true): Genera un constructor sin parámetros, inicializando campos final con valores por defecto.

@Data: Genera automáticamente getters, setters, toString, equals y hashCode.

2@Service - Componente de Spring

Esta anotación le dice a Spring que esta clase es un componente de servicio:

  • Spring la detecta automáticamente durante el component scan
  • La registra como un bean en el contexto de aplicación
  • Permite que sea inyectada en otras clases
  • Es una especialización de @Component

3Inyección del Repository

El repository se inyecta como dependencia:

ControllerServiceRepositoryDatabase

Usamos @Autowired junto con final para garantizar que la dependencia no pueda cambiar después de la construcción.

4Métodos Override

@Override: Garantiza que estamos implementando correctamente los métodos de la interface.

Cada método delega la operación al repository correspondiente, manteniendo la separación de responsabilidades.

READgetAllMascotas()

Service: repository.findAll()

Obtiene todas las mascotas de la base de datos. Delega completamente al repository sin lógica adicional.

CREATEguardarMascota()

Service: repository.save(entity)

Guarda una nueva mascota o actualiza una existente. JPA decide automáticamente según el ID.

READfindById()

Service: repository.findById(id).orElse(null)

Busca por ID y retorna null si no encuentra la mascota. Maneja el Optional de JPA con orElse().

DELETEdeleteMascota()

Service: repository.deleteById(id)

Elimina una mascota por su ID. Si el ID no existe, JPA lanza una excepción que debería ser manejada.

⚠️ Áreas de Mejora en el Código Actual

Aunque el código funciona, hay algunas mejoras que podríamos implementar:

🔍 1. Manejo de Excepciones

El método findById() retorna null cuando no encuentra una mascota. Es mejor lanzar una excepción específica:

public MascotaEntity findById(Long id) {
    return repository.findById(id)
        .orElseThrow(() -> new EntityNotFoundException("Mascota no encontrada con ID: " + id));
}

📝 2. Validaciones de Negocio

Podríamos agregar validaciones antes de guardar:

public MascotaEntity guardarMascota(MascotaEntity entity) {
    if (entity.getNombre() == null || entity.getNombre().trim().isEmpty()) {
        throw new IllegalArgumentException("El nombre de la mascota es obligatorio");
    }
    return repository.save(entity);
}

🗂️3. DTOs vs Entities

En aplicaciones grandes, es recomendable usar DTOs (Data Transfer Objects) en lugar de exponer las entidades directamente.

✨ Mejores Prácticas del Service Layer

🎯Single Responsibility

Cada servicio debe tener una única responsabilidad bien definida. Nuestro MascotaService solo maneja operaciones de mascotas.

🔒Transacciones

Para operaciones complejas, usa @Transactional para garantizar la consistencia de los datos.

📋Interfaces

Siempre define interfaces para tus servicios. Facilita el testing y permite múltiples implementaciones.

🚫No Logic in Controllers

Los controllers deben ser "tontos". Toda la lógica de negocio debe estar en los servicios.

Inyección por Constructor

Prefiere la inyección por constructor sobre @Autowired en campos. Es más testeable y seguro.

📊Logging

Agrega logs informativos para operaciones importantes, especialmente en métodos de creación y eliminación.

🚀 Versión Mejorada del Service
@Service
@Transactional
@Slf4j // Para logging
public class MascotaServiceImpl implements MascotaService {
private final MascotaRepository repository;
// Constructor injection (sin @Autowired necesario en Spring 4.3+)
public MascotaServiceImpl(MascotaRepository repository) {
this.repository = repository;
}
@Override
@Transactional(readOnly = true)
public List<MascotaEntity> getAllMascotas() {
log.info("Obteniendo todas las mascotas");
return repository.findAll();
}
@Override
public MascotaEntity guardarMascota(MascotaEntity entity) {
validateMascota(entity);
log.info("Guardando mascota: {}", entity.getNombre());
return repository.save(entity);
}
@Override
@Transactional(readOnly = true)
public MascotaEntity findById(Long id) {
return repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(
"Mascota no encontrada con ID: " + id));
}
@Override
public void deleteMascota(Long id) {
if (!repository.existsById(id)) {
throw new EntityNotFoundException("No se puede eliminar. Mascota no encontrada: " + id);
}
log.info("Eliminando mascota con ID: {}", id);
repository.deleteById(id);
}
private void validateMascota(MascotaEntity entity) {
if (entity.getNombre() == null || entity.getNombre().trim().isEmpty()) {
throw new IllegalArgumentException("El nombre es obligatorio");
}
}
}

🔍 ¿Qué Agregamos en la Versión Mejorada?

1@Transactional

Agregamos control transaccional:

  • @Transactional: En la clase para operaciones de escritura
  • @Transactional(readOnly = true): Para operaciones de solo lectura (mejor rendimiento)

2Logging con @Slf4j

Agregamos logging para rastrear operaciones:

  • Log de operaciones importantes
  • Información útil para debugging
  • Trazabilidad en producción

3Manejo de Excepciones

Mejoramos el manejo de errores:

  • EntityNotFoundException en lugar de null
  • Validación antes de eliminar
  • Mensajes de error descriptivos

4Validaciones de Negocio

Agregamos validaciones específicas:

  • Método privado para validaciones
  • Verificación de campos obligatorios
  • Separación clara de responsabilidades

🎯 ¿Qué Viene en la Parte 3?

En la próxima entrega construiremos el Repository Layer que conecta con nuestra base de datos H2:

🗃️ Repository

JpaRepository

🔍 Queries

Custom Methods

📊 JPA

Annotations

💾 H2

Database Config

¡No te pierdas cómo conectamos todo con la base de datos y creamos queries personalizadas!

Extends ___

Politica privacidad

politica de privacidad

Term. condiciones

Terminos y condiciones del vlog elingaldo

Disclaimer

Descargo de responsabilidades.

Donación

Donaciones, sabes? el servidor no se mantiene del aire.