El problema con la estructura por tipo
Organizar el proyecto poniendo todos los controladores juntos, todos los servicios juntos, todos los repositorios juntos funciona en proyectos pequeños. En proyectos reales, navegar para entender qué hace una sola feature implica saltar entre carpetas. La cohesión está fragmentada.
La estructura que uso
Prefiero una estructura que refleje la separación de responsabilidades y sea clara para cualquier miembro del equipo:
src/
└── com/dominio/app/
├── config/ # Configuraciones (Security, CORS, Swagger)
├── controllers/ # Capa de entrada HTTP
├── services/
│ ├── interfaces/ # Contratos del dominio
│ └── impl/ # Implementaciones
├── repositories/ # Acceso a datos (JPA)
├── models/
│ ├── entities/ # Entidades JPA
│ ├── dtos/
│ │ ├── request/ # DTOs de entrada
│ │ └── response/ # DTOs de salida
│ └── enums/
└── exceptions/ # Manejo global de erroresPor qué importa la separación service/impl
Separar interfaz e implementación puede ser útil cuando el dominio necesita más de una implementación o pruebas aisladas. No es una regla universal; en ese contexto permite:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService; // interfaz, no impl
@GetMapping("/{id}")
public ResponseEntity<UserResponseDto> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
}El manejo global de excepciones
Un @ControllerAdvice centralizado es no-negociable. Sin él, cada controlador maneja errores de manera diferente, el cliente recibe formatos inconsistentes y el código se llena de try-catch redundantes.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("NOT_FOUND", ex.getMessage()));
}
}Conclusión
La estructura de un proyecto es la primera forma en que comunicas tus intenciones arquitectónicas al equipo. Una buena estructura te da navegación intuitiva, separación de responsabilidades clara y base para escalar sin refactorizar todo.
