Saltar a contenido

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 Service para 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 @Valid y manejar errores con @ControllerAdvice.
  • Usar ResponseEntity para 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 page y size como @RequestParam y devolver Page<FormDto> o un wrapper con metadatos.
  • Seguridad: proteger endpoints con @PreAuthorize o filtros (JWT, sesiones, etc.).
  • HATEOAS: si se requiere hipermedia, construir enlaces (Link) en los DTOs.
  • Pruebas: probar controladores con @WebMvcTest y MockMvc, usando mocks para el Service.

¿Quieres que añada el @ControllerAdvice completo con el ErrorDto y ejemplos de tests para este FormController?