Proyecto compartido. En el taller de Intermodular que abre esta semana has trabajado el ci del repositorio de servidor. En Servidor continúas la implementación del mismo producto.
Se explica
25 minutos · explicación y demostración
La API ya tiene rutas, DTO, validaciones y errores definidos. Hoy consolidarás el contrato que utilizará el cliente de Intermodular. Publicar aquí significa dejar una versión identificable y comprobable; el procedimiento de despliegue se trabaja en el otro módulo.
Correcto no es lo mismo que coherente
Cada endpoint escrito en estas tres semanas es correcto por separado. Aun así, una API puede fallar en su conjunto de una forma que ninguna prueba individual detecta:
- Un recurso se llama en plural y otro en singular
- Un endpoint devuelve
201conLocationy otro se olvida de la cabecera - Una fecha viaja como texto ISO en un sitio y como número en otro
- Un recurso valida el nombre y otro, con el mismo campo, no lo valida
- Un error llega con tu formato y otro con el de Spring, porque quedó un
ResponseEntitysuelto
Todas son «pequeñas». Todas obligan a quien consume la API a tratar cada endpoint como un caso especial, que es exactamente lo que la unidad quería evitar.
Coherencia entre endpoints
Dos operaciones pueden pasar sus pruebas individuales y, aun así, formar una API incoherente. Por ejemplo, un alta puede devolver errores con un campo llamado mensaje y una modificación devolver una página HTML. El cliente tendría que programar una estrategia distinta para cada ruta. Hoy comprobamos que los patrones de respuesta, validación y nombres se mantienen a través de todo el contrato.
Una auditoría útil parte de la tabla de endpoints. Para cada uno se comparan entrada válida, entrada inválida, ausencia del recurso y conflicto cuando corresponda. No todos los endpoints necesitan todos los casos: se justifica cuáles aplican. El resultado se guarda en la colección HTTP, no solo en una captura de pantalla.
Intermodular consumirá esta misma API. Por eso se entregan una URL base configurable, ejemplos de petición y respuesta y una versión del repositorio que compila. A partir de este punto, renombrar un campo publicado es también una decisión que afecta al cliente; el cambio se comunica y se comprueba en ambas piezas.
Se trabaja
140 minutos · implementación guiada sobre el proyecto propio
Paso 1 · Retomar el proyecto y preparar la comprobación
- Ejecuta la colección y abre el README, los DTO y el manejador de errores. Identifica el commit desde el que parte la revisión.
- Contrasta cada ruta consumida por el portfolio con su petición y respuesta reales. Anota los cambios que todavía no se han comunicado al cliente.
- Prepara los casos de alta válida, validación, ausencia y conflicto con datos de tu dominio.
Paso 2 · La segunda auditoría
Vuelve a la rúbrica de la sesión 9, la misma tabla, sin cambiar ni un criterio:
| # | Criterio | Sesión 9 | Hoy |
|---|---|---|---|
| 1 | Ninguna ruta contiene un verbo | ||
| 2 | Cada recurso tiene una URL propia y estable | ||
| 3 | Las colecciones se nombran en plural | ||
| 4 | La acción la expresa siempre el método HTTP | ||
| 5 | GET nunca modifica nada |
||
| 6 | Cada final posible tiene su código de estado | ||
| 7 | Los recursos relacionados se expresan con jerarquía | ||
| 8 | El mismo tipo de dato se representa igual en todos los endpoints | ||
| 9 | La API no publica campos internos del modelo | ||
| 10 | Las respuestas incluyen enlaces a operaciones relacionadas |
Rellena las dos columnas y quédate con la diferencia: es la medida de lo que has aprendido en tres semanas, y forma parte de la entrega.
El criterio 10 debe seguir siendo «no», y tienes que poder decir por qué sin que suene a excusa.
Paso 3 · Especificación · el estado final de la API
Tu API tiene que cumplir esto por completo.
Estructura del proyecto
com.ejemplo.gestor
├── controller · recibe y responde, nada más
├── dto · lo que se acepta y lo que se publica
├── mapper · el único sitio que traduce
├── model · lo que maneja tu código
├── validacion · anotaciones propias
└── error · excepciones, formato y manejador
Recursos y rutas
| Método y ruta | Caso | Respuesta |
|---|---|---|
GET /proyectos |
Con filtros opcionales | 200 con array |
GET /proyectos/{id} |
Existe / no existe | 200 / 404 |
POST /proyectos |
Válido / inválido | 201 con Location / 400 |
PUT /proyectos/{id} |
Válido / no existe | 200 / 404 |
PATCH /proyectos/{id} |
Válido / no existe | 200 / 404 |
DELETE /proyectos/{id} |
— | 204 |
GET /proyectos/{id}/tareas |
Proyecto existe / no existe | 200 / 404 |
GET /tareas |
Con filtros opcionales | 200 con array |
GET /tareas/{id} |
Existe / no existe | 200 / 404 |
POST /tareas |
Válido / inválido | 201 con Location / 400 |
PUT /tareas/{id} |
Válido / no existe | 200 / 404 |
PATCH /tareas/{id} |
Válido / no existe | 200 / 404 |
DELETE /tareas/{id} |
— | 204 |
Reglas que se comprueban
- Ninguna ruta lleva verbos y todas las colecciones están en plural.
- Ningún
@RequestBodyrecibe una clase del modelo. - Ningún endpoint devuelve una clase del modelo.
- Toda la traducción vive en el paquete
mapper. - Toda entrada de creación y sustitución lleva
@Valid. - Todos los mensajes de validación están en español y dicen qué corregir.
- Existe al menos una anotación de validación propia.
- Todos los errores, sin excepción, tienen el mismo formato.
- Ningún controlador construye una respuesta de error.
- Un
500no revela nada del interior. - Existe al menos un caso que responda
409.
Estoy atascado · me quedan errores con formato distinto
Busca en tu código ResponseEntity.notFound, ResponseEntity.badRequest y ResponseEntity.status. Cada aparición dentro de un controlador es un error que no pasa por tu manejador.
Y prueba los caminos raros: una ruta que no existe, un método no permitido, un Content-Type incorrecto. Alguno de esos ni siquiera llega a un controlador tuyo, así que piensa qué excepción lanza Spring y si tu manejador la cubre.
Paso 4 · La colección de aceptación
Reutiliza la colección de la sesión 8. Crea una carpeta para errores y guarda un caso por comportamiento: cuerpo ilegible, campo inválido, regla propia, id inexistente y conflicto. En cada petición comprueba estado y los campos status, detail e instance de ProblemDetail; para validación comprueba también el campo rechazado. Prepara primero los registros necesarios para provocar conflictos y usa sus ids reales. Ejecuta también los casos correctos para comprobar que el manejador no los altera.
Paso 5 · Preparar la evidencia de esta versión
- El proyecto, con la estructura de paquetes de la especificación.
- La colección exportada, en
pruebas/. - la auditoría: la rúbrica con sus dos columnas y un párrafo comentando la diferencia.
- las decisiones técnicas, ampliado con estas cinco:
- Qué campos dejaste fuera de cada DTO de entrada y de salida, y por qué.
- Qué anotación de validación propia escribiste y por qué no bastaba
@Pattern. - Qué formato de error elegiste y qué campo añadirías si tuvieras que depurar un fallo reportado por un cliente.
- Qué caso de tu API responde
409y por qué no es un400ni un404. - Por qué tu API se queda en el nivel 2 y no implementa hipermedia.
Paso 6 · Autoevaluación · el examen de coherencia
Esta lista no mira endpoints sueltos: mira el conjunto. Pásatela con la API delante.
| Comprobación | Cómo se verifica |
|---|---|
| Nombres uniformes | Lista todas tus rutas seguidas y léelas de un tirón. ¿Alguna desentona? |
| Representaciones uniformes | ¿Un mismo campo se llama igual en todos los recursos donde aparece? |
| Tipos uniformes | ¿Las fechas viajan siempre igual? ¿Los booleanos? |
| Códigos uniformes | ¿Dos operaciones equivalentes en recursos distintos devuelven lo mismo? |
| Errores uniformes | ¿Los seis errores tienen la misma forma? |
| Validación uniforme | ¿Un campo con el mismo significado tiene las mismas reglas en los dos recursos? |
La fila de la validación es la que más suspende. Es muy fácil validar a fondo el recurso con el que empezaste y dejar el segundo a medias.
Paso 7 · Lo que sigue haciendo mal, y es mucho
Esto ya no son defectos del contrato: el contrato está bien. Lo que está mal es lo que hay detrás.
Abre tu TareaController y cuenta lo que hace:
- Recibe la petición y devuelve la respuesta
- Guarda la lista de tareas como atributo
- Lleva la cuenta del siguiente identificador
- Busca, filtra y recorre
- Decide reglas de negocio, como que una tarea nace sin completar
- Y todo eso se pierde entero al reiniciar
| Lo que está mal | Se arregla en |
|---|---|
| El controlador guarda los datos y lleva la lógica | UD4, con capas |
| No hay forma de reutilizar una regla en dos endpoints | UD4, con un service |
| Nada comprueba la lógica sin arrancar el servidor | UD4, con los primeros tests |
| La regla «el proyecto debe existir» sigue sin sitio | UD4 |
| Al reiniciar se pierde todo | UD5, con PostgreSQL |
Esa cuarta fila viene de la sesión 13: la encontraste, viste por qué una anotación no podía resolverla, y desde entonces está esperando. En la UD4 tendrá por fin dónde vivir.
Ver respuestas
1 · Las incoherencias del conjunto: nombres, tipos, códigos o validaciones que difieren entre recursos. Cada endpoint es correcto por separado y la API obliga a tratarlos como casos especiales.
2 · Uno: el manejador. Si sale más de uno, queda trabajo.
3 · Guardar los datos, generar identificadores, buscar y filtrar, y decidir reglas de negocio. Un controlador debería recibir la petición, delegar y responder.
4 · Porque la hipermedia añade complejidad al servidor que muy pocos clientes aprovechan, y es una decisión de coste y beneficio tomada a conciencia.
Paso 8 · Comprobar y registrar el resultado del proyecto
- Otra persona debe poder ejecutar la colección y obtener las respuestas documentadas sin preguntarte qué campos o ids escribir.
- Deja enlazados la versión del contrato y los cambios que afecten al cliente. El backend continúa en memoria hasta la UD5; documenta esa limitación.
Cierre
15 minutos · resultado comprobable y explicación individual
Al terminar la sesión:
El repositorio compila y el contrato queda disponible para el CI y el despliegue de Intermodular; los cambios siguientes se comunican al cliente.
Cada integrante explica una decisión del código apoyándose en una de las comprobaciones realizadas.