Apariencia
Por qué validar y por qué proteger invariantes
Índice · Siguiente: entradas y valores
Una aplicación recibe propuestas de cambio. El usuario puede escribir datos incompletos, un cliente de API puede enviar fechas invertidas y una operación puede llegar cuando el estado ya no permite ejecutarla. Validar permite responder qué condición se incumple antes de continuar. La pregunta importante es qué estamos validando: un dato, un estado, una acción o una relación con datos externos.
Un ejemplo que separa las responsabilidades
Una reserva tiene fecha de entrada, fecha de salida y un indicador de cierre.
| Situación | Problema | Responsabilidad |
|---|---|---|
| La entrada contiene una fecha que no se puede interpretar | No se puede construir el candidato | Lectura y conversión de entrada |
| La salida es anterior a la entrada | El estado contradice el significado de reserva | Invariante local |
| Las fechas son correctas, pero la reserva está cerrada | La acción no está permitida | Política de operación |
| Las fechas son correctas, pero otra reserva ocupa el recurso | El estado externo impide confirmar | Validación contextual |
| Otra operación ocupa el recurso después de consultar | La comprobación previa quedó desactualizada | Garantía transaccional del almacenamiento |
Una reserva cerrada no es inválida. Es un estado válido que restringe acciones. Esta diferencia evita introducir la condición !Closed como invariante: hacerlo impediría cerrar cualquier reserva.
Por qué la validación de entrada no basta
Validar un formulario ofrece una respuesta temprana y permite mostrar varios errores. Sin embargo, el mismo dominio puede ser utilizado por una importación, un proceso en segundo plano u otra API. Si la relación entre fechas solo vive en la pantalla, otro punto de entrada puede romperla.
La entidad protege el estado con invariantes antes de publicarlo. La validación de entrada y la invariante pueden reutilizar el mismo plan, pero actúan en momentos distintos y devuelven resultados a consumidores distintos.
Por qué un candidato separado
Modificar primero la entidad y validar después obliga a restaurar cambios si algo falla. Un candidato inmutable permite comprobar el cambio completo antes de reemplazar el estado actual.
text
Estado actual válido
↓ construir candidato
Propuesta de nuevas fechas
↓ comprobar invariantes
┌── falla ──→ devolver error; conservar estado actual
└── cumple → publicar candidato como nuevo estadoNo necesitas copiar el objeto entero manualmente: un record con componentes inmutables permite construir el candidato con with. Una lista mutable dentro de un record sigue siendo mutable; la garantía requiere que todos los componentes respeten la inmutabilidad.
Por qué reglas, composición y plan
Una regla comprueba una condición. La composición indica cómo se relaciona con otras. El plan reúne esa composición y los diagnósticos para repetir la evaluación con candidatos distintos.
Por ejemplo, para comprobar un correo disponible, primero debe aceptarse su contenido y después consultarse el repositorio. Una Sequence expresa esa dependencia. Otros campos independientes pueden seguir acumulando errores.
La plataforma ofrece el motor y el reporte. El dominio adapta los incumplimientos a errores de entidad, modelo o negocio. El usuario de una pantalla puede recibir mensajes por campo; la entidad utiliza los mismos resultados para decidir si publica el estado.
Qué pieza mínima usar
Una condición booleana aislada puede resolverse con una regla o un predicado. Usa un plan cuando existan composición, rutas o mensajes. Usa invariantes para proteger estados de entidades. Usa políticas de negocio para autorizar acciones. Usa contexto cuando la respuesta dependa de información externa a ese estado.
No necesitas introducir una entidad para validar un texto, ni una sesión de formulario para procesar una petición única. Las sesiones de plataforma resuelven la coordinación temporal de varias ediciones, no las invariantes del dominio.
Continúa con entradas y objetos de valor para ver cómo una misma política evita que la UI acepte algo que la fábrica rechace.