API, @RestController
Lógica de negocio
Ubicación en la arquitectura
Sección titulada «Ubicación en la arquitectura»Lógica, @Service
Persistencia, JpaRepository
@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.
Inyección de dependencias
Sección titulada «Inyección de dependencias»@Autowired hace que Spring inyecte el bean requerido, en lugar de construirlo con new.
@Servicepublic class BookService { @Autowired private BookRepository bookRepository;}
@RestControllerpublic class BookController { @Autowired private BookService bookService;}El flujo es Controller → Service → Repository. Esto desacopla las capas y facilita las pruebas y el mantenimiento.
Reglas de negocio
Sección titulada «Reglas de negocio»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:
- Consultar los datos existentes que necesita la regla.
- Evaluar la condición de negocio.
- Lanzar una excepción y cancelar la operación si la condición no se cumple.
- 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.
CompanyEntity entity
name = entity.getName()findByName(name)
→ [CompanyEntity] (ya existe)IllegalOperationException
repository.save() NO se ejecutaConsultar 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.
Implementación
Sección titulada «Implementación»@Transactionalpublic 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);}Paso a paso
Sección titulada «Paso a paso»- El service recibe una
CompanyEntity. findByNameconsulta las compañías que ya tienen ese nombre.- Si la lista no está vacía, se lanza
IllegalOperationException. - Si la lista está vacía, se ejecuta
repository.save(entity).
2. El ISBN del libro debe ser válido
Sección titulada «2. El ISBN del libro debe ser válido»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.
Implementación
Sección titulada «Implementación»@Transactionalpublic 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);}Paso a paso
Sección titulada «Paso a paso»- El service recibe una
BookEntity. - Comprueba si
isbnesnullo está vacío. - Si alguna condición se cumple, lanza la excepción y no guarda el libro.
- 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.
Implementación
Sección titulada «Implementación»// Validar coherencia de fechasif (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" ); }}Paso a paso
Sección titulada «Paso a paso»- Se obtienen la fecha de publicación del libro y la fecha de nacimiento del autor.
- La comparación solo se realiza cuando ambas fechas están presentes.
- Si la publicación es anterior al nacimiento, se lanza la excepción.
- 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.
Implementación
Sección titulada «Implementación»private static final Double MAX_SALARY = 50000000.0;
@Transactionalpublic EmployeeEntity createEmployee(EmployeeEntity emp) throws IllegalOperationException { if (emp.getSalary() > MAX_SALARY) { throw new IllegalOperationException( "Salario excede el máximo permitido" ); }
return repository.save(emp);}Paso a paso
Sección titulada «Paso a paso»- El service recibe la entidad del empleado.
- Compara
salaryconMAX_SALARY. - Si el valor es mayor, lanza la excepción y no persiste.
- 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 del repository
Sección titulada «Consulta del repository»// Consulta derivada por convenciónList<DepartmentEntity> findByCompanyIdAndName( Long companyId, String name);Implementación del service
Sección titulada «Implementación del service»@Transactionalpublic 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);}Paso a paso
Sección titulada «Paso a paso»- El service recibe el
companyIdy el departamento nuevo. - El repository busca el mismo nombre únicamente dentro de esa compañía.
- Si encuentra resultados, lanza la excepción y no ejecuta
save(). - 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.
Implementación
Sección titulada «Implementación»@Transactionalpublic 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);}Paso a paso
Sección titulada «Paso a paso»- Se comprueba que el libro incluya información de la editorial.
findByIdconsulta el identificador recibido.- Si el
Optionalestá vacío, se lanza la excepción y no se guarda el libro. - Si existe, se asigna la
EditorialEntityobtenida desde la base de datos. - Finalmente se ejecuta
bookRepository.save(bookEntity).