Apariencia
Validación del dominio
La validación de dominio responde preguntas distintas según el momento: si un valor puede existir, si una entidad puede adoptar un estado, si una operación está permitida y si la escritura es consistente con los datos almacenados. Separarlas evita convertir cada condición en un contrato nuevo y permite reutilizar su lógica.
Lee la motivación de plataforma para comprender condiciones, composición y planes. Esta guía explica cómo esos planes se integran con errores e invariantes de dominio.
Recorrido
Para empezar escribiendo código, abre el inicio rápido: dos campos, ejecución y lectura directa de errores. Después consulta las convenciones cotidianas y la evaluación de usabilidad. Las páginas siguientes desarrollan la motivación y las integraciones.
- Motivación y decisiones: por qué validar y qué protege cada capa.
- Entradas y objetos de valor: aceptar, normalizar y construir con la misma política.
- Invariantes y cambios de estado: proteger una reserva antes de publicar el candidato.
- Operaciones de negocio: rechazar acciones sobre un estado que sigue siendo válido.
- Persistencia paso a paso: consultas, escritura, conflictos y cancelación.
- Diagnósticos y ejecución: mensajes por campo, alternativas, traza y opciones.
- Flujo completo de cliente: une valores, entidad y persistencia.
- Usabilidad y consistencia: garantías compartidas y diferencias que siguen siendo intencionales.
- Migración y contratos avanzados.
- Plan inicial y plan de consistencia: decisiones y criterios de aceptación.
Elegir una entrada
| Necesidad | Entrada | Garantía |
|---|---|---|
| Construir correo, moneda o nombre | Email.Create, CurrencyCode.Create, etc. | La fábrica acepta o devuelve un error funcional |
| Validar campos y relaciones | EntityValidator.For<T>(configure) | Produce EntityError conservando rutas y códigos |
| Reutilizar una composición | Validation.For<T>() y WithRules(plan) | Comparte configuración, no resultados |
| Proteger el estado de una entidad | Invariants.For<T>().Include(plan) | El cambio se publica solamente si cumple las invariantes |
| Autorizar una operación | BusinessValidator.For<C>() | Reúne razones de negocio y protege con prerrequisitos globales |
| Consultar persistencia | ModelValidator.For<T, C>(configure) | Evalúa con el contexto de esa ejecución |
| Validar y escribir | ModelPersistence.ExecuteAsync | Entrega a escritura el mismo candidato validado |
Los callbacks de las entradas de entidad y modelo reciben el builder común. Dentro de ellos se usan Field, Sequence, When, AnyOf, Must y MustAsync. No es necesario construir RuleNode ni razones genéricas para empezar.
Invariante, condición de operación y consulta
Una invariante local debe cumplirse en cada estado publicado de la entidad. Una condición de negocio puede depender de la operación: un cliente válido puede estar cerrado y no admitir cambios. Una condición contextual puede cambiar fuera de la entidad: un correo disponible puede dejar de estarlo antes de guardar.
Por eso la entidad valida localmente antes de reemplazar su estado; la operación comprueba su política; la persistencia consulta y aplica las restricciones del almacenamiento. El orden de estos pasos debe responder a dependencias explícitas.
Objetos de valor y default
Una estructura de C# siempre permite default, aunque tenga constructor privado. La fábrica garantiza sus resultados exitosos; el tipo por sí solo no demuestra que se haya llamado a esa fábrica.
| Tipo | Significado de default | Uso seguro |
|---|---|---|
Email, CurrencyCode, FirstName, LastName, PhoneNumber, Url | Texto sin inicializar | Comprobar IsInitialized antes de consumir Value |
Money | Moneda sin inicializar | IsInitialized; Add y Subtract rechazan ambos operandos no inicializados |
BirthDate | Fecha sin inicializar | IsInitialized; Age lanza InvalidOperationException si falta |
Percentage | Cero por ciento | Es un valor admitido |
Gender | GenderKind.Male, valor cero actual | Es un valor admitido; no representa ausencia |
ToString() de los tipos no inicializados de texto y fecha entrega una cadena vacía. Es una representación segura, no una validación. BirthDate.IsInitialized no reevalúa antigüedad a medida que pasa el tiempo. Los desbordamientos aritméticos de Money conservan el comportamiento de decimal.
Estado y concurrencia
Entity.State tiene setter privado, pero expone el estado. Usa records con componentes inmutables y colecciones inmutables: un record que contiene una lista mutable no ofrece inmutabilidad profunda. La entidad no admite uso concurrente.
Los planes y las especificaciones conservan estructura reutilizable. Cada ejecución conserva sus resultados por separado. Predicados, servicios capturados y evidencia deben respetar sus propias garantías de concurrencia; preparar un árbol una vez no vuelve inmutable una dependencia.
Consultas y almacenamiento
IModelQueries recibe Func<TEntity, bool>. Ese delegado no es una expresión traducible automáticamente a SQL. La interfaz actual es adecuada para adaptaciones controladas o datos en memoria; no debe motivar descargar una tabla completa para evaluar un predicado.
Para acceso real a datos, el contexto tipado de ModelValidator<T, C> puede proporcionar operaciones específicas como IsEmailTakenAsync(email, excludedId, token). La identidad excluida debe pertenecer al contexto de edición. La aplicación controla transacciones, restricciones únicas y claves foráneas.
Una respuesta favorable antes de escribir no elimina carreras. ModelPersistenceErrorAdapter debe reconocer únicamente restricciones conocidas del proveedor; las excepciones desconocidas se propagan y la cancelación no se convierte en un incumplimiento funcional.
Verificación
El contrato ejecutable comprueba las entradas recomendadas y el flujo de persistencia. Desde la raíz:
powershell
./tests/Test-DomainValidationContracts.ps1 -RestoreEn ejecuciones posteriores, omite -Restore si los contratos ya tienen sus recursos restaurados. El script ejecuta nueve contratos de dominio; los ejemplos de esta guía se apoyan en esos contratos.