Validation Pattern no Domain Driven Design

Validation Pattern no Domain Driven Design DDD

Falar de Domain Driven Design é, entre outras coisas, falar em ter um domínio consistente que reflete o modelo do negócio. Assim deve ser apoiado e entendido tanto pelos especialistas do negócio quanto pelos desenvolvedores. Pois bem, o domínio estável pressupõe classes bem definidas com métodos expressivos para todos os envolvidos. Mas, além disso, é fundamental que objetos dessas classes sejam consistentes todo o momento e para tal é fundamental que hajam validações constantes nas suas definições. O artigo Validation Pattern no Domain Driven Design tem por objetivo de explorar um pouco mais desse entendimento.

Eventualmente uma classe que represente uma entidade ou mesmo value object tem construtores que validam os atributos chamados. Mas factories podem suportar outros pseudo-construtores com outros possíveis estados consistentes para da entidade. Além disso seus métodos específicos também precisam seguir essa preocupação.

Caso seja novo no uso do DDD e não esteja entendendo bem alguns termos ou gostaria de ver outros pontos de vista do que você já conhece, por favor, veja nossos artigos relacionados ao tema:

A consistência de um agregado

Do ponto de vista da estratégia o Domain Driven Design orienta que o desenvolvimento se foque em observar o domínio, sua linguagem, seus jeitos e particularidades. A definição de modelos é uma técnica fundamental para alinhar a forma de pensar do especialista de domínio com o desenvolvedor. Nesse sentido os domínios devem ser claro para todos, bem como os agregados que os compõe.

Já do ponto de vista tático o domínio se apresenta através dos agregados com entidades e value objects – além de outras estruturas que não vêm ao caso agora. Pois bem, assim como a estratégia precisa estar bem ancorada no domínio e na linguagem, a tática precisa estar afinada com o correto entendimento do estado do agregado que está tratando.

Para entender melhor, considere por exemplo uma entidade produto. Essa entidade precisa ter um SKU (que é um ValueObject), e o preço precisa ser positivo. Nesse caso não deve haver qualquer possibilidade de se estabelecer um preço negativo ou não ter o SKU definido. Para isso deve haver um construtor padrão que exige tais valores e a validação deles. Além disso qualquer método dessa classe precisa garantir que tudo esteja ok todo o tempo, revalidando o agregado/entidade se necessário.

public class Produto
{
   public SKU CodigoSKU { get; private set;}
   public decimal Preco { get; private set;}

   public Produto(decimal preco, SKU codigoSKU)
        {
            if(codigoSKU == null || preco <= 0) throw new DomainException();
             
            this.CodigoSKU = codigoSKU ;
            this.Preco = preco;
        }
}

Validação, Open Close Principle e testes

O princípio “O” do SOLID é o Open/Close Principle, ou OCP. Esse é um dos princípios mais desafiadores de se manter. Esse princípio orienta o programador a escrever classes apenas um vez e depois não alterar mais. Porém deve ser possível aumentar as capacidades dessa classe por meio de outras estruturas. Literalmente o princípio diz: As classes devem estar abertas para extensão, mas fechadas para modificação.

Porém a coisa mais difícil é que o negócio (ou domínio) das empresas é vivo e dinâmico: eles precisam ser alterados. Para alterá-los e manter tudo estável deve-se utilizar estruturas para isso. Algumas estruturas comuns são baseados em padrões como builder, factory, strategy, etc.

Vale destacar que os testes unitários agradecem quando o princípio OCP é respeitado. Note que desse modo não há alterações na classe em questão, logo os testes que estão construídos continuam passando com sucesso. Só para comparar, num projeto que temos um BBOM (Big ball of mud) pequenas alterações costumam impactar nos testes e gerar grande retrabalho.

Assertion Concern do Vaughn Vernon

Vaughn Vernon é uma referência quando se fala em Domain Driven Design, escritor do livro Implementing Domain-Driven Design, o da “capa vermelha”. Ele estruturou uma forma tática chamada Assertion Concern (Assertivas de interesse, livre tradução) que ajuda a pensar como validar e tratar regras de domínio.

O Vernon presa muito pela expressividade do domínio mesmo para as validações. Por conta disso ele entende que métodos devem ser muito claros em cada validação que faz. Veja a seguir um pequeno trecho feito com base no repositório do próprio Vernon. O código a seguir está escrito em C# mas pode ser portado para praticamente qualquer linguagem de mercado.

 public class AssertionConcern
    {
        public static void AssertArgumentEquals(object object1, object object2, string message)
        {
            if (!object1.Equals(object2))
            {
                throw new InvalidOperationException(message);
            }
        }

        public static void AssertArgumentFalse(bool boolValue, string message)
        {
            if (boolValue)
            {
                throw new InvalidOperationException(message);
            }
        }

        public static void AssertArgumentLength(string stringValue, int maximum, string message)
        {
            int length = stringValue.Trim().Length
            if (length > maximum)
            {
                throw new InvalidOperationException(message);
            }
        }

        public static void AssertArgumentLength(string stringValue, int minimum, int maximum, string message)
        {
            int length = stringValue.Trim().Length
            if (length < minimum || length > maximum)
            {
                throw new InvalidOperationException(message);
            }
        }
   }

Note que há métodos bem específicos para cada validação. Seu projeto pode ter diversas classes para cada um de seus tipos, removendo da entidade as estruturas concretas de validação.

Domain Exceptions

Há uma outra técnica muito aplicada no mercado para empacotamento dos erros que ocorrem na aplicação. Já vi implementações em C# ou Java, mas grande parte das linguagens suportaria algo semelhante. Linguagens como GO que não possuem exception precisariam de abordagens mais específicas.

Bom, Domain Exceptions são estruturas de exceção específicas para o domínio. Note que as AssertionConcern citadas acima geram exceção do tipo InvalidOperationException que pode não ser interessante para todos os cenários. Empacotá-las em uma exceção específica para o domínio pode ser algo interessante.

public class DomainException : Exception
{
    public DomainException(string? message) : base(message)
    {
    }

    public DomainException(string? message, Exception? innerException) : base(message, innerException)
    {
    }
}

Frameworks de validação

Há diversos frameworks de validação para seus códigos. Em .NET é muito comum o uso do Fluent Validation, mas outra opção válida é o Fluent Assertion. Todas essas soluções são muito interessantes tanto para o código principal quanto para auxílio em testes unitários.

Veja a seguir um exemplo de um código C# utilizando o FluentValidation. Ele possui uma classe específica para validação, mas não pertencente ao código da entidade em si. Note também que ela possui diversas regras bastante flexíveis.

using FluentValidation;

namespace Anselme.Contatos.Domain.Aggregates;

public class ProdutoValidator : AbstractValidator<Produto> 
{
    public ProdutoValidator()
    {
        RuleFor(p => p.Nome).NotEmpty().NotNull().WithMessage("Nome não pode ser nulo ou vazio");
        RuleFor(p => p.Nome).Matches("^[a-zA-Z0-9_]*$").WithMessage("Nome não pode ter caracteres especiais");
        RuleFor(p => p.Nome).Length(0,255).WithMessage("Nome não pode ter mais do que 255 caracteres");
        RuleFor(p => p.Preco).LessThan(0).WithMessage("O preço não pode ser menor do que zero");
        RuleFor(p =>p.DataDeRegistro).GreaterThanOrEqualTo(new DateTime(2000,1,1)).WithMessage("A data de registro do produto não pode ser inferior a 01/01/2000");
    }
}

A seguir veja o código do produto utilizando essa regra. Caso o código seja invalidado note que ele gera uma exceção do tipo DomainException. Ela, por sua vez, tem também uma estrutura interna de Assertion Concern utilizando o método ThrowIfIsFalse(). Claro que se trata de uma situação didática, mas é interessante ver um código utilizando todas as técnicas ao mesmo tempo.

public void Validar(string nome, decimal preco, DateTime dataDeRegistro)
        {
            var validator = new ProdutoValidator();
            var result = validator.Validate(this);

            DomainException.ThrowIfIsFalse(result.IsValid,$"Produto inválido: {result.ToString()}");
            
        }

Note que é possível potencializar a expressividade de seu domínio utilizando todas as técnicas citadas, afim de evitar falhas na parte mais problemática de grandes projetos.

Conclusão sobre Validation Pattern no Domain Driven Design

A validação é algo um tanto quanto básico, mas se feito do jeito errado o custo do software pode ser maior do que deveria. Algumas técnicas podem ser utilizadas de forma a garantir a consistência dos agregados e a expressividade dos códigos, reduzindo a curva de aprendizado de novos integrantes aos projetos de TI. O artigo Validation Pattern no Domain Driven Design explora o tema demonstrando algumas técnicas de referência úteis para qualquer linguagem de programação.


Thiago Anselme
Thiago Anselme - Gerente de TI - Arquiteto de Soluções

Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.

Deixe um comentário