Pular para o conteúdo

Validação Fortemente Tipada em Delphi: Apresentando a Fluent Validation API do Dext

Fluent Validation do Dext

A validação de dados é um dos pontos mais críticos de qualquer software. Por anos, desenvolvedores Delphi dependeram de extensos blocos condicionais (if/then) espalhados pelo código ou de strings mágicas para validar dados. O Dext já oferecia uma alternativa robusta com a Validação baseada em Atributos ([Required], [Range]), mas aplicações corporativas complexas demandavam uma flexibilidade ainda maior.

Hoje, temos o prazer de anunciar a conclusão da Fluent Validation API no Dext — um motor de validação baseado em código fluente, fortemente tipado e totalmente integrado com as Smart Properties (Prop<T>) do ORM e com o pipeline de Model Binding Web.


Embora decorar propriedades com atributos (como [Required] ou [StringLength(3, 50)]) funcione muito bem para cenários simples, a abordagem declarativa apresenta limitações em regras de negócio mais complexas:

  1. Falta de Condicionais Dinâmicas: Não é simples validar um campo baseado no estado de outro em tempo de execução (ex: “E-mail é obrigatório apenas se o tipo de notificação for por E-mail”).
  2. Dependência de Strings Mágicas: Validadores customizados costumam exigir os nomes das propriedades em string literals, introduzindo riscos durante refatorações.
  3. Acoplamento com a Entidade: As validações ficam estaticamente presas à classe de dados, dificultando o uso de regras diferentes para contextos de negócios distintos.

A Fluent Validation API do Dext introduz uma abordagem programática limpa inspirada no FluentValidation do C#. Herdando de TAbstractValidator<T>, você declara as regras de forma fluida no construtor da classe validadora utilizando o 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 executar a validação de forma programática na sua camada de serviço:

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. Integração com Smart Properties (Tipagem Forte sem Strings)

Seção intitulada “1. Integração com Smart Properties (Tipagem Forte sem Strings)”

Se as suas entidades usam Smart Properties (Prop<T>, StringType, IntType), você pode eliminar completamente qualquer string literal mapeando as propriedades através de um objeto fantasma gerado pelo 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, o Dext expõe sobrecargas concretas do RuleFor para os registros Prop<string>, Prop<Integer>, etc. Isso evita conversões implícitas errôneas para tipos básicos vazios e garante que os campos sejam validados em tempo de compilação.

Você pode encadear condicionais .When(...) e asserções baseadas em expressões (Dext Style com Smart Properties) ou métodos anônimos tradicionais:

// --- Estilo Dext (Recomendado - Usando Smart Properties & Expressões) ---
// O helper "Model" (ou o alias curto "M") é exposto automaticamente pelo TAbstractValidator<T>
// Asserção customizada diretamente sobre expressões do modelo
RuleFor(Model.Active = True).WithMessage('O usuário precisa estar ativo');
// Condicional fluida baseada em expressões fortemente tipadas
RuleFor(Model.Email).Required.When(Model.Age >= 18);
// --- Estilo Tradicional (Fallback - Usando Strings e Métodos Anônimos) ---
// Útil para projetos sem a infraestrutura de Smart Properties do Dext
// Asserção customizada com 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('O usuário precisa estar ativo');
// Validar e-mail apenas se o usuário for maior de idade
RuleFor('Email').Required.When(function(Model: TUser): Boolean
begin
Result := Model.Age >= 18;
end);

Em vez de repetir expressões regulares complexas em vários validadores, o Dext fornece um registro centralizado e localizado:

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

O grande diferencial do Fluent Validation no Dext é seu acoplamento automático com o Model Binding do servidor Web:

  1. Registro no Container de DI: Registre sua classe validadora nos serviços durante o startup:
    Services.AddSingleton<IValidator<TUser>, TUserValidator>;
  2. Auto-Validação: Quando uma requisição HTTP chega em um endpoint mapeado com o parâmetro TUser, o Dext localiza automaticamente o validador correspondente no contêiner DI e executa a validação antes mesmo do seu método ser invocado.
  3. Retorno Padronizado: Se houver falhas, a execução é interrompida imediatamente, disparando uma TWebValidationException, o que retorna automaticamente um status HTTP 400 Bad Request contendo um JSON detalhado com os erros específicos de cada campo.

Além de validar no recebimento de requisições HTTP, o Dext integra a validação de forma nativa ao ciclo de vida de persistência do seu ORM (TDbContext):

  1. Mesmo Registro de DI: A mesma injeção de dependência registrada no contêiner (Services.AddSingleton<IValidator<TUser>, TUserValidator>) é aproveitada pelo ORM.
  2. Interceptação no SaveChanges: Durante a execução de SaveChanges ou SaveChangesAnsync, o TDbContext localiza automaticamente o validador registrado para cada classe de entidade modificada ou inserida.
  3. Prevenção de Dados Inválidos: A validação roda antes de gerar os comandos SQL de inserção ou atualização no banco de dados. Caso falhe, uma exceção EValidationException contendo todas as mensagens de erro é lançada, abortando imediatamente a transação e impedindo dados inválidos de chegarem ao banco.

Esta implementação foi amplamente validada:

  • Testes Unitários: Cobertura completa testando as regras fluentes, condicionais, customizadas e auto-validação em chamadas REST web, com sucesso absoluto.
  • Compatibilidade Retroativa: O código foi escrito sem o uso de variáveis inline (var declaradas no meio do bloco) internas nos arquivos do framework, assegurando total compatibilidade do Dext com versões do Delphi anteriores à 10.4.

A validação fluente do Dext está finalizada, limpa e pronta para blindar as regras de negócio do seu domínio.


Para aprofundar-se nos detalhes de implementação e conferir outros exemplos práticos de validação no framework, consulte a documentação oficial: