Domain-driven design (DDD) não é tema novo (Eric Evans formalizou o conceito há mais de uma década). Entretanto, é curioso como, até hoje, os conceitos básicos sobre o assunto despertam interesse, principalmente em comunidades .NET. Ainda mais curioso é como esses mesmos conceitos são erroneamente interpretados.
A fundação de DDD está no conceito de linguagem onipresente (ubiquitous language, no original). Não está em determinar o que é uma entidade, objeto de valor, agregado, serviço ou repositório.
A impressão que temos é que quem começa a estudar DDD, lê as primeiras páginas do livro do Evans, ou do livro do Vernon, e pensa ter “captado a ideia” de ter especialistas do domínio e especialistas de tecnologia falando a mesma língua. Então, pula direto para as páginas que descrevem os padrões.
É comum falarmos com pessoas de TI que pensam que basta fazer com que o nome das classes e das propriedades, escritas por programadores, estejam de acordo com o vocabulário dos especialistas de domínio. Entretanto, essa é uma simplificação grosseira.
Para entender a importância da linguagem onipresente, comecemos pela “entidade” listada abaixo:
public class Employee { public string Id { get; set; } public string Name { get; set; } public string Cpf { get; set; } public decimal Salary { get; set; } }
Eis aqui um bom exemplo de implementação anêmica! Mas, antes de continuar lendo, tente imaginar o porquê.
Em nossas consultorias, quando usamos esse exemplo, ouvimos sugestões de que esta classe carece de comportamentos (O que está certo!). Entretanto, quando questionamos quais seriam os tais “comportamentos”, constatamos que as pessoas estão repetindo argumentos genéricos (de gente que também não “pegou” a ideia) e não tem condições de identificar o que há de errado!
Uma das argumentações mais frequentes é de os “setters” das propriedades deveriam estar privados. No lugar deles, deveríamos ter métodos “DefinirXXX” que implementariam validações. Concordemos, entretanto, que essa ideia não faz sentido, pois o propósito dos “setters” é, exatamente, definir valores para as propriedades e não haveria problema algum do código para validação estar implementado neles.
[tweet]Os métodos de uma entidade deveriam explicitar as motivações para suas mudanças de estado. [/tweet] Além disso, deve haver pelo menos um construtor capaz de instanciar a entidade colocando-a, desde o início, em estado válido.
Não é incomum encontrarmos implementações de propriedades que verificam, por exemplo, se o valor que se está tentando atribuir é diferente de nulo. Entretanto, nulo é o valor default do tipo e é assumido quando a classe é instanciada.
Vejamos outro exemplo de classe anêmica e sem valor para o domínio.
public class Customer { public string Name { get; private set; } public string Email { get; private set; } public DateTime BirthDate { get; private set; } public Customer(Guid id, string name, string email, DateTime birthDate) { Id = id; Name = name; Email = email; BirthDate = birthDate; } }
Na classe acima temos um “record” comum para uma implementação funcional. Mas, não temos uma entidade. A ideia até faria sentido em uma linguagem puramente funcional onde a “entidade” fica espalhada conceitualmente em diversas funções que definem comportamento, mas, em C#, não tem sentido algum.
Vamos a uma segunda tentativa com nossa classe Employee:
public class Employee { public string Id { get; private set; } public string Name { get; private set; } public string Cpf { get; private set; } public decimal Salary { get; private set; } public Employee(string name, string cpf) { //.. } public void RaiseSalary(decimal amount) { //.. } }
Dessa vez, a entidade é potencialmente menos anêmica. Afinal, estão claras as motivações que levam a mudança de valor das propriedades. Perceba como, inclusive, fica mais claro o valor da escrita de testes. Afinal, em uma classe anêmica (só com propriedades), os testes validariam a capacidade do C# de aceitar mudanças de valor (o que a Microsoft, com certeza, já testou e garante).
O problema do código acima está no método “dar aumento”. Por quê? Por essa não ser a forma como especialistas de domínio descrevem essa operação (fale com alguém de RH e irá ouvir expressões como “movimentação salarial”, “acordo coletivo” e “dissídio”. Mas, não ouvirá nada sobre “dar aumento”). Ou seja, o código não está fiel a linguagem onipresente.
Para pensar…
Identificar e implementar a linguagem onipresente não serve apenas para definir nome de classes ou de propriedades. A linguagem onipresente deve se revelar, principalmente, nas motivações para as mudanças de estado de nossas entidades de forma explícita.
Entender, de verdade, a linguagem onipresente é muito mais importante do que aprender os padrões. Sozinhos, o valor deles é nulo.
Preocupe-se, primeiro, em aprender o mais importante. Dedique tempo e esforço para entender bem isso. Depois, pode até fazer sentido pensar em associar DDD com conceitos mais técnicos geralmente entendidos como mais avançados como CQRS, sagas, event sourcing e tudo mais (complexidade de solução técnica, mal justificada, é só custo!).
Muito bom, valeu pelo post Elemar, aguardando mais dessa série!
Achei que faltou dar exemplo concreto do que seria entidade com método ubiquotous language, ou seja, faltou exemplo Beneficiario requer carteirinha algo mais fácil das pessoas assimilarem…
Olá Anderson. Tudo bom? Obrigado pelo feedback.
Meu argumento, no post, é que a linguagem Ubíqua deve se revelar, principalmente, na definição dos métodos que modificam as propriedades de uma entidade.
No meu exemplo, a classe Employee (ou mesmo a classe Customer) possuem apenas propriedades sem qualquer método. Minha defesa é que essas classes deveriam ter métodos que indicassem, claramente, os motivos para a mudança de suas propriedades.
No caso do Employee, por exemplo, quais são as motivações para a mudança do valor das propriedades? Um aumento de salário? O nome mudaria por alteração do estado cívil (que nem está relacionado entre as proprieades)? Métodos que revelam esses motivos poderiam (e deveriam) substituir os setters.
Me explica um pouco melhor sua ideia do beneficiário.
Eleimar, concordo em tudo que você falou mas queria discutir algo contigo. A linguagem onipresente eu vejo que é o mais importante no DDD.
Mas ai eu entro naquela dúvida, “Estou escrevendo meu código pra um cliente que fala sobre Empregado e Cpf”, eu deveria escrever uma Classe “Employee” (em inglês)? Não estaria eu, ferindo a linguagem onipresente? Fora termos como Cpf, dissídio, e outros que só existem na nossa língua, como resolver este caso?
Parabéns pelo post e por alguns insights dados, que todos deveríamos refletir sobre, como o caso dos testes.
Além da linguagem onipresente, acho importante chamar atenção para a essência da abordagem “domain driven”, e a prática de modelagem de domínio que Evans propõe (com a identificação do “core” de nosso domínio, domínios de suporte e a delimitação dos contextos). Essa prática estrapola o último “D” de seu livro (que no final do dia está também fortemente associado a Design de código), e nos leva a utilizar o mesmo raciocínio para uma série de outras disciplinas, como arquitetura de software e solução, modelagem de dados, etc.
Abs!
Acho que compartilho da opinião do Anderson, penso que o texto poderia ser enriquecido com um exemplo prático de como a entidade poderia evoluir a partir da identificação de um comportamento do domínio. Talvez utilizando os próprios exemplos de expressões que os especialistas no domínio utilizam comentados como “movimentação salarial” ou “dissídio”.
Parabéns pela série!
*ubiquitous