Ir al contenido

Lógica de negocio

API, @RestController

Trabaja con DTO y JSON. Recibe peticiones y no accede directamente a la base de datos.

Lógica, @Service

Valida reglas e invoca persistencia. Trabaja exclusivamente con entidades, sin DTO, JSON ni HTTP.

Persistencia, JpaRepository

Expone CRUD y consultas personalizadas; Spring genera la implementación.

@Service registra la clase como bean de Spring. Los nombres habituales son BookService, AuthorService o EditorialService.

La capa de lógica recibe y retorna objetos @Entity; los controllers realizan la conversión entre Entity y DTO. Sus responsabilidades son validar antes de persistir, coordinar API y persistencia, comunicarse con servicios externos y lanzar excepciones cuando una regla no se cumple.

@Autowired hace que Spring inyecte el bean requerido, en lugar de construirlo con new.

@Service
public class BookService {
@Autowired
private BookRepository bookRepository;
}
@RestController
public class BookController {
@Autowired
private BookService bookService;
}

El flujo es Controller → Service → Repository. Esto desacopla las capas y facilita las pruebas y el mantenimiento.

La capa de lógica valida las condiciones que deben cumplirse antes de persistir o modificar datos. Aunque cada regla consulta información diferente, todas siguen el mismo patrón:

  1. Consultar los datos existentes que necesita la regla.
  2. Evaluar la condición de negocio.
  3. Lanzar una excepción y cancelar la operación si la condición no se cumple.
  4. Persistir solamente cuando todas las validaciones terminan correctamente.

Simula una validación de negocio

Las reglas cambian, pero todas recorren el patrón consultar, evaluar, detener o persistir.

Entrada al servicioCompanyEntity entity name = entity.getName()
Estado previo de la BDfindByName(name) → [CompanyEntity] (ya existe)
Resultado esperadoIllegalOperationException repository.save() NO se ejecuta
RepositoryPaso 1 de 4
Consultar por nombre

Se busca si ya existe una empresa con el nombre recibido.

repository.findByName(entity.getName())

1. No puede haber dos compañías con el mismo nombre

Sección titulada «1. No puede haber dos compañías con el mismo nombre»

Antes de crear o actualizar una compañía, el service consulta la base de datos para verificar si ya existe otra con el mismo nombre.

@Transactional
public CompanyEntity createCompany(CompanyEntity entity)
throws IllegalOperationException {
// 1. Consultar la persistencia
List<CompanyEntity> alreadyExist =
repository.findByName(entity.getName());
// 2. Validar la regla de negocio
if (!alreadyExist.isEmpty()) {
throw new IllegalOperationException(
"Ya existe una compañía con ese nombre"
);
}
// 3. Si pasa, persistir
return repository.save(entity);
}
  1. El service recibe una CompanyEntity.
  2. findByName consulta las compañías que ya tienen ese nombre.
  3. Si la lista no está vacía, se lanza IllegalOperationException.
  4. Si la lista está vacía, se ejecuta repository.save(entity).

Antes de crear un libro se verifica que el ISBN no sea null ni una cadena vacía. Esta regla valida directamente un atributo de la entidad y no requiere consultar la base de datos.

@Transactional
public BookEntity createBook(BookEntity book)
throws IllegalOperationException {
// Validar que el ISBN no sea vacío
if (book.getIsbn() == null
|| book.getIsbn().isEmpty()) {
throw new IllegalOperationException(
"El ISBN no puede ser vacío"
);
}
return repository.save(book);
}
  1. El service recibe una BookEntity.
  2. Comprueba si isbn es null o está vacío.
  3. Si alguna condición se cumple, lanza la excepción y no guarda el libro.
  4. Si el ISBN tiene contenido, ejecuta repository.save(book).

3. La publicación debe ser posterior al nacimiento del autor

Sección titulada «3. La publicación debe ser posterior al nacimiento del autor»

Esta regla compara datos de dos entidades relacionadas: el libro y su autor.

// Validar coherencia de fechas
if (book.getPublishDate() != null
& author.getBirthDate() != null) {
if (book.getPublishDate()
.before(author.getBirthDate())) {
throw new IllegalOperationException(
"La fecha de publicación no puede "
+ "ser anterior al nacimiento del autor"
);
}
}
  1. Se obtienen la fecha de publicación del libro y la fecha de nacimiento del autor.
  2. La comparación solo se realiza cuando ambas fechas están presentes.
  3. Si la publicación es anterior al nacimiento, se lanza la excepción.
  4. Si las fechas son coherentes, el flujo puede continuar hacia la persistencia.

4. Un empleado no puede superar el salario máximo

Sección titulada «4. Un empleado no puede superar el salario máximo»

El salario de un empleado no puede ser superior a 50 millones. El límite se representa mediante una constante de la clase.

private static final Double MAX_SALARY = 50000000.0;
@Transactional
public EmployeeEntity createEmployee(EmployeeEntity emp)
throws IllegalOperationException {
if (emp.getSalary() > MAX_SALARY) {
throw new IllegalOperationException(
"Salario excede el máximo permitido"
);
}
return repository.save(emp);
}
  1. El service recibe la entidad del empleado.
  2. Compara salary con MAX_SALARY.
  3. Si el valor es mayor, lanza la excepción y no persiste.
  4. Si está dentro del límite, guarda la entidad.

5. El nombre del departamento es único dentro de su compañía

Sección titulada «5. El nombre del departamento es único dentro de su compañía»

Esta regla es contextual: dos departamentos pueden compartir nombre si pertenecen a compañías diferentes. Por eso la consulta combina el identificador de la compañía y el nombre.

// Consulta derivada por convención
List<DepartmentEntity> findByCompanyIdAndName(
Long companyId,
String name
);
@Transactional
public DepartmentEntity createDepartment(
Long companyId,
DepartmentEntity dept)
throws IllegalOperationException {
List<DepartmentEntity> existing =
deptRepo.findByCompanyIdAndName(
companyId,
dept.getName()
);
if (!existing.isEmpty()) {
throw new IllegalOperationException(
"Ya existe un departamento con ese nombre "
+ "en esta compañía"
);
}
return deptRepo.save(dept);
}
  1. El service recibe el companyId y el departamento nuevo.
  2. El repository busca el mismo nombre únicamente dentro de esa compañía.
  3. Si encuentra resultados, lanza la excepción y no ejecuta save().
  4. Si no encuentra resultados, persiste el departamento.

6. La editorial debe existir al crear un libro

Sección titulada «6. La editorial debe existir al crear un libro»

El libro debe estar asociado con una editorial válida. No basta con recibir un identificador: el service comprueba que ese registro realmente exista antes de guardar el libro.

@Transactional
public BookEntity createBook(BookEntity bookEntity)
throws IllegalBusinessLogicException {
// 1. Verificar que venga la información
if (bookEntity.getEditorial() == null) {
throw new IllegalBusinessLogicException(
"La editorial no es válida"
);
}
// 2. Buscar la editorial en la base de datos
Optional<EditorialEntity> editorial =
editorialRepository.findById(
bookEntity.getEditorial().getId()
);
// 3. Validar existencia
if (editorial.isEmpty()) {
throw new IllegalBusinessLogicException(
"La editorial no existe"
);
}
// 4. Asignar la entidad persistida y guardar
bookEntity.setEditorial(editorial.get());
return bookRepository.save(bookEntity);
}
  1. Se comprueba que el libro incluya información de la editorial.
  2. findById consulta el identificador recibido.
  3. Si el Optional está vacío, se lanza la excepción y no se guarda el libro.
  4. Si existe, se asigna la EditorialEntity obtenida desde la base de datos.
  5. Finalmente se ejecuta bookRepository.save(bookEntity).
1. ¿Qué anotación identifica una clase de lógica de negocio?
2. ¿Qué hace @Autowired?
3. ¿Con qué objetos trabaja la capa de servicios en este modelo?
4. ¿Qué debe ocurrir al violarse una regla de negocio?
5. ¿Cómo accede un servicio a la base de datos?