Ir al contenido

Validación Fuertemente Tipada en Delphi: Presentando la Fluent Validation API de Dext

Dext Fluent Validation

La validación de datos es uno de los puntos más críticos de cualquier software. Por años, los desarrolladores de Delphi han dependido de extensos bloques condicionales (if/then) dispersos por el código o de strings mágicas para validar datos. Dext ya ofrecía una alternativa robusta con la Validación basada en Atributos ([Required], [Range]), pero las aplicaciones corporativas complejas demandaban una flexibilidad aún mayor.

Hoy, nos complace anunciar la finalización de la Fluent Validation API en Dext — un motor de validación basado en código fluido, fuertemente tipado y totalmente integrado con las Smart Properties (Prop<T>) del ORM y con el flujo de Model Binding Web.


Aunque decorar propiedades con atributos (como [Required] o [StringLength(3, 50)]) funciona muy bien para escenarios simples, el enfoque declarativo presenta limitaciones en reglas de negocio más complejas:

  1. Falta de Condicionales Dinámicas: No es sencillo validar un campo basado en el estado de otro en tiempo de ejecución (ej. “El correo electrónico es obligatorio solo si el tipo de notificación es Correo”).
  2. Dependencia de Strings Mágicas: Los validadores personalizados suelen requerir nombres de propiedades como string literals, introduciendo riesgos durante las refactorizaciones.
  3. Acoplamiento con la Entidad: Las validaciones quedan estáticamente ligadas a la clase de datos, lo que dificulta el uso de reglas diferentes para contextos de negocios distintos.

La Fluent Validation API de Dext introduce un enfoque programático limpio inspirado en FluentValidation de C#. Al heredar de TAbstractValidator<T>, usted declara las reglas de forma fluida en el constructor de la clase validadora utilizando el método RuleFor.

type
TUserValidator = class(TAbstractValidator<TUser>)
public
constructor Create; override;
end;
constructor TUserValidator.Create;
begin
inherited Create;
RuleFor('Name').Required.Length(3, 50);
RuleFor('Email').EmailAddress;
RuleFor('Age').Range(18, 99);
end;

Para ejecutar la validación de forma programática en su capa de servicio:

var
Validator: TUserValidator;
Result: TValidationResult;
begin
Validator := TUserValidator.Create;
try
Result := Validator.Validate(UserInstance);
try
if not Result.IsValid then
raise Exception.Create(Result.ErrorMessage);
finally
Result.Free;
end;
finally
Validator.Free;
end;
end;

1. Integración con Smart Properties (Tipado Fuerte sin Strings)

Sección titulada «1. Integración con Smart Properties (Tipado Fuerte sin Strings)»

Si sus entidades utilizan Smart Properties (Prop<T>, StringType, IntType), puede eliminar por completo cualquier string literal mapeando las propiedades a través de un objeto fantasma generado por Prototype:

constructor TOrderValidator.Create;
begin
inherited Create;
var m := Prototype.Entity<TOrder>;
RuleFor(m.CustomerName).Required.Length(3, 100);
RuleFor(m.Total).Range(1.0, 10000.0);
end;

[!NOTE] Internamente, Dext expone sobrecargas concretas de RuleFor para los registros Prop<string>, Prop<Integer>, etc. Esto evita conversiones implícitas erróneas a tipos básicos vacíos y garantiza que los campos se validen en tiempo de compilación.

2. Validaciones Personalizadas y Condicionales

Sección titulada «2. Validaciones Personalizadas y Condicionales»

Puede encadenar condicionales .When(...) y aserciones basadas en expresiones (Dext Style con Smart Properties) o métodos anónimos tradicionales:

// --- Estilo Dext (Recomendado - Usando Smart Properties y Expresiones) ---
// El helper "Model" (o el alias corto "M") se expone automáticamente en TAbstractValidator<T>
// Aserción personalizada directamente sobre expresiones del modelo
RuleFor(Model.Active = True).WithMessage('El usuario debe estar activo');
// Condicional fluido basado en expresiones fuertemente tipadas
RuleFor(Model.Email).Required.When(Model.Age >= 18);
// --- Estilo Tradicional (Fallback - Usando Strings y Métodos Anónimos) ---
// Útil para proyectos sin la infraestructura de Smart Properties de Dext
// Aserción personalizada con método anónimo
RuleFor('Active', function(Model: TUser): TValue
begin
Result := Model.Active;
end).Must(function(Val: TValue): Boolean
begin
Result := Val.AsBoolean = True;
end).WithMessage('El usuario debe estar activo');
// Validar correo electrónico solo si el usuario es mayor de edad
RuleFor('Email').Required.When(function(Model: TUser): Boolean
begin
Result := Model.Age >= 18;
end);

3. Registro de Patrones (TValidationPatterns)

Sección titulada «3. Registro de Patrones (TValidationPatterns)»

En lugar de repetir expresiones regulares complejas en varios validadores, Dext proporciona un registro centralizado y localizado:

// Registro en Startup
TValidationPatterns.Register('PostalCode', '^\d{5}$', 'fr-FR');
// En el Validador
RuleFor(m.PostalCode).MatchesPattern('PostalCode', 'fr-FR');

El gran diferencial de Fluent Validation en Dext es su acoplamiento automático con el Model Binding del servidor Web:

  1. Registro en el Contenedor de DI: Registre su clase validadora en los servicios durante el startup:
    Services.AddSingleton<IValidator<TUser>, TUserValidator>;
  2. Auto-Validación: Cuando una solicitud HTTP llega a un endpoint mapeado con el parámetro TUser, Dext localiza automáticamente el validador correspondiente en el contenedor DI y ejecuta la validación antes de que se invoque su método.
  3. Respuesta Estandarizada: Si hay fallos, la ejecución se interrumpe inmediatamente, lanzando una TWebValidationException, lo que devuelve automáticamente un estado HTTP 400 Bad Request con un JSON detallado que contiene los errores específicos de cada campo.

Validación Automática en el ORM (Dext Entity)

Sección titulada «Validación Automática en el ORM (Dext Entity)»

Además de validar al recibir solicitudes HTTP, Dext integra la validación de forma nativa en el ciclo de vida de persistencia de su ORM (TDbContext):

  1. Mismo Registro de DI: La misma inyección de dependencia registrada en el contenedor (Services.AddSingleton<IValidator<TUser>, TUserValidator>) es aprovechada por el ORM.
  2. Interceptación en SaveChanges: Durante la ejecución de SaveChanges o SaveChangesAsync, el TDbContext localiza automáticamente el validador registrado para cada clase de entidad modificada o insertada.
  3. Prevención de Datos Inválidos: La validación se ejecuta antes de generar los comandos SQL de inserción o actualización en la base de datos. Si falla, se lanza una excepción EValidationException con todos los mensajes de error, abortando inmediatamente la transacción e impidiendo que los datos inválidos lleguen a la base de datos.

Esta implementación ha sido ampliamente validada:

  • Pruebas Unitarias: Cobertura completa probando las reglas fluidas, condicionales, personalizadas y auto-validación en llamadas REST web, con absoluto éxito.
  • Compatibilidad con Versiones Anteriores: El código interno del framework fue escrito sin el uso de variables inline (var declaradas en medio del bloque), lo que garantiza la compatibilidade total de Dext con versiones de Delphi anteriores a la 10.4.

La validación fluida de Dext está finalizada, limpia y lista para proteger las reglas de negocio de su dominio.


Para profundizar en los detalles de implementación y revisar otros ejemplos prácticos de validación en el framework, consulte la documentación oficial: