05 - Validación e invariancias
Con el CRUD de catálogos y productos del hito 04, la API ya existe y es exactamente la del contrato; lo que falta es rechazar correctamente lo que no admite. Este hito aplica el tema 03 — Validación e invariancias: dos preguntas distintas, respondidas en lugares distintos.
| Pregunta | Dónde se responde | Mecanismo | Respuesta |
|---|---|---|---|
| ¿Cumple la petición el formato del contrato? | La petición de entrada, en la capa de presentación | Jakarta Validation | 400 con campo y motivo |
| ¿El estado propuesto es válido para el negocio? | El dominio | Invariantes en los modelos | Excepción de dominio, mapeada al error del contrato |
1. Constraints en el contrato
- Ampliar
openapi.yaml(v0.1.1) con lo que la API admite: los constraints se redactan en el propio documento, porque describen el contrato, no el código.
| Esquema | Constraint |
|---|---|
CatalogRequest.name | minLength 1 · maxLength 80 |
CatalogRequest.description | maxLength 240 |
ProductRequest.name | minLength 1 · maxLength 80 |
ProductRequest.description | maxLength 240 |
ProductRequest.price | minimum 0.01 · maximum 999999.99 |
ProductRequest.stock | minimum 0 · maximum 100000 |
ProductRequest.catalogId | minimum 1 |
- Regenerar, con el generador preparado para ello (opciones en el tema 02): los constraints llegan al esqueleto como anotaciones Jakarta Validation, y la validación se dispara en el punto declarado por el propio contrato.
2. El 400 coherente con el contrato
- Las respuestas de error de la API salen en formato ProblemDetail (RFC 9457): el esquema
Problemque el contrato declara, materializado. - A la base que trae Spring WebMVC se añade el manejo global de excepciones de la presentación, como expone el tema 03: el 400 con el detalle — campo y motivo por cada violación — y las excepciones del dominio mapeadas a sus estados (404 para lo inexistente). Las excepciones propias del dominio del catálogo viven en él; el formato lo da la presentación.
3. Invariantes con value objects
La petición válida según el contrato puede seguir proponiendo un estado imposible para el negocio: el precio y el stock deben protegerse en el dominio.
- Crear los value objects del catálogo en
domain, según la especificación:Money(precio: decimal mayor que cero, escala de 2) yStock(entero no negativo). Cada uno valida en su construcción y lanza la excepción de dominio propia. - El modelo
Productpasa a componerse con ellos; la creación y la modificación de productos construyen primero los value objects: si una invariante falla, el estado inválido nunca llega a la persistencia. - El mismo dato puede aparecer en las dos fronteras:
minimumen el contrato (formato) y la validación delMoney(invariante). No es redundancia: protegen fronteras distintas, como explica el tema.
La invariante no se confía a la validación de la petición: el value object la garantiza para cualquier código que construya un
Money, no solo para el flujo que llega validado.
4. Pruebas
Criterios de prueba de este nivel, según el tema: qué probar y qué aislar.
- Value objects (unitarias):
MoneyyStockconstruidos dentro de rango y rechazados fuera; sin Spring, sin base de datos. - Servicio (unitarias con un doble del repositorio): la creación y la actualización construyen el modelo a través de los value objects; un producto con
catalogIddesconocido no se llega a crear, y un producto inexistente al modificar o borrar falla con la excepción de dominio correspondiente. - Presentación (con el servicio doblado): una petición que no cumple cada constraint — de catálogo o de producto — responde 400 con las violaciones; un recurso inexistente — sintácticamente válido — responde 404; una correcta, 200 / 201 / 204.
Comprobación
- Una petición con precio 0 o stock negativo responde 400 antes de tocar el dominio: es la constraint del contrato.
- Un catálogo con nombre vacío responde 400 por la misma vía.
- El mismo rango está garantizado por los value objects en pruebas unitarias: construir un
Moneycon precio 0 lanza la excepción de dominio. - Un catálogo o producto inexistente en ficha, modificación o borrado responde 404 con el problema coherente.
- El error 400 muestra campo y motivo en el formato
Problemdel contrato. - Versión del contrato 0.1.1; commit y tag
hito-05.
Siguiente hito: hito 06 — Módulos: pedidos y API pública.