Skip to content

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:

  1. Condición: ¿se cumple una regla concreta?
  2. Composición: ¿cómo se relaciona esa regla con las demás?
  3. Diagnóstico: ¿cómo se explica un incumplimiento al consumidor?
  4. 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 repositorio

Ejecutar 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.Unique

Cada 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
                         │
                         ▼
                 ValidationReport

El 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, Failed y Canceled.

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 continuar

Elegir la pieza mínima ​

NecesidadPieza suficiente
Comprobar una condición y obtener un booleanoIRule<T>
Reutilizar una condición identificableRuleDefinition<T, C>
Expresar operadores y examinar la ejecuciónRuleTree y RuleEvaluator
Obtener rutas, códigos y mensajesValidationPlan<T, C>
Validar durante una interacción con varias edicionesFormValidationSession<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.

Uso personal limitado. Uso comercial sujeto a autorización escrita.