Domain Driven Design tático

Os 7 padrões do DDD Tático

O DDD pode ser observado por dois grandes pontos de vista: o estratégico e o tático. O DDD estratégico oferece elementos conceituais robustos que ligam a estruturação do software com as características particulares do negócio. Já o Domain Driven Design tático dá meios para que implementações reais, independentes de linguagem de programação, que consigam garantir a entrega das necessidades estratégicas. Muitos veem o DDD apenas do ponto de vista tático – com seu conjunto de Patterns – mas entenda que somente esse ponto sem a estratégia há um empobrecimento de seu uso e, de certo modo, destrói-se a implementação do DDD. Veja nesse artigo os 7 padrões do DDD tático e como eles podem ser utilizados.

Visão Geral dos padrões táticos

Entendida a questão estratégica, vamos observar como o Domain Driven Design tático se conecta a ela. Ao observar um mapa de contexto (context map) vê-se que os sub-domínios têm relações relevantes e que possuem necessidades particulares. Os padrões táticos exploram um sub-domínio em específico ao invés dessa visão mais ampla fornecida pela estratégia.

Então que problema essa parte tática resolve? Vamos ser pragmáticos: um sub-domínio de Clientes certamente terá estruturas para lidar com as regras de negócio de Clientes. Nesse caso teremos métodos como: CadastrarNovoCliente() ou SubmeterDocumentacaoParaRevisao() ou coisas do tipo. A tática lida com essas características mais intermediárias.

Vale destacar que quando se fala de DDD, seja tático ou estratégico, não estamos falando de arquitetura de microserviços (microservices). Embora essa abordagem de projetos seja muito alinhada com essa arquitetura, não há uma relação direta. Ou seja, é completamente possível e razoável criar um sistema monolítico, modularizado, utilizado DDD. Essa decisão arquitetural deve levar em consideração outras características que não serão exploradas aqui.

Mas além de tudo isso gostaria de reforçar um ponto importante: Domain Driven Design não é padrão de pastinhas do VSCode (e afins). É comum ter um padrão de pastas de referência e dizer que por te-las desse modo tem-se DDD: não é isso! Essa abordagem presa por alguns conceitos. Se eles não forem observados, não há Domain Driven Design.

Tendo todo esse arcabolso em mente, vamos ver, en passant, quais são os padrões que existem. A figura a seguir está no livro azul do Eric Evans (Domain-Driven Design: Atacando as complexidades no coração do software, 2003). Ela oferece uma visão geral desses padrões e como eles se relacionam. Ao longo do artigo vamos ver isso em mais detalhes.

Smart UI, Model-Driven Design, Layered Architecture, Services, Entities, Value Objects, Factories, Aggregates, Repositories. Essa é um diagrama de Eric Evans sobre a relação entre os padrões táticos do Domain Driven Design.
Diagrama com a relação entre os padrões táticos do DDD segundo Eric Evans

Domain Model Pattern

Já esse diagrama produzido aqui por nossa equipe demonstra de uma maneira um pouco mais simplificada as entidades, value objects, como eles se agrupam em agregados e como eles se relacionadam com os serviços e eventos de domínio, bem como se relacionam com as factories e repository. Veja a seguir detalhes sobre cada uma delas.

Smart UI, Model-Driven Design, Layered Architecture, Services, Entities, Value Objects, Factories, Aggregates, Repositories. Esse diagrama relaciona os padrões táticos do Domain Driven Design por uma abordagem específica do Anselme.com.br
Diagrama com a relação entre os padrões táticos sob a visão do anselme.com.br

1 – Entity / Entidade

Uma Entidade é uma estrutura rica, portanto não anêmica. Isso significa que seus atributos, métodos, etc. não apenas trazem características técnicas como getters/setters, CRUD ou validações básicas, mas sim representam o que o negócio espera dele. Esse é um conceito importante é muito confundido. Para não ser anêmica a classe precisa ter métodos como SolicitarDespejoDeMaterialQuimico, ou seja, possui evidente e clara intensão do negócio no seu corpo. E além disso a Entidade possui um significado claro para o DomainExpert, de tal modo que se ele lesse a assinatura do método ele entenderia minimamente, mesmo sem saber programação.

Normalmente entidades não possuem seus setters expostos para fora da estrutura ou classe. Nesse caso, um getter seria público mas seu setter seria privado.

Entidade e identidade

Além disso, entidades são identificáveis. Ou seja, ela tem ID. Pode ser int, UUID, GUID, uniqueidentifier, ou qualquer outra coisa, mas ela possui uma identidade única. Veja que não estou falando necessariamente de como será a sua relação com o banco de dados no momento.

Outro ponto importante da entidade é que ela costuma possuir um construtor. Desse modo é convidativo que se preencha os parâmetros necessários para que a classe de forma clara. Isso torna desnecessario o uso de setters avulsos. Veja também DDD Estratégico.

Por fim, as entidades devem sempre manter-se consistentes. Nesse caso, não deve ser possível preencher um atributo nome sem ter preenchido um atributo sobrenome, por exemplo. Toda vez que qualquer método é utilizado há de se fazer uma validação e garantir sua consistência. Lembrando, não estou falando nada de banco de dados no momento. A consistência que me refiro é na aplicação em relação ao que o domínio diz.

public class MaterialQuimico
{
  public Id {get; private set; }

  public void SolicitarDespejoDeMaterialQuimico(Responsavel responsavel_pelo_despejo)
  {
       // Detalhes da implementação do método

       Validar();
  }

  public void Validar()
  {
      // ....
      throw new Exception("estrutura inválida");
  }
  
}

2 – Value Object / Objeto de Valor

O Value Object tem uma relação muito clara com a entidade mas com algumas particularidades. Bom, pense que existe uma método CadastrarNovoCliente com os parametros nome, sobrenome e telefone. Esse é um método bastante simples de ser entendido, porém, ele pode ter implementações diferentes. Veja que ele pode retornar vazio (void, dependendo da linguagem) e ter 3 strings como parâmetro. Isso parace razoável, porém o uso de tipos primitivos é algo muito natural mas pode prejudicar parte da semântica do código.

Os códigos devem evidenciar a sua semantica todo o momento, refletindo assim, inequivocamente o domínio. Então, uma alternativa seria, ao invés de usar os parametros nome, sobrenome como string, poderia-se ter uma classe ou estrutura para isso chamada nome. Essa, por sua vez, teria um construtor e faria a validação de seus dados, por ela mesma. A mesma coisa poderia ser feita pelo telefone. Desse modo a entidade tenderia a não ter tipos primitivos.

ValueObjects apoiando as Entities

Pois bem, essas classes ou estruturas auxiliares ao domínio são as tais das Value Objects. Elas, ao contrário das entities, não possuem ID. Além disso elas devem seguir um conceito de imutabilidade. Ou seja, elas não podem ser alteradas por dentro. Para alterar o nome, no exemplo citado, deveria-se criar uma nova instância da classe Nome.

A implementação prática é bem simples, mas o conceito por trás é fundamental para a correta implementação do Domain Driven Design tático. Veja a seguir um pequeno exemplo com esse modelo:

public struct Responsavel
{
  string matricula_do_solicitante;
  string nome;
  string setor;

  public Responsavel(string matricula_do_solicitante, string nome, string setor)
  {
    this.matricula_do_solicitante = matricula_do_solicitante;
    this.nome = nome;
    this.setor= setor;

    Validate();
  }   

  public void Validate()
  {
     if(this.nome == "") throw new Expcetion("Nome inválido");
  }
}

3 – Aggregate / Agregado

Entenda que no Domain Driven Design estratégico há os sub-domínios que possuem contextos delimitados (bounded contexts). Já esses contextos possuem entities e value objects, alguns que têm relação direta e outros não necessáriamente. As entities que se interrelacionam com outras ou com value objects são chamadas de aggragates.

Vamos a uma situação já citada, um método CadastrarNovoCliente(NomeCompleto clientenovo, Telefone telefone). Esse é um método da entidade Cliente que tem relação com 2 Value Objects. Trata-se de uma agregação clara. Outro exemplo pode ser uma Entidade chamada OrdemDeServiço que tem uma relação com uma outra entidade chamada ItemDeNotaDeServiço, ambas com suas ValueObjects. Nesse caso todas elas também fazem parte de uma só agregação.

Agregações orientam o contexto

As agregações são conjuntos de entidades e value objetos que têm relação e participam de um contexto. Mas note que num mesmo contexto pode haver várias agregações e uma agregação até pode ter alguma relação com outra, mas não podem ter entidades diretamente relacionadas. Por exemplo, a entidade Cliente e OrdemDeServiço podem estar num mesmo contexto, mas cada uma é uma agregação diferente. Mas a OrdemDeServiço deve ter o cliente relacionado. Fica mais ou menos assim:

public class Cliente
{
 // Implementação do cluiente
}

public class OrdemDeServiço
{
  int ClienteId;     // Implementação correta
  // Cliente cliente;   // Implementação errada!
}

Veja que como são de agregados diferentes a relação tem que ser por id e não por um objeto da outra classe. Isso gera menor acoplamento e facilita futuras separações em arquivos, sistemas, micro-serviços, bancos de dados ou até equipes diferentes. Trata-se de uma questão visualmente muito simples, mas a fundamentação é bastante profunda.

Note também que uma agregação é apenas um conceito e não há nenhuma obrigatoriedade de indicar claramente que uma classe é um agregado. Isso é uma opção e se trata de complexidade de implementação: faça se achar conveniente.

4 – Domain Services / Serviços do Domínio

Essas são estruturas especiais utilizadas essencialmente quando uma Entity ou uma Value Object não se adequam a ação que se deseja fazer. Por exemplo, se a ideia for obter o somatório de todos os saldos das contas existentes num sistema. Apenas uma entidade Conta em específico não pode dar isso e nem nenhum Value Object. Nesse caso faz-se necessário o uso de Domain Services.

Há também um segundo cenário em que os Domain Services são úteis. Imagine que uma operação precisa ser feita com dois ou mais agregados diferentes. Por exemplo, se a intenção é obter todos os Clientes com saldo negativo na conta. Um Domain Service pode ser a solução para esse cenário.

Perceba que os Domain Services não podem possuir estado (são stateless) e, por isso, em várias implementações eles utilizam classes estáticas. Mas, muito cuidado! Não utilize isso para tudo deixando suas entidades anêmicas. Isso pode ser um sinal de que o conceito não está sendo bem implementado.

public static ClientesService
{
  public static decimal ObterSaldoAcumuladoDeTodasAsContas()
  {
      // implementação específica para o cenário
  }

  public static list<Cliente> ObterClientesComSaldoNegativo()
  {
      // implementação específica para o cenário
  }
}

5 – Domain Events / Eventos do domínio

Os Domain Events são uma ferramenta poderosa para o design de sistemas orientados a eventos, pois permitem que os desenvolvedores capturem eventos importantes que ocorrem dentro do sistema e os usem para orquestrar ações em diferentes partes. Isso pode ajudar a melhorar a escalabilidade, a modularidade e a extensibilidade de um sistema, permitindo que diferentes partes do sistema respondam de maneira independente aos eventos que ocorrem dentro do domínio.

Existem dois tipos principais de eventos de domínio: os eventos internos e os eventos externos. Os internos são aqueles que ocorrem dentro do próprio domínio em questão, sem efeitos colaterais em outros sistemas ou serviços. Já os eventos externos, pelo contrário, são aqueles que têm efeitos colaterais em outros.

Evento: O que, quem e quando

Os Domain Events são geralmente implementados como objetos imutáveis que encapsulam informações sobre o evento (o que ocorreu, por quem e quando). Quando um evento ocorre, ele é registrado pela entidade ou objeto de valor relevante e enviado para um objeto chamado de Event Dispatcher, que é responsável por notificar outras partes do sistema que podem estar interessadas no evento.

Não é incomum relacionar a estrutura de eventos com sistemas de mensageria como RabbitMQ, ActiveMQ, Kafka, etc. Eles auxiliam nesse modelo e dão robustez a um uso mais extensivo dos conceitos da EDA (Event Driven Architecture).

Definitivamente esse é um tema bastante complexo e profundo e não cabe colocar muitos detalhes por aqui. Mas vale entender tecnicamente que há 3 estruturas fundamentais: o event (onde, quando e oque), o eventhandler (ação executada quando o evento ocorre) e o eventdispacher (registrador e organizador dos eventos).

// Classe utilizada para representar o conteúdo do evento
public class PedidoConcluidoEvent
{
    public Pedido Pedido { get; set; }
}

// Entidade disparando o evento
public class Pedido
{
    public event EventHandler<PedidoConcluidoEvent> Concluido;
    public void ConcluirPedidoDeCompra()
    {
        OnPedidoConcluido(new PedidoConcluidoEvent { Pedido = this });
    }

    protected virtual void OnPedidoConcluido(PedidoConcluidoEvent e)
    {
        EventHandler<PedidoConcluidoEvent> handler = Concluido;
        handler?.Invoke(this, e);

    }
}
// Um serviço (de exemplo) acionado como efeito colateral do evento disparado
public class EmailService
{
    public void EnviarEmailPedidoConcluido(object sender, PedidoConcluidoEvent e)
    {
        Console.WriteLine($"Enviando email de confirmação para {e.Pedido.Cliente.Email}");
    }
}
// Registrando o evento à entidade
var pedido = new Pedido();
var emailService = new EmailService();
pedido.Concluido += emailService.EnviarEmailPedidoConcluido;
pedido.Concluir();

6 – Repository / Repositório

Esse é um dos padrões mais conhecidos, responsável por abstrair as relações com banco de dados do domínio. Ele normalmente é conectado através de injeções de dependência para que não haja acoplamento entre as estruturas.

Devemos ter um repositório por agregado. Esse é um conceito mais específico que nem sempre é respeitado. Não é interessante ter um repositório por entidade, por exemplo. Desse modo as estruturas ficam mais aproveitáveis e fazem até mais sentido. Pensa no caso da OrdemDeServiço e dos ItensDaOrdemDeServiço: um só repositório torna tudo muito mais fácil.

Repositórios e seus modelos

Os repositório devem ter modelos próprios, completamente separados das entidades. Isso por que o modelo deve representar o banco de dados, em especial se a aplicação estiver usando um ORM. Já o domínio não deve ter nenhuma preocupação com como é a implementação de banco de dados. Num caso mais específico, você pode ter uma entidade Cliente com um value object Endereço. Uma possível implementação de banco de dados pode ter uma tabela Cliente e outra Endereço com uma relação de 1 para 1. Em outra implementação basta a tabela Cliente com o Endereço em suas colunas. Isso não é um problema do domínio, mas sim da infraestrutura da aplicação.

As agregações precisam sempre estar consistentes. Isso já foi explicado quando falamos de Entity/ValueObject/Aggregate. Mas isso também é verdadeiro na camada de infraestrutura. Pode existir, por exemplo, um método findOne() que obtém um Cliente. Esse cliente deve ter os dados e suas estrutura adequados para ser utilizado por qualquer ponto da aplicação.

Como comentei o domínio não precisa conhecer o repositório, devendo ser acessado através de indireções, como injeções de dependência. É comum haver implementações de interfaces com os métodos que ficarão disponíveis para acesso e os repositórios concretos utilizando o banco de dados. As entidades e demais estruturas devem consumir tais abstrações.

// Interface repositório
public interface Repository<T>
{
 void Create<T>(T t);
 T ReadOne();
 IList<T> ReadAll();
 Update(T t1, T t2);
 Delete(T);

}
// Repositório concreto
public class ClienteRepository: repository<Cliente>
{
 void Create<Cliente>(Cliente t)
 {
   // Implementação específica
 }

 // Demais implementações
  
}
// Uso do repositório em uma entidade
public class MaterialQuimico
{
  public Id {get; private set; }

  public void SolicitarDespejoDeMaterialQuimico(Responsavel responsavel_pelo_despejo)
  {
       using(IRepository<MaterialQuimico> repo = Service.Get<MaterialQuimicoRepository>())
  {
      repo.Create(....);
  }

       Validar();
  }

  public void Validar()
  {
      // ....
      throw new Exception("estrutura inválida");
  }
  
}

7 – Factories

Esse é um padrão de projetos bastante conhecido por fazer parte dos padrões GOF, mas possui um destaque especial no tema do Domain Driven Design tático. Veja que não é raro ser necessário criar diferentes estruturas sob um mesmo padrão mas variando por tipo. Vamos ser mais práticos: imagine ter uma entidade Produto e outra entidade chamada ProdutoReajustadoComImpostoNovo. Ambas as classes podem implementar uma mesma interface IProduct. A Factory faria o trabalho sujo de devolver um IProduct do tipo correto a depender dos parâmetros passados.

public interface IProduct
{
   SKU ObterSKU();
   void efetuarCompra();
   decimal gerarPrecoFinal();
}

public class Produto: IProduct
{
  // Implementação específica do produto
}

public class ProdutoReajustadoComImpostoNovo: IProduct
{
  // Implementação específica do produto com o imposto novo
}
public class ProdutoFactory
{
  public IProduct ObterProduto(string nomeDoProduto, decimal valor, bool comImposto)
  {
     if(comImposto)
          return new ProdutoReajustadoComImpostoNovo(nomeDoProduto,valor);
     else
          return new Produto(nomeDoProduto,valor);
  }
}

Referências do DDD estratégico

Aqui no blog temos alguns artigos que dissertam sobre a questão estratégica do Domain Driven Design. Vale a pena conferir para ver com mais profundidas mas não são obrigatórios:

Conclusão de Domain Driven Design tático

O artigo Domain Driven Design tático traz uma visão geral com certo nível de detalhes de cada um dos padrões sugeridos pelo Eric Evans. Alguns padrões são mais ou menos complexos do ponto de vista técnico, mas é importante sempre ter em mente os conceitos por traz de cada um deles. Trata-se de uma das formas mais eficazes de retratar as necessidades de negócios complexos garantindo a geração de valor.


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