Semana 3 - Patrones de Diseño: MVC y su aplicación en una API REST con Spring Boot
Resumen ejecutivo
El patrón Modelo-Vista-Controlador (MVC) separa responsabilidades en tres capas:
- Modelo → datos y lógica de negocio.
- Vista → representación al usuario (JSON, HTML, XML, etc.).
- Controlador → orquesta las peticiones y respuestas.
En una API REST con Spring Boot la Vista se reduce a los DTO/JSON y los Controladores REST (@RestController) gestionan el flujo.
A continuación veremos la teoría detallada con diagramas, código y buenas prácticas.
1. ¿Qué es un patrón de diseño?
Definición
Un patrón de diseño es una solución reutilizable a un problema común en el diseño de software.
- No es código terminado, es una receta.
- Mejora la mantenibilidad, escalabilidad y claridad del sistema.
2. Patrón MVC (Modelo-Vista-Controlador)
2.1 Origen y propósito
| Año | Autor | Contexto |
|---|---|---|
| 1979 | Trygve Reenskaug | Interfaces de usuario en Smalltalk-80 |
| Hoy | Comunidad | Aplicaciones web, móviles, APIs REST |
Objetivo
Separar la interfaz de usuario (Vista) de la lógica de negocio (Modelo) y del flujo de control (Controlador).
2.2 Diagrama conceptual
graph TD
subgraph "Aplicación MVC"
U[Usuario]
V[Vista]
C[Controlador]
M[Modelo]
DB[(Base de datos)]
end
U -->|interacción| V
V -->|evento| C
C -->|actualiza estado| M
M -->|notifica cambios| V
M -->|persiste| DB
style C fill:#ffaaaa
style M fill:#aaffaa
style V fill:#aaaaff
2.3 Responsabilidades de cada capa
| Capa | Responsabilidades clave | Ejemplo en API REST |
|---|---|---|
| Modelo | Entidades JPA, repositorios, servicios, reglas de negocio, validaciones. | Libro, LibroRepository, LibroService |
| Vista | Representación del recurso: JSON, XML, PDF, etc. | LibroDTO, LibroResponse |
| Controlador | Recibe peticiones HTTP, llama al servicio, devuelve respuesta. | LibroController (@RestController) |
En las APIs REST la Vista NO es HTML, es el JSON serializado que Spring genera automáticamente.
3. MVC dentro de una API REST con Spring Boot
3.1 Arquitectura de paquetes recomendada
com.miempresa.apirest
├── controller # ← Capa Controlador
├── service # ← Lógica de negocio
├── repository # ← Acceso a datos (JPA)
├── model # ← Entidades JPA (Modelo)
├── dto # ← Objetos de transferencia (Vista)
└── config # ← Configuraciones globales
3.2 Flujo de una petición REST con MVC
sequenceDiagram
participant Cliente
participant Controller
participant Service
participant Repository
participant DB
Cliente->>Controller: GET /api/v1/libros/1
Controller->>Service: obtenerLibro(1)
Service->>Repository: findById(1)
Repository->>DB: SELECT * FROM libro WHERE id=1
DB-->>Repository: libro entity
Repository-->>Service: Libro
Service-->>Controller: LibroDTO
Controller-->>Cliente: 200 OK + JSON
Nota
La conversión de Libro (entidad) a LibroDTO se realiza normalmente con MapStruct o un mapper manual.
4. Implementación paso a paso en Spring Boot
4.1 Entidad (Modelo)
package com.miempresa.apirest.model;
import jakarta.persistence.*;
@Entity
@Table(name = "libros")
public class Libro {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String titulo;
private String autor;
private Integer anio;
/* getters y setters */
}
4.2 Repositorio (Modelo)
package com.miempresa.apirest.repository;
import com.miempresa.apirest.model.Libro;
import org.springframework.data.jpa.repository.JpaRepository;
public interface LibroRepository extends JpaRepository<Libro, Long> {
// CRUD ya disponible: findAll, findById, save, deleteById...
}
4.3 DTO (Vista)
package com.miempresa.apirest.dto;
public record LibroDTO(
Long id,
String titulo,
String autor,
Integer anio
) {}
4.4 Servicio (Modelo + lógica)
package com.miempresa.apirest.service;
import com.miempresa.apirest.dto.LibroDTO;
import com.miempresa.apirest.model.Libro;
import com.miempresa.apirest.repository.LibroRepository;
import org.springframework.stereotype.Service;
@Service
public class LibroService {
private final LibroRepository repo;
public LibroService(LibroRepository repo) {
this.repo = repo;
}
public LibroDTO obtenerPorId(Long id) {
return repo.findById(id)
.map(this::toDto)
.orElseThrow(() -> new LibroNoEncontradoException(id));
}
private LibroDTO toDto(Libro l) {
return new LibroDTO(l.getId(), l.getTitulo(), l.getAutor(), l.getAnio());
}
}
4.5 Controlador REST (Controlador)
package com.miempresa.apirest.controller;
import com.miempresa.apirest.dto.LibroDTO;
import com.miempresa.apirest.service.LibroService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/libros")
public class LibroController {
private final LibroService service;
public LibroController(LibroService service) {
this.service = service;
}
@GetMapping("/{id}")
public ResponseEntity<LibroDTO> obtener(@PathVariable Long id) {
LibroDTO dto = service.obtenerPorId(id);
return ResponseEntity.ok(dto);
}
@PostMapping
public ResponseEntity<LibroDTO> crear(@RequestBody LibroDTO dto) {
LibroDTO nuevo = service.crear(dto);
return ResponseEntity
.created(URI.create("/api/v1/libros/" + nuevo.id()))
.body(nuevo);
}
@PutMapping("/{id}")
public LibroDTO actualizar(@PathVariable Long id, @RequestBody LibroDTO dto) {
return service.actualizar(id, dto);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> eliminar(@PathVariable Long id) {
service.eliminar(id);
return ResponseEntity.noContent().build();
}
}
5. Diagrama de clases simplificado
classDiagram
class Libro {
+Long id
+String titulo
+String autor
+Integer anio
}
class LibroRepository {
<<interface>>
}
class LibroService {
-LibroRepository repo
+LibroDTO obtenerPorId(Long)
+LibroDTO crear(LibroDTO)
}
class LibroController {
-LibroService service
+ResponseEntity~LibroDTO~ obtener(Long)
+ResponseEntity~LibroDTO~ crear(LibroDTO)
}
class LibroDTO {
+Long id
+String titulo
+String autor
+Integer anio
}
LibroController --> LibroService
LibroService --> LibroRepository
LibroService --> LibroDTO
LibroRepository --> Libro
6. Buenas prácticas y errores comunes
| Buena práctica | Ejemplo concreto |
|---|---|
| No exponer entidades JPA | Usar siempre DTOs (LibroDTO). |
| Validaciones | @NotBlank, @Size, @Valid en el DTO. |
| Manejo global de excepciones | @ControllerAdvice + @ExceptionHandler. |
| Paginación | Pageable de Spring Data. |
| Logging | SLF4J + @Slf4j (Lombok). |
| Tests | @WebMvcTest, @DataJpaTest, Testcontainers. |
Errores comunes
- Devolver la entidad JPA directamente → exposición de datos sensibles o lazily loaded.
- Colocar lógica de negocio en el controlador → acoplamiento y dificultad de test.
- Ignorar el versionado de API → romper a los clientes antiguos.
7. Extensión: MVC vs MVVM vs MVP
| Patrón | Vista | Intermediario | Caso de uso típico |
|---|---|---|---|
| MVC | HTML/JSON | Controlador | APIs REST, monolitos web |
| MVP | Android View | Presenter | Android nativo |
| MVVM | WPF/Angular | ViewModel | SPA reactivas, WPF |