Apariencia
Por qué validar, componer y construir un plan
Índice · Siguiente: casos de uso
Validar no consiste solamente en responder true o false. Una aplicación recibe datos incompletos, contradictorios, desactualizados o dependientes de servicios externos. Antes de ejecutar una operación necesita decidir si esos datos cumplen las condiciones del caso de uso y, cuando no las cumplen, explicar qué debe corregirse.
Esta guía separa cuatro preguntas que a menudo aparecen mezcladas:
- Condición: ¿se cumple una regla concreta?
- Composición: ¿cómo se relaciona esa regla con las demás?
- Diagnóstico: ¿cómo se explica un incumplimiento al consumidor?
- Ejecución: ¿qué candidato y qué contexto se están evaluando ahora?
platform.rules responde principalmente las dos primeras. platform.validators añade diagnósticos, planes y coordinación para escenarios de aplicación.
Por qué validar
Los datos pueden tener una forma válida y aun así no ser aceptables para una operación. Por ejemplo, un correo puede tener formato correcto pero estar registrado por otro cliente. La validación protege las precondiciones conocidas antes de continuar y devuelve información que una UI, una API o una prueba puede interpretar.
Una validación útil debe poder distinguir al menos tres situaciones:
- el candidato cumple las condiciones aplicables;
- el candidato incumple una condición funcional y puede recibir un diagnóstico;
- la comprobación no pudo completarse por cancelación o por un fallo técnico.
La última situación no convierte al candidato en inválido. Si un repositorio no responde, afirmar «el correo ya existe» sería una conclusión falsa. Por eso las excepciones y la cancelación se mantienen separadas de los diagnósticos funcionales.
La validación tampoco sustituye las garantías del dominio o del almacenamiento. Una comprobación previa mejora la experiencia y evita trabajo innecesario, pero una restricción única de base de datos sigue siendo necesaria para resolver carreras entre comprobar y guardar.
Por qué no basta un predicado
Un predicado aislado es suficiente cuando solo interesa una condición inmediata:
csharp
var hasName = !string.IsNullOrWhiteSpace(candidate.Name);Deja de ser suficiente cuando la aplicación necesita saber:
- a qué campo pertenece el fallo;
- qué código estable debe enviar una API;
- qué mensaje debe mostrar;
- si otra comprobación debe ejecutarse después;
- si una regla fue omitida porque no era aplicable;
- qué resultado debe conservarse para diagnóstico o trazabilidad.
IRule<T> encapsula la condición reusable. Una definición le asigna identidad. Una definición de validación añade el diagnóstico. Cada nivel conserva una responsabilidad distinta para que la condición no dependa de la presentación.
Por qué componer
Las condiciones de un objeto no siempre forman una lista plana. Considera un correo de cliente:
text
Correo presente
↓ si se cumple
Formato válido
↓ si se cumple
Correo disponible en el repositorioEjecutar las tres condiciones de manera independiente consultaría el repositorio incluso cuando el correo está vacío o mal formado. Una secuencia expresa el prerrequisito y evita consultas sin sentido.
Otros casos requieren relaciones diferentes:
- independencia: nombre y edad pueden producir ambos diagnósticos en una ejecución;
- alternativa: se acepta teléfono o correo como medio de contacto;
- condición: la dirección fiscal solo se valida cuando se solicita factura;
- negación: un valor no debe pertenecer a un conjunto prohibido;
- dependencia: una consulta remota solo se ejecuta después de validar localmente.
Componer hace explícitas estas relaciones. También permite que el resultado explique qué se ejecutó, qué falló y qué se omitió. Una colección plana de delegados no conserva esa información.
Por qué un árbol
Una composición con conjunciones, alternativas, condiciones y prerrequisitos tiene estructura jerárquica. El árbol es la representación de esa estructura, no un requisito que el consumidor cotidiano deba construir manualmente.
text
AllOf
├── Name.Required
└── Email.Sequence
├── Email.Required
├── Email.Format
└── Email.UniqueCada hoja representa una condición y cada grupo expresa cómo interpretar a sus hijos. Esta representación permite aplicar cortocircuito, registrar omisiones, capturar evidencia y producir una traza sin esconder la intención original.
El builder es la API recomendada para construir esa estructura. Los contratos de árbol son útiles cuando una integración necesita controlar identidades, operadores o trazas directamente.
Por qué hace falta un plan
Una regla responde una pregunta concreta. Un ValidationPlan<T, C> describe la política de validación completa de un caso de uso. Reúne:
- condiciones y su composición;
- selectores y rutas de propiedades;
- códigos, mensajes y evidencia;
- opciones de evaluación;
- la proyección del resultado a
ValidationReport.
Sin un plan, cada endpoint o formulario tendría que reconstruir estas decisiones y podría ejecutar las mismas reglas en distinto orden o producir mensajes incompatibles. El plan convierte esa configuración en un objeto reutilizable y ejecutable con candidatos y contextos diferentes.
text
Configuración, una vez Datos, en cada ejecución
───────────────────── ─────────────────────────
reglas candidato
composición ── plan + ──> contexto
rutas cancelación
diagnósticos opciones
│
▼
ValidationReportEl plan no guarda el candidato ni el resultado anterior. Puede compartirse si las reglas y servicios capturados son seguros para concurrencia. El estado temporal de una pantalla pertenece a una sesión, no al plan.
Por qué separar evaluación y diagnóstico
La evaluación describe hechos de ejecución: una regla se satisfizo, falló o fue omitida. El diagnóstico interpreta un incumplimiento para un consumidor mediante código, ruta y mensaje.
La separación permite:
- usar una misma condición con mensajes distintos;
- inspeccionar una traza sin publicar todos sus nodos como errores;
- cambiar la presentación sin cambiar la lógica;
- capturar evidencia durante la comprobación y formatearla después;
- distinguir un grupo lógico de los mensajes terminales que debe mostrar una UI.
Un ValidationReport entrega ambas vistas: Evaluation para entender la ejecución y Diagnostics para comunicar los incumplimientos.
Por qué existe el contexto
Una regla local depende únicamente del candidato. Una regla contextual necesita datos propios de la ejecución: repositorios, permisos, la identidad excluida durante una edición o información de la operación.
Pasar esos elementos como contexto evita esconder estado mutable dentro de una regla compartida. El mismo plan puede evaluar simultáneamente candidatos distintos con contextos distintos, siempre que sus dependencias sean seguras para concurrencia.
No uses contexto si todas las condiciones se resuelven en memoria. Empieza con Validation.For<T>() y cambia a Validation.For<T, C>() cuando una regla realmente necesite información externa.
Por qué una sesión de formulario
Un formulario produce una sucesión de candidatos mientras el usuario escribe. Una respuesta lenta para una edición antigua puede llegar después de una respuesta nueva y sobrescribirla. El plan no debe conservar ese estado porque también se utiliza fuera de la UI.
FormValidationSession<T, C> agrega las responsabilidades temporales:
- debounce durante la escritura;
- cancelación cooperativa de la operación anterior;
- versión de cada solicitud;
- descarte de resultados obsoletos;
- publicación de estados
Pending,Valid,Invalid,FailedyCanceled.
Para una petición única de API o una validación antes de guardar, ejecuta el plan directamente. Crea una sesión solamente cuando exista una interacción prolongada que deba coordinar varias evaluaciones.
Flujo completo
text
Entrada del usuario o de una API
↓
El plan selecciona campos y aplica la composición
↓
El evaluador ejecuta condiciones locales o contextuales
↓
RuleEvaluation conserva estados, omisiones y evidencia
↓
RuleDiagnostics proyecta los incumplimientos publicables
↓
ValidationReport entrega validez y mensajes
↓
La aplicación decide mostrar, rechazar o continuarElegir la pieza mínima
| Necesidad | Pieza suficiente |
|---|---|
| Comprobar una condición y obtener un booleano | IRule<T> |
| Reutilizar una condición identificable | RuleDefinition<T, C> |
| Expresar operadores y examinar la ejecución | RuleTree y RuleEvaluator |
| Obtener rutas, códigos y mensajes | ValidationPlan<T, C> |
| Validar durante una interacción con varias ediciones | FormValidationSession<T, C> |
No toda aplicación necesita usar directamente todos estos contratos. Para código de aplicación, el punto de entrada habitual es Validation.For<T>(); las capas inferiores existen para reutilización e integración avanzada.
Continúa con los casos de uso para ver estos conceptos aplicados a flujos completos.