Arquitectura del Backend

Arquitectura del Backend

El proyecto ATC-API está estructurado bajo los principios de Clean Architecture combinados con Vertical Slicing (separación por features).


Equivalencias con el Portal Anterior

Para los desarrolladores familiarizados con la estructura anterior del portal, aquí está el mapa de migración conceptual:

Portal ViejoATC-APICapa
Archivos .ejbdomain/modelDomain
Archivos .facade (solo consultas/queries)RepositoryInfrastructure / Persistence
Archivos .facade (solo lógica de negocio)ServiceApplication / Domain

Estructura del Proyecto

El código se organiza en las siguientes capas dentro de src/main/java/:


1. Inyección de Dependencias

Está estrictamente prohibido el uso de @Autowired, @EJB, @ManagedProperty o @Inject directamente sobre propiedades.

Se debe utilizar inyección por constructor mediante Lombok declarando las dependencias como private final:

// ✅ CORRECTO: private final + @RequiredArgsConstructor
@Service
@RequiredArgsConstructor
public class UsuarioService {
    private final WebUsuarioRepository repository;
    private final RolRepository rolRepository;
    private final PasswordEncoder passwordEncoder;
    private final TipoUsuarioRepository tipoUsuarioRepository;
}
🚫

@Autowired sobre campos está prohibido. Además de violar principios de diseño, dificulta las pruebas unitarias al impedir la inyección manual de dependencias.


2. Entidades JPA y Lombok

Para mantener código limpio y control estricto sobre la base de datos se aplican las siguientes reglas en todas las entidades:

CascadeType — Uso cero

🚫

El uso de CascadeType está estrictamente prohibido. Ningún tipo (ALL, PERSIST, REMOVE, etc.) debe aparecer en las entidades. Todas las operaciones de persistencia deben ser explícitas para mantener el control total sobre las transacciones.

Anotaciones Lombok recomendadas

A nivel de clase se recomienda el uso de: @Getter, @Setter, @NoArgsConstructor, @AllArgsConstructor y @Builder para reducir el boilerplate, pero no es obligatorio.

Secuencias personalizadas

Si la entidad utiliza una secuencia, no se usa la generación por defecto de JPA. Es obligatorio el decorador personalizado @AssignedOrSequence.

@Entity
@Table(name = "web_usuario")
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
public class WebUsuario {
 
    @Id
    @AssignedOrSequence(name = "web_usuario_seq")
    private Integer id;
}

3. Caché de Segundo Nivel (Hibernate)

La estrategia de concurrencia para @Cacheable depende de si los datos van a mutar:

EstrategiaCuándo usar
CacheConcurrencyStrategy.READ_ONLYExclusivo para catálogos o registros que nunca cambian
CacheConcurrencyStrategy.READ_WRITEPara entidades transaccionales que sufren actualizaciones constantes

4. Patrón Result y Manejo de Respuestas

En lugar de lanzar excepciones para controlar el flujo lógico, todo el manejo de respuestas se centraliza con el patrón Result<T>, junto con ErrorCodes y ApiResponse.

public Result<WebUsuario> create(WebUsuario usuario) {
    if (repository.existsByUsuario(usuario.getUsuario())) {
        return Result.fail(WebUsuarioErrorCode.ALREADY_EXISTS);
    }
 
    usuario.setPassword(passwordEncoder.encode(usuario.getPassword()));
 
    var defaultRol = rolRepository.findById(1);
    if (defaultRol.isEmpty()) {
        return Result.fail(WebRolErrorCode.NOT_FOUND);
    }
 
    usuario.setRol(defaultRol.get());
    usuario.setLocale("ES");
    usuario.setActivo(true);
 
    var tipoUsuario = tipoUsuarioRepository.findById((short) 1);
    if (tipoUsuario.isEmpty()) {
        return Result.fail(TipoUsuarioErrorCode.NOT_FOUND);
    }
 
    usuario.setTipoUsuario(tipoUsuario.get());
    usuario.setCreatedAt(LocalDateTime.now());
    usuario.setFechaPassword(LocalDate.now().plusMonths(3));
    usuario.setUsuarioIngreso(repository.findById(1).orElseThrow());
 
    return Result.ok(repository.save(usuario));
}

Este ejemplo ilustra el manejo estándar con Result. Los escenarios complejos que requieran @Transactional y RollbackException tendrán su propia documentación específica más adelante.


5. Logs del Sistema

Para emitir logs se utiliza la anotación de Lombok @Slf4j a nivel de clase, la cual inyecta automáticamente la variable log:

@Slf4j
@Service
@RequiredArgsConstructor
public class UsuarioService {
 
    public void ejemplo() {
        log.info("Usuario creado correctamente");
        log.warn("Intento de acceso fallido");
        log.error("Error al procesar la solicitud", e);
    }
}