Domain Driven Design: Afirmações

Em síntese, o Domain Driven Design (DDD) se revela não apenas como uma metodologia técnica, mas como uma estrutura de conceitos que transcende a construção de software, buscando alinhar a linguagem do código à linguagem do negócio. Ao enfatizar assertivas nos testes de unidade, conforme preconizado por visionários como Eric Evans e Vaughn Vernon, o DDD não só fortalece a estabilidade do sistema, mas também se torna uma ferramenta essencial para a comunicação efetiva das intenções do código. A ênfase na clareza, intencionalidade e na criação de código isento de efeitos colaterais não apenas valida requisitos de negócio, mas também estabelece uma documentação dinâmica que destaca as nuances das operações e propicia a evolução sustentável do software.

No desenvolvimento de software, onde a complexidade muitas vezes se torna o desafio central, temos uma abordagem transformadora conhecida como Domain Driven Design (DDD). Assim, concebido por Eric Evans em sua obra prima, ela oferece um conjunto de diretrizes e conceitos para construir sistemas de software de maneira mais alinhada com o mundo real e os domínios específicos nos quais operam. Então, o artigo Domain Driven Design: Afirmações explorará uma pouco esse tema.

Desse modo, à medida que os projetos de software crescem em escopo e complexidade, surge a necessidade de uma estrutura que vai além da mera organização de código. Além disso, essa aliança entre desenvolvedores e especialistas no domínio permite a criação de sistemas mais robustos, flexíveis e adaptáveis às mudanças frequentes nos requisitos.

Ao explorarmos as práticas fundamentais do DDD, especialmente focadas nas afirmações (assertions ou assertivas), descobrimos um caminho que não apenas simplifica a complexidade inerente ao desenvolvimento de software, mas também promove a criação de sistemas que reflitam de maneira mais precisa os desafios do domínio em questão.

Além disso, aqui no blog temos uma grande quantidade de artigos relevantes sobre Domain Driven Design. Alguns deles são suscitados no corpo desse post, mas vou indicar alguns outros, que embora não sejam obrigatórios, são complementares:

System design flexível

Para fazer um bom uso de abstrações e polimorfismos algumas práticas são essenciais. Criar bons nomes de métodos para que revelem suas intenções já é um bom começo. Mas também é importante observar os efeitos colaterais de operações e refatorá-las, reduzindo o escopo das consequências de um comando que chama outro e outro. Essas práticas são recomendadas pelo Eric Evans em sua obra prima.

Pois bem, vamos sugerir então uma máxima: as abstrações e os polimorfismos precisam ser realmente úteis. Isso significa que se você os utiliza, precisa garantir que além de gerar reaproveitamento de código e versatilidade, garanta um bom entendimento para os desenvolvedores. Estou dizendo que a intenção envolvida na ação precisa ser clara.

Delegados e emaranhados

Eventualmente sistemas possuem operações (comandos, consultas ou funções) que agem por suas maneiras particulares. Alguns comandos chamam outros comandos e outros, dificultando a compreensão das suas consequências, exigindo uma leitura mais profunda do código. Mas há também situações onde a computação é passada por parâmetro para métodos, como os ponteiros de função ou C/C++, delegates do C# ou interfaces funcionais do Java. Já citamos acima os polimorfismos e encapsulamentos. Tudo isso complexifica o código e precisa ser utilizado com o devido pudor.

Imagine uma situação em que a entidade recebe delegates por parâmetro, como base de suas ações. E a composição desses delegates é feita numa estrutura distante da entidade, mas que pode acessar banco de dados, sistema de filas ou quaisquer outras coisas. Isso não facilita a vida de novos programadores. Entenda que esse tipo de técnica é relevante mas deve ser utilizada com inteligência para não emporcalhar o código.

Domain Driven Design: Afirmações – Uma visão geral

Então, Evans sugere algumas formas de lidar com isso. A primeira é a interface reveladora de intenções. A ideia por traz desse conceito é construir métodos, classes, etc. que deixem claro o seu propósito. A segunda é a construção de operações isentas de efeitos colaterais. A ideia é notar que algumas operações modificam o sistema e outras não. Ao construí-las, separe os comandos das consultas e torne tudo mais fácil para outros desenvolvedores. Porém, fazer isso pode não ser suficiente, uma vez que provavelmente existirá comandos que chamam comandos e comandos… Para resolver essa questão há o que ele chama de Afirmações (Assertions). Particularmente não gostei da expressão Afirmações, que vem do livro traduzido para Português. Prefiro pessoalmente a expressão Assertiva, que explica melhor.

Pré-condições e pós-condições

Há um conceito muito conhecido quando se pensa em testes de unidade que é o AAA, ou Arrange-Act-Assert. Basicamente é uma estrutura de codificação em que se organiza as pré-condições de um dado cenário, as pós-condições e a execução do cenário em questão. Pois bem, muitas linguagens de programação suportam ou possuem frameworks particulares para lidar com isso. Veja um exemplo escrito em C# de 3 testes de unidade que utilizam essa forma.


[TestClass]
public class ReservaVooTest
{
    [TestMethod]
    public void ReservarVoo_ComAssentoDisponivel_ReservaConfirmada()
    {
        // Arrange
        var sistemaReserva = new SistemaReservaVoos();
        var voo = new Voo("Voo 123", DateTime.Now, DateTime.Now.AddHours(3));
        var assento = new Assento("A1");

        // Act
        var reserva = sistemaReserva.ReservarVoo(voo, assento);

        // Assert
        Assert.AreEqual(StatusDaReserva.Confirmada, reserva.Status, "A reserva não foi confirmada corretamente.");
    }

    [TestMethod]
    public void ReservarVoo_ComAssentoIndisponivel_ReservaNegada()
    {
        // Arrange
        var sistemaReserva = new SistemaReservaVoos();
        var voo = new Voo("Voo 456", DateTime.Now, DateTime.Now.AddHours(3));
        var assento = new Assento("A1");
        // Simular reserva prévia
        sistemaReserva.ReservarVoo(voo, assento);

        // Act
        var segundaReserva = sistemaReserva.ReservarVoo(voo, assento);

        // Assert
        Assert.AreEqual(StatusDaReserva.Negada, segundaReserva.Status, "A reserva não foi negada corretamente.");
    }

    [TestMethod]
    public void ReservarVoo_ComDataInvalida_ArgumentExceptionLancada()
    {
        // Arrange
        var sistemaReserva = new SistemaReservaVoos();
        var voo = new Voo("Voo 789", DateTime.Now, DateTime.Now.AddHours(3));
        var assento = new Assento("B2");
        var dataPassada = DateTime.Now.AddHours(-1);

        // Act & Assert
        Assert.ThrowsException<ArgumentException>(() =>
        {
            sistemaReserva.ReservarVoo(voo, assento, dataPassada);
        }, "Uma exceção deveria ser lançada para uma data inválida.");
    }
}

Note que essa forma ajuda a esclarecer o problema dos comandos que chamam outros comandos. Ao estabelecer um teste de unidade se evidencia uma intenção do sistema, considerando suas condições, invariantes e tudo mais como ponto de partida. Ao ler isso um novo programador consegue mais facilmente entender os potenciais impactos do cenário real. As pós-condições revelam o que se espera quando a ação é executada sob essa condição.

Tudo isso é muito simples de entender, porém o que não é necessariamente uma prática é o novo desenvolvedor consultar os testes de um sistema antes de começar a trabalhar. Fazendo isso ele tem acesso a informações relevantes das intenções e como elas interagem em cenários mais complexos. Todos os cenários complexos que não são visíveis nos testes são bons candidatos a serem testados futuramente.

Vaughn Vernon é outro teórico sobre Domain Driven Design bastante conhecido. Ele versa sobre uma forma padronizada de fazer validações de seus códigos. Ele chamou-a de Assertion Concern, criada para indicar para os desenvolvedores os impactos indiretos de operações num sistema, sem necessariamente construir testes de unidade. Nessa abordagem os testes se concentram na validação da funcionalidade em si e não necessariamente em informar intenções aos desenvolvedores. Para mais detalhes consulte o artigo Validation Pattern no Domain Driven Design.

Conclusão de Domain Driven Design: Afirmações

Em síntese, o Domain Driven Design (DDD) se revela não apenas como uma metodologia técnica, mas como uma estrutura de conceitos que transcende a construção de software, buscando alinhar a linguagem do código à linguagem do negócio. Ao enfatizar assertivas nos testes de unidade, conforme preconizado por visionários como Eric Evans e Vaughn Vernon, o DDD não só fortalece a estabilidade do sistema, mas também se torna uma ferramenta essencial para a comunicação efetiva das intenções do código. A ênfase na clareza, intencionalidade e na criação de código isento de efeitos colaterais não apenas valida requisitos de negócio, mas também estabelece uma documentação dinâmica que destaca as nuances das operações e propicia a evolução sustentável do software.

Em última análise, os testes de unidade, ao adotarem o padrão AAA e incorporarem assertivas, transformam-se em instrumentos valiosos para uma melhor compreensão do código, permitindo que os desenvolvedores não apenas construam sistemas funcionais. O resultado é a construção não apenas de software robusto, mas de sistemas que verdadeiramente refletem e se adaptam aos desafios específicos de seus domínios.


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.