====== 05 - Validación e invariancias ====== Con el CRUD de catálogos y productos del [[proyecto:04-api-first|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 [[temario:03-validacion|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 [[temario:02-api-first|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 ''Problem'' que 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 [[temario:03-validacion|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 [[proyecto:02-especificacion|especificación]]: ''Money'' (precio: decimal mayor que cero, escala de 2) y ''Stock'' (entero no negativo). Cada uno valida en su construcción y lanza la excepción de dominio propia. - El modelo ''Product'' pasa 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**: ''minimum'' en el contrato (formato) y la validación del ''Money'' (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): ''Money'' y ''Stock'' construidos 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 ''catalogId'' desconocido 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 ''Money'' con 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 ''Problem'' del contrato. * Versión del contrato 0.1.1; commit y tag ''hito-05''. **Siguiente hito:** [[proyecto:06-modulos-modulith|hito 06 — Módulos: pedidos y API pública]].