Compartilhar via


Criar um microsserviço orientado a DDD

Dica

Esse conteúdo é um trecho do eBook, arquitetura de microsserviços do .NET para aplicativos .NET em contêineres, disponível em do .NET Docs ou como um PDF para download gratuito que pode ser lido offline.

miniatura da capa do eBook sobre arquitetura de microsserviços do .NET para aplicativos .NET em contêineres.

O DDD (design controlado pelo domínio) defende a modelagem com base na realidade dos negócios, conforme relevante para seus casos de uso. No contexto da criação de aplicativos, o DDD fala sobre problemas como domínios. Descreve áreas de problema independentes como Contextos Limitados (cada Contexto Limitado correlaciona-se a um microsserviço) e enfatiza uma linguagem comum para falar sobre esses problemas. Também sugere muitos conceitos e padrões técnicos, como entidades de domínio com modelos avançados (sem modelo de domínio anêmico), objetos de valor, agregados e regras agregadas de raiz (ou entidade raiz) para dar suporte à implementação interna. Esta seção apresenta o design e a implementação desses padrões internos.

Às vezes, essas regras e padrões técnicos de DDD são percebidos como obstáculos que têm uma curva de aprendizado íngreme para implementar abordagens DDD. Mas a parte importante não são os padrões em si, mas organizar o código para que ele seja alinhado aos problemas de negócios e usar os mesmos termos de negócios (linguagem onipresente). Além disso, as abordagens DDD devem ser aplicadas somente se você estiver implementando microsserviços complexos com regras de negócios significativas. Responsabilidades mais simples, como um serviço CRUD, podem ser gerenciadas com abordagens mais simples.

Onde desenhar os limites é a tarefa chave ao projetar e definir um microsserviço. Os padrões DDD ajudam você a entender a complexidade no domínio. Para o modelo de domínio para cada Contexto Limitado, você identifica e define as entidades, objetos de valor e agregações que modelam seu domínio. Você cria e refina um modelo de domínio contido em um limite que define seu contexto. E isso é explícito na forma de um microsserviço. Os componentes dentro desses limites acabam sendo seus microsserviços, embora em alguns casos um BC ou microsserviços empresariais possam ser compostos por vários serviços físicos. DDD é sobre limites, assim como microsserviços.

Manter os limites de contexto do microsserviço relativamente pequenos

Determinar onde colocar limites entre Contextos Limitados equilibra duas metas concorrentes. Primeiro, você deseja criar inicialmente os menores microsserviços possíveis, embora esse não seja o driver principal; você deve criar um limite em torno de coisas que precisam de coesão. Em segundo lugar, você deseja evitar comunicações tagarelas entre microsserviços. Esses objetivos podem contradizer uns aos outros. Você deve equilibrá-las decompondo o sistema em tantos microsserviços quanto puder até ver esses limites de comunicação crescendo rapidamente a cada tentativa adicional de separar um novo contexto limitado. A coesão é fundamental em um único contexto limitado.

É semelhante ao cheiro de código de Intimidade Inadequada ao implementar classes. Se dois microsserviços precisarem colaborar muito uns com os outros, eles provavelmente devem ser o mesmo microsserviço.

Outra maneira de examinar esse aspecto é a autonomia. Se um microsserviço precisar contar com outro serviço para atender diretamente a uma solicitação, ele não será realmente autônomo.

Camadas em microsserviços de DDD

A maioria dos aplicativos empresariais com complexidade técnica e comercial significativa é definida por várias camadas. As camadas são um artefato lógico e não estão relacionadas à implantação do serviço. Eles existem para ajudar os desenvolvedores a gerenciar a complexidade no código. Camadas diferentes (como a camada de modelo de domínio versus a camada de apresentação, etc.) podem ter tipos diferentes, que exigem traduções entre esses tipos.

Por exemplo, uma entidade pode ser carregada do banco de dados. Em seguida, parte dessas informações, ou uma agregação de informações, incluindo dados adicionais de outras entidades, pode ser enviada para a interface do usuário do cliente por meio de uma API Web REST. O ponto aqui é que a entidade de domínio está contida na camada do modelo de domínio e não deve ser propagada para outras áreas às quais ela não pertence, como a camada de apresentação.

Além disso, você precisa ter entidades sempre válidas (consulte as validações de criação na seção de camada de modelo de domínio) controladas por raízes agregadas (entidades raiz). Portanto, as entidades não devem estar associadas a exibições do cliente, pois no nível da interface do usuário alguns dados ainda podem não ser validados. Esse é o motivo para o qual o ViewModel serve. O ViewModel é um modelo de dados exclusivamente para necessidades de camada de apresentação. As entidades de domínio não pertencem diretamente ao ViewModel. Em vez disso, você precisa converter entre entidades de domínio e ViewModels e vice-versa.

Ao lidar com a complexidade, é importante ter um modelo de domínio controlado por raízes agregadas que garantam que todos os invariáveis e regras relacionados a esse grupo de entidades (agregação) sejam executados por meio de um único ponto de entrada ou porta, a raiz de agregação.

A Figura 7-5 mostra como um design em camadas é implementado no aplicativo eShopOnContainers.

Diagrama mostrando as camadas em um microsserviço de design controlado pelo domínio.

Figura 7-5. Camadas DDD no microsserviço de ordenação em eShopOnContainers

As três camadas em um microsserviço de DDD como o de pedidos. Cada camada é um projeto VS: a camada de aplicativo é Ordering.API, a camada de domínio é Ordering.Domain e a camada infraestrutura é Ordering.Infrastructure. Você deseja projetar o sistema para que cada camada se comunique apenas com determinadas outras camadas. Essa abordagem pode ser mais fácil de impor se as camadas forem implementadas como bibliotecas de classes diferentes, porque você pode identificar claramente quais dependências são definidas entre bibliotecas. Por exemplo, a camada de modelo de domínio não deve depender de nenhuma outra camada (as classes de modelo de domínio devem ser objetos de classe antiga simples ou poco). Conforme mostrado na Figura 7-6, a biblioteca de camadas do Ordering.Domain tem dependências apenas de bibliotecas .NET ou pacotes NuGet, mas não em nenhuma outra biblioteca personalizada, como uma biblioteca de dados ou de persistência.

Captura de tela das dependências do Ordering.Domain.

Figura 7-6. As camadas implementadas como bibliotecas permitem um melhor controle das dependências entre camadas

A camada de modelo de domínio

O excelente livro de Eric Evans , Domain Driven Design , diz o seguinte sobre a camada do modelo de domínio e a camada de aplicativo.

Camada de Modelo de Domínio: responsável por representar conceitos do negócio, informações sobre a situação dos negócios e regras de negócios. O estado reflectindo a situação dos negócios é controlado e utilizado aqui, embora os detalhes técnicos de seu armazenamento sejam delegados à infraestrutura técnica. Essa camada é o coração do software de negócios.

A camada de modelo de domínio é onde a empresa é expressa. Quando você implementa uma camada de modelo de domínio de microsserviço no .NET, essa camada é codificada como uma biblioteca de classes com as entidades de domínio que capturam dados mais comportamento (métodos com lógica).

Seguindo os princípios de Ignorância da Persistência e da Ignorância da Infraestrutura , essa camada deve ignorar completamente os detalhes de persistência de dados. Essas tarefas de persistência devem ser executadas pela camada de infraestrutura. Portanto, essa camada não deve assumir dependências diretas na infraestrutura, o que significa que uma regra importante é que as classes de entidade do modelo de domínio devem ser POCOs.

As entidades de domínio não devem ter nenhuma dependência direta (como derivar de uma classe base) em qualquer estrutura de infraestrutura de acesso a dados, como Entity Framework ou NHibernate. Idealmente, suas entidades de domínio não devem derivar ou implementar qualquer tipo definido em qualquer estrutura de infraestrutura.

A maioria das estruturas ORM modernas, como o Entity Framework Core, permite essa abordagem, para que as classes de modelo de domínio não sejam acopladas à infraestrutura. No entanto, ter entidades POCO nem sempre é possível ao usar determinados bancos de dados e estruturas NoSQL, como atores e coleções confiáveis no Azure Service Fabric.

Mesmo quando é importante seguir o princípio da Ignorância de Persistência para seu modelo de Domínio, você não deve ignorar as preocupações de persistência. Ainda é importante reconhecer o modelo de dados físico e como ele é mapeado para o modelo de objeto de entidade. Caso contrário, você poderá criar designs impossíveis.

Além disso, esse aspecto não significa que você pode pegar um modelo projetado para um banco de dados relacional e movê-lo diretamente para um banco de dados orientado a documentos ou NoSQL. Em alguns modelos de entidade, o modelo pode ser adequado, mas geralmente não é. Ainda há restrições às quais o modelo de entidade deve aderir, com base na tecnologia de armazenamento e na tecnologia ORM.

A camada de aplicativo

Passando para a camada de aplicativo, podemos citar novamente o livro design controlado pelo domínio de Eric Evans:

Camada de aplicativo: Define os trabalhos que o software deve fazer e direciona os objetos de domínio expressivos para resolver problemas. As tarefas pelas quais essa camada é responsável são significativas para o negócio ou necessárias para interação com as camadas de aplicativo de outros sistemas. Essa camada é mantida fina. Ele não contém regras de negócios ou conhecimento, mas apenas coordena tarefas e delega trabalho para colaborações de objetos de domínio na próxima camada. Ele não tem estado que reflita a situação dos negócios, mas pode ter um estado que reflita o progresso de uma tarefa para o usuário ou o programa.

A camada de aplicativo de um microsserviço no .NET normalmente é codificada como um projeto de API Web do ASP.NET Core. O projeto implementa a interação do microsserviço, o acesso remoto à rede e as APIs Web externas usadas da interface do usuário ou dos aplicativos cliente. Ele inclui consultas se estiver usando uma abordagem CQRS, comandos aceitos pelo microsserviço e até mesmo a comunicação controlada por eventos entre microsserviços (eventos de integração). A API Web do ASP.NET Core que representa a camada de aplicativo não deve conter regras de negócios ou conhecimento de domínio (especialmente regras de domínio para transações ou atualizações); elas devem pertencer à biblioteca de classes do modelo de domínio. A camada de aplicativo deve apenas coordenar tarefas e não deve manter ou definir nenhum estado de domínio (modelo de domínio). Ele delega a execução de regras de negócios para as próprias classes de modelo de domínio (raízes agregadas e entidades de domínio), que, em última análise, atualizarão os dados dentro dessas entidades de domínio.

Basicamente, a lógica do aplicativo é onde você implementa todos os casos de uso que dependem de um determinado front-end. Por exemplo, a implementação relacionada a um serviço de API Web.

A meta é que a lógica de domínio na camada de modelo de domínio, suas invariáveis, o modelo de dados e as regras de negócios relacionadas devem ser completamente independentes das camadas de apresentação e aplicativo. Acima de tudo, a camada do modelo de domínio não deve depender diretamente de nenhuma estrutura de infraestrutura.

A camada de infraestrutura

A camada de infraestrutura é como os dados inicialmente mantidos em entidades de domínio (na memória) são mantidos em bancos de dados ou em outro repositório persistente. Um exemplo é usar o código do Entity Framework Core para implementar as classes de padrão de repositório que usam um DBContext para persistir dados em um banco de dados relacional.

De acordo com os princípios de Ignorância de Persistência e Ignorância de Infraestrutura mencionados anteriormente, a camada de infraestrutura não deve "contaminar" a camada do modelo de domínio. Você deve manter as classes de entidade do modelo de domínio independentes da infraestrutura que você usa para persistir dados (EF ou qualquer outra estrutura) não usando dependências rígidas em estruturas. Sua biblioteca de classes de camada de modelo de domínio deve ter apenas seu código de domínio, apenas classes de entidade POCO implementando o coração do software e completamente dissociadas das tecnologias de infraestrutura.

Portanto, suas camadas ou bibliotecas de classes e projetos devem, em última análise, depender da sua camada de modelo de domínio (biblioteca), não vice-versa, conforme mostrado na Figura 7-7.

Diagrama mostrando dependências existentes entre camadas de serviço DDD.

Figura 7-7. Dependências entre camadas no DDD

Dependências em um Serviço DDD: a camada de aplicação depende do Domínio e da Infraestrutura, a Infraestrutura depende do Domínio, mas o Domínio não depende de nenhuma camada. Esse design de camada deve ser independente para cada microsserviço. Como observado anteriormente, você pode implementar os microsserviços mais complexos seguindo padrões DDD, ao mesmo tempo em que implementa microsserviços controlados por dados mais simples (CRUD simples em uma única camada) de maneira mais simples.

Recursos adicionais