Saltar a contenido

Services en la capa de negocio

Los Service (clases anotadas con @Service en Spring) contienen la lógica de negocio de la aplicación y actúan como la capa intermedia entre los Controller (capa de presentación) y los Repository (capa de persistencia). Sus responsabilidades principales son:

  • Orquestar operaciones que involucran varias entidades o repositorios.
  • Mantener límites transaccionales (@Transactional).
  • Aplicar validaciones y reglas de negocio antes de persistir datos.
  • Gestionar excepciones y adaptar errores para la capa superior.
  • Mapear entidades a DTOs y viceversa (p. ej. con ModelMapper).

Separar esta lógica en Service mejora la testabilidad, la reutilización y mantiene los Controller ligeros.

Buenas prácticas

  • Marcar métodos que modifican datos con @Transactional.
  • Devolver/aceptar DTOs en las fronteras del Service para evitar exponer entidades JPA.
  • Mantener servicios cohesionados (cada Service debería tener responsabilidad clara).
  • Documentar efectos secundarios (persistencia, eventos publicados, llamadas externas).

Ejemplo: CRUD completo con ModelMapper

Supongamos una entidad Form y su FormDto. El Service manejará operaciones básicas: crear, leer, actualizar y borrar, usando ModelMapper para convertir entre entidad y DTO.

1) Entidad (resumen)

@Entity
public class Form {
    @Id
    private UUID id;
    private String title;
    private String description;
    private Status status;
    // getters/setters
}

2) DTO (resumen)

public class FormDto {
    private UUID id;
    private String title;
    private String description;
    // getters/setters
}

3) FormService — implementación CRUD

@Service
public class FormService {

    private final FormRepository formRepository;
    private final ModelMapper modelMapper;

    public FormService(FormRepository formRepository, ModelMapper modelMapper) {
        this.formRepository = formRepository;
        this.modelMapper = modelMapper;
    }

    // Leer por id
    @Transactional(readOnly = true)
    public FormDto get(UUID id) {
        Form form = formRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException("Form not found"));
        return modelMapper.map(form, FormDto.class);
    }

    // Leer todos
    @Transactional(readOnly = true)
    public List<FormDto> getAll() {
        List<Form> forms = formRepository.findAll();
        Type listType = new TypeToken<List<FormDto>>() {}.getType();
        return modelMapper.map(forms, listType);
    }

    // Crear
    @Transactional
    public FormDto create(FormDto dto) {
        Form form = modelMapper.map(dto, Form.class);
        if (form.getId() == null) {
            form.setId(UUID.randomUUID());
        }
        form.setStatus(Status.ACTIVE);
        Form saved = formRepository.save(form);
        return modelMapper.map(saved, FormDto.class);
    }

    // Actualizar
    @Transactional
    public FormDto update(UUID id, FormDto dto) {
        Form existing = formRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException("Form not found"));
        // Mapear sobre la entidad existente para conservar campos no presentes en el DTO
        modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());
        modelMapper.map(dto, existing);
        Form saved = formRepository.save(existing);
        return modelMapper.map(saved, FormDto.class);
    }

    // Borrar
    @Transactional
    public void delete(UUID id) {
        if (!formRepository.existsById(id)) {
            throw new EntityNotFoundException("Form not found");
        }
        formRepository.deleteById(id);
    }
}