.NET - Usando a abordagem DDD : Padrões e práticas - I


 Hoje veremos como podemos usar a abordagem Domain Driven Design na plataforma .NET apresentando os padrões e práticas relacionadas.

O DDD - Domain Driven Design, é uma filosofia de desenvolvimento de softwares complexos usando boas práticas na qual a estrutura e a linguagem do seu código (nomes de classe, métodos, variáveis, etc.) devem estar focados no modelo de domínio ou negócio.

Encare o DDD como uma prescrição de metodologia e processo para o desenvolvimento de sistemas complexos cujo foco é mapear atividades, tarefas, eventos e dados dentro de um domínio de problema nos artefatos de tecnologia de um domínio de solução.

Assim  o DDD não é arquitetura, não é tecnologica, não é framework e não é linguagem.

O objetivo deste artigo é apresentar as práticas e padrões mais usados ao se adotar a abordagem do Domain Driven Design para o desenvolvimento de uma aplicação na plataforma .NET.

Vamos iniciar apresentando os principais conceitos usados na abordagem do Domain Driven Design que você tem que conhecer para iniciar o desenvolvimento.

Conceitos básicos do Domain Driven Design

A seguir vou apresentar de forma bem resumida (resumidíssima) os conceitos básicos do Domain Driven Design que você deve conhecer:

Domínio de Negócios: É a área específica de conhecimento relacionada à sua aplicação ou problema, como um sistema de e-commerce, sistema bancário, saúde, etc.

Modelagem Ubíqua: Envolve criar uma linguagem comum que seja compartilhada por todas as partes envolvidas no projeto (desenvolvedores, especialistas em domínio, etc.). Isso ajuda a evitar mal-entendidos e a garantir que todos falem a mesma língua.

Bounded Contexts: São limites delimitados dentro dos quais os termos do domínio têm significado específico. Um sistema complexo pode ser dividido em múltiplos bounded contexts.

Entidades: Representam objetos no domínio com identidade própria. Elas possuem atributos e podem ter métodos que encapsulam a lógica de negócios.

Value Objects: São objetos imutáveis que representam um conceito no domínio, mas não têm identidade própria. Eles são definidos apenas por seus atributos e são usados para encapsular valores.

Aggregates: São grupos de entidades e value objects relacionados que são tratados como uma única unidade transacional. Cada aggregate possui uma raiz de aggregate (Aggregate Root) que atua como o ponto de entrada e controle para o aggregate.

Agregação de Raiz (Aggregate Root): É a entidade principal dentro de um aggregate que atua como ponto de entrada para todas as operações no aggregate. Ela garante a consistência do aggregate.

Repositórios: São responsáveis por persistir e recuperar aggregates. Eles fornecem uma interface para acessar os dados do domínio sem expor detalhes de armazenamento.

Eventos de Domínio: São eventos que representam mudanças significativas no estado do domínio. Eles podem ser usados para comunicar e sincronizar diferentes partes do sistema.

Teste de Domínio: Use testes de unidade para verificar o comportamento das entidades e agregados, garantindo que a lógica de negócios seja implementada corretamente.

DDD e Arquitetura : O DDD pode ser implementado em conjunto com a Arquitetura Hexagonal (ou Ports and Adapters), a Clean Architecture, ou qualquer outra arquitetura que seja aderente aos princípios do DDD de forma a separar claramente o código de domínio das preocupações de infraestrutura.

Não pense que basta ler essas definições para entender o básico sobre o DDD, não, o DDD é uma disciplina complexa que exige prática e estudo contínuo para dominar completamente. Comece com projetos pequenos e gradualmente ganhe experiência na aplicação desses conceitos.

Obs: A leitura do livro 'Implementando Domain-Driven Design' de Vaughn Verson vai te dar uma visão sólida sobre os conceitos do DDD.

Configurando a Estrutura do Projeto

Um projeto bem estruturado é crucial para uma implementação bem-sucedida do DDD. Ele permite que os desenvolvedores localizem e gerenciem códigos com mais eficiência, promovem a separação de interesses e incentivam o uso de padrões claros e consistentes em todo o aplicativo.

Uma estrutura de projeto bem organizada facilita a navegação, reduz o acoplamento e melhora a capacidade de manutenção. Ele também garante que cada camada da aplicação cumpra o Princípio de Responsabilidade Única (SRP), com foco específico nas questões de domínio, aplicação e infraestrutura.
Estrutura de pasta recomendada:

Para um projeto .NET baseado criado usando a abordagem do DDD, é recomendado separar o código em quatro camadas principais: Apresentação, Domínio, Aplicativo e Infraestrutura ou Presentation, Domain, Application e Infrastructure.

A estrutura de pastas para tal projeto pode ser semelhante a esta:

Nesta estrutura, cada camada possui uma pasta e um projeto correspondente:

Projeto.Domain: Contém o modelo de domínio, incluindo entidades, objetos de valor e eventos de domínio.

Projeto.Application: Inclui casos de uso, comandos, consultas e seus respectivos manipuladores, com foco na coordenação de tarefas e na delegação de trabalho às camadas de domínio e infraestrutura.

Projeto.Infrastructure: Lida com persistência, serviços externos e outras questões técnicas e de baixo nível. Aqui geralmente estão as implementações dos repositórios e de outros recursos usados nas camadas internas.

Projeto.Presentation : Esta camada é responsável pela interação direta com os usuários ou outros sistemas externos e é onde a interface do usuário é criada e mantida.

Os projetos Domain, Application e Infrastructure geralmente são criados usando o template Class Library,  e o projeto Presentation usa um template para criar um projeto ASP.NET Core Web API ou um template para criar um projeto ASP.NET Core MVC.

Aqui estou considerando apenas os testes de unidade que se concentram em testar unidades individuais de código, como métodos de classes, isoladamente, sem depender de componentes externos ou da interação com a camada de apresentação. Isso ajuda a garantir que cada parte do código funcione corretamente por si só.

Por isso não temos uma camada Presentation em tests, isso se deve ao fato de que a camada de apresentação, que lida com a interação direta do usuário e a lógica de interface do usuário, é mais frequentemente testada por meio de outros tipos de testes, como testes de integração ou testes de aceitação.

Dentro de cada camada, as classes devem ser organizadas usando namespaces e uma estrutura de pastas lógica.

Aqui está uma descrição mais detalhada da organização de cada camada:

  1. Camada de Apresentação (Presentation):
  1. Domínio (Domain):
  2. Aplicação (Application):
  3. Infraestrutura (Infrastructure):

A seguir segue uma sugestão de estrutura de pastas para cada um dos projetos descritos acima:

1- Domain

Organize as classes de domínio por tipo (por exemplo, entidades, objetos de valor, eventos de domínio) e, se necessário, categorize-as ainda mais com base em seus subcontextos de domínio específicos.
Nota: Na pasta Abstractions são definidas as interfaces dos repositórios usados no projeto.

2- Application

Aqui agrupamos as classes da camada de aplicação por suas funções, como comandos, consultas e seus manipuladores correspondentes. Além disso, incluimos serviços e validadores que lidem com questões transversais. Também podemos definir os DTOs - Data Transfer Objects usados.

3- Infrastructure

Organize as classes da camada de infraestrutura com base em sua finalidade, como persistência de dados, integração de serviços externos, configuração e métodos de extensão.etc.

4- Presentation

Nesta camada podemos ter controladores, view models ou models e outros recursos relacionados com a camada de apresentação que pode variar bastante

Na próxima parte do artigo vamos apresentar os padrões e práticas usadas na camada Domain.

E Jesus, respondendo, disse-lhes: Não necessitam de médico os que estão sãos, mas, sim, os que estão enfermos;
Eu não vim chamar os justos, mas, sim, os pecadores, ao arrependimento.
Lucas 5:31,32

Referências:


José Carlos Macoratti