Skip to content

Persistencia: validar, consultar y confirmar ​

Índice · Anterior: operaciones · Siguiente: diagnósticos

Motivación ​

Un correo puede ser aceptable y aun así pertenecer a otro cliente. La entidad no puede decidirlo mirando solo su estado. Además, la respuesta puede cambiar entre consultar y escribir. La validación contextual ofrece un rechazo comprensible antes de escribir; las restricciones de almacenamiento resuelven conflictos al confirmar.

El ejemplo completo de cliente contiene las declaraciones utilizadas aquí. Sus consultas y escritura son simuladas; no representa una transacción real.

Paso 1. Preparar un candidato y un contexto ​

csharp
var email = Email.Create(" [email protected] ");
if (email.IsError) throw new InvalidOperationException(email.Error.Message);
var initial = new CustomerState(Guid.NewGuid(), email.Value.Value, false);
var customer = new Customer(initial);
var queries = new CustomerQueries();
var context = new SaveContext(queries, initial.Id);

Se construye primero el correo normalizado. En una API real devuelve el error de fábrica al llamador; la excepción del fragmento solo señala una entrada fija inesperadamente inválida en el ejemplo.

SaveContext.ExcludedId es la identidad actual al editar y null al crear. El contexto pertenece a esta ejecución. No captures la identidad de un usuario en una definición compartida.

Paso 2. Declarar dependencias ​

csharp
var model = ModelValidator.For<CustomerState, SaveContext>(b => b.Sequence(s => s
    .Include(CustomerPolicy.Local)
    .MustAsync(async (state, ctx, token) =>
        !await ctx.Queries.IsEmailTakenAsync(state.Email!, ctx.ExcludedId, token),
        "El correo está registrado.", "email.unique")));

El plan local usa la política de fábrica. La consulta solo tiene sentido si esa política acepta el dato. La secuencia expresa la dependencia; dos llamadas independientes a WithRules no la crearían.

El ejemplo almacena y consulta la dirección normalizada preparada en el paso 1. Validar una cadena con espacios mediante una fábrica no la normaliza automáticamente; no uses la entrada original en una consulta que espere el valor normalizado.

Paso 3. Reconocer conflictos concretos ​

csharp
var adapter = new ModelPersistenceErrorAdapter(exception =>
    exception is DuplicateEmailException
        ? new ModelReason("email.unique", "El correo está registrado.")
        : null);

El archivo completo incluye los namespaces requeridos. En un proveedor real, reconoce el código y la restricción concreta. Un timeout o una conexión fallida no deben convertirse en «correo duplicado». null deja que el error desconocido se propague.

Paso 4. Validar y escribir la misma instantánea ​

csharp
var result = await ModelPersistence.ExecuteAsync(
    customer.State, context, model, Write, adapter);

Write recibe exactamente el candidato y el contexto evaluados. Debe ejecutar la escritura bajo las garantías apropiadas del almacenamiento. Si quieres autorizar una acción de negocio, comprueba su política antes de llamar al puente; el puente no descubre ni ejecuta políticas de operación automáticamente.

Paso 5. Interpretar cada salida ​

text
Candidato
   ↓
Plan local ── falla ──→ ModelError; no consulta; no escribe
   ↓ cumple
Consulta ─── ocupado → ModelError; no escribe
   ↓ disponible
Escritura ── conflicto reconocido → ModelError
   ├──────── error desconocido ───→ excepción técnica
   └──────── éxito ───────────────→ resultado satisfactorio

La cancelación se comprueba durante la evaluación y antes de escribir. Una operación cancelada no es una evaluación satisfactoria ni un error funcional. La escritura recibe el token y debe atenderlo; el puente no revierte por sí solo una escritura que ya se haya confirmado.

Paso 6. Verificar escenarios ​

El ejemplo imprime un guardado correcto y una consulta. El contrato ejecutable comprueba también entrada inválida sin consultas, duplicidad sin escritura, conservación del candidato, restricción reconocida y propagación de errores desconocidos.

IModelQueries usa delegados Func, no expresiones traducibles automáticamente a SQL. Para una integración de almacenamiento utiliza consultas específicas del contexto, como IsEmailTakenAsync, sin descargar tablas para evaluar predicados arbitrarios.

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