Controllers (capa de presentación / API)¶
Los Controller son la puerta de entrada de las peticiones HTTP en una aplicación Spring. Se encargan de:
- Recibir y validar las peticiones del cliente (HTTP).
- Convertir payloads a objetos (por ejemplo, DTOs anotados con
@RequestBody). - Llamar a la capa de
Servicepara ejecutar la lógica de negocio. - Devolver respuestas HTTP (códigos, headers y cuerpos) al cliente.
En Spring MVC hay dos anotaciones principales:
@Controller: pensado para devolver vistas o manejar respuestas HTML.@RestController: shortcut para@Controller+@ResponseBody, usado en APIs REST y devuelve JSON/XML directamente.
Principales componentes y anotaciones usadas en controllers:
@RequestMapping,@GetMapping,@PostMapping,@PutMapping,@DeleteMapping: mapeo de rutas y verbos HTTP.@PathVariable: extrae variables desde la URL.@RequestParam: parámetros de consulta (query params).@RequestBody: deserializa el body JSON a un objeto Java.@Valid: activa validación JSR-380 (Bean Validation) sobre el DTO.ResponseEntity<T>: permite controlar el código de respuesta y headers.@ResponseStatus: definir el status de respuesta en métodos o excepciones.@ControllerAdvice/@ExceptionHandler: manejo centralizado de errores.
Buenas prácticas:
- Exponer/aceptar DTOs en los controladores, no entidades JPA.
- Mantener los métodos del controlador ligeros: delegar la lógica al
Service. - Validar las entradas con
@Validy manejar errores con@ControllerAdvice. - Usar
ResponseEntitypara devolver códigos HTTP apropiados (201 para creación, 204 para borrado sin contenido, 404 para no encontrado, etc.).
Ejemplo práctico: FormController (basado en FormService)¶
Este ejemplo muestra un @RestController completo que expone un CRUD sobre FormDto y usa el FormService implementado anteriormente.
@RestController
@RequestMapping("/api/forms")
public class FormController {
private final FormService service;
public FormController(FormService service) {
this.service = service;
}
@GetMapping("/{id}")
public ResponseEntity<FormDto> get(@PathVariable UUID id) {
FormDto dto = service.get(id);
return ResponseEntity.ok(dto);
}
@GetMapping
public ResponseEntity<List<FormDto>> getAll() {
List<FormDto> list = service.getAll();
return ResponseEntity.ok(list);
}
@PostMapping
public ResponseEntity<FormDto> create(@Valid @RequestBody FormDto dto) {
FormDto created = service.create(dto);
// Devolver 201 Created y opcionalmente Location header
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<FormDto> update(@PathVariable UUID id, @Valid @RequestBody FormDto dto) {
FormDto updated = service.update(id, dto);
return ResponseEntity.ok(updated);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable UUID id) {
service.delete(id);
return ResponseEntity.noContent().build();
}
}
Manejo de errores y validación¶
Es recomendable centralizar el manejo de excepciones con @ControllerAdvice. Ejemplo simplificado:
@ControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<ErrorDto> handleNotFound(EntityNotFoundException ex) {
ErrorDto error = new ErrorDto("NOT_FOUND", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorDto> handleValidation(MethodArgumentNotValidException ex) {
// construir mensajes a partir de bindingResult
ErrorDto error = new ErrorDto("VALIDATION_ERROR", "Campos inválidos");
return ResponseEntity.badRequest().body(error);
}
}
Notas de diseño¶
- Paginación: para colecciones grandes, aceptar
pageysizecomo@RequestParamy devolverPage<FormDto>o un wrapper con metadatos. - Seguridad: proteger endpoints con
@PreAuthorizeo filtros (JWT, sesiones, etc.). - HATEOAS: si se requiere hipermedia, construir enlaces (
Link) en los DTOs. - Pruebas: probar controladores con
@WebMvcTestyMockMvc, usando mocks para elService.
¿Quieres que añada el @ControllerAdvice completo con el ErrorDto y ejemplos de tests para este FormController?