Partilhar via


Projetar um modelo de domínio de microsserviço

Gorjeta

Este conteúdo é um trecho do eBook, .NET Microservices Architecture for Containerized .NET Applications, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Defina um modelo de domínio avançado para cada microsserviço de negócios ou contexto limitado.

Seu objetivo é criar um único modelo de domínio coeso para cada microsserviço de negócios ou Contexto Delimitado (BC). Tenha em mente, no entanto, que um BC ou microsserviço de negócios às vezes pode ser composto por vários serviços físicos que compartilham um único modelo de domínio. O modelo de domínio deve capturar as regras, o comportamento, a linguagem de negócios e as restrições do único Contexto Delimitado ou microsserviço de negócios que ele representa.

O padrão de entidade de domínio

As entidades representam objetos de domínio e são definidas principalmente por sua identidade, continuidade e persistência ao longo do tempo, e não apenas pelos atributos que as compõem. Como diz Eric Evans, "um objeto definido principalmente por sua identidade é chamado de Entidade". As entidades são muito importantes no modelo de domínio, uma vez que são a base para um modelo. Portanto, você deve identificá-los e projetá-los cuidadosamente.

A identidade de uma entidade pode cruzar vários microsserviços ou contextos limitados.

A mesma identidade (ou seja, o mesmo Id valor, embora talvez não seja a mesma entidade de domínio) pode ser modelada em vários contextos delimitados ou microsserviços. No entanto, isso não implica que a mesma entidade, com os mesmos atributos e lógica, seria implementada em vários contextos limitados. Em vez disso, as entidades em cada Contexto Delimitado limitam seus atributos e comportamentos aos exigidos no domínio desse Contexto Delimitado.

Por exemplo, a entidade compradora pode ter a maioria dos atributos de uma pessoa definidos na entidade do usuário no microsserviço de perfil ou identidade, incluindo a identidade. Mas a entidade compradora no microsserviço de pedidos pode ter menos atributos, porque apenas determinados dados do comprador estão relacionados ao processo do pedido. O contexto de cada microsserviço ou Contexto Delimitado afeta seu modelo de domínio.

As entidades de domínio devem implementar comportamento, além de implementar atributos de dados.

Uma entidade de domínio no DDD deve implementar a lógica de domínio ou o comportamento relacionado aos dados da entidade (o objeto acessado na memória). Por exemplo, como parte de uma classe de entidade de ordem, você deve ter a lógica de negócios e as operações implementadas como métodos para tarefas como adicionar um item de ordem, validação de dados e cálculo total. Os métodos da entidade cuidam dos invariantes e regras da entidade em vez de ter essas regras espalhadas pela camada de aplicativo.

A Figura 7-8 mostra uma entidade de domínio que implementa não apenas atributos de dados, mas operações ou métodos com lógica de domínio relacionada.

Diagram showing a Domain Entity's pattern.

Figura 7-8. Exemplo de um design de entidade de domínio implementando dados mais comportamento

Uma entidade de modelo de domínio implementa comportamentos através de métodos, ou seja, não é um modelo "anêmico". É claro que, às vezes, você pode ter entidades que não implementam nenhuma lógica como parte da classe de entidade. Isso pode acontecer em entidades filhas dentro de uma agregação se a entidade filha não tiver nenhuma lógica especial porque a maior parte da lógica é definida na raiz agregada. Se você tiver um microsserviço complexo que tenha lógica implementada nas classes de serviço em vez de nas entidades de domínio, você pode estar caindo no modelo de domínio anêmico, explicado na seção a seguir.

Modelo de domínio avançado versus modelo de domínio anêmico

Em seu post AnemicDomainModel, Martin Fowler descreve um modelo de domínio anêmico desta forma:

O sintoma básico de um Modelo de Domínio Anêmico é que, à primeira vista, ele parece a coisa real. Existem objetos, muitos com nomes de substantivos no espaço de domínio, e esses objetos estão conectados com as ricas relações e estrutura que os verdadeiros modelos de domínio têm. O problema vem quando você olha para o comportamento, e percebe que quase não há comportamento nesses objetos, tornando-os pouco mais do que sacos de getters e setters.

É claro que, quando você usa um modelo de domínio anêmico, esses modelos de dados serão usados a partir de um conjunto de objetos de serviço (tradicionalmente chamados de camada de negócios) que capturam todo o domínio ou lógica de negócios. A camada de negócios fica sobre o modelo de dados e usa o modelo de dados como dados.

O modelo de domínio anêmico é apenas um design de estilo processual. Objetos de entidade anêmica não são objetos reais porque carecem de comportamento (métodos). Eles apenas possuem propriedades de dados e, portanto, não é um design orientado a objetos. Ao colocar todo o comportamento em objetos de serviço (a camada de negócios), você essencialmente acaba com código espaguete ou scripts de transação e, portanto, perde as vantagens que um modelo de domínio oferece.

Independentemente disso, se o seu microsserviço ou Contexto Delimitado for muito simples (um serviço CRUD), o modelo de domínio anêmico na forma de objetos de entidade com apenas propriedades de dados pode ser bom o suficiente, e pode não valer a pena implementar padrões DDD mais complexos. Nesse caso, será simplesmente um modelo de persistência, porque você criou intencionalmente uma entidade com apenas dados para fins de CRUD.

É por isso que as arquiteturas de microsserviços são perfeitas para uma abordagem multiarquitetural, dependendo de cada contexto limitado. Por exemplo, no eShopOnContainers, o microsserviço de pedido implementa padrões DDD, mas o microsserviço de catálogo, que é um serviço CRUD simples, não.

Algumas pessoas dizem que o modelo de domínio anêmico é um anti-padrão. Depende realmente do que você está implementando. Se o microsserviço que você está criando for simples o suficiente (por exemplo, um serviço CRUD), seguindo o modelo de domínio anêmico, ele não é um antipadrão. No entanto, se você precisar lidar com a complexidade do domínio de um microsserviço que tem muitas regras de negócios em constante mudança, o modelo de domínio anêmico pode ser um antipadrão para esse microsserviço ou contexto limitado. Nesse caso, projetá-lo como um modelo rico com entidades contendo dados mais comportamento, bem como implementar padrões DDD adicionais (agregados, objetos de valor, etc.) pode ter enormes benefícios para o sucesso a longo prazo de tal microsserviço.

Recursos adicionais

O padrão Value Object

Como Eric Evans observou, "Muitos objetos não têm identidade conceitual. Esses objetos descrevem certas características de uma coisa."

Uma entidade requer uma identidade, mas há muitos objetos em um sistema que não o fazem, como o padrão Value Object. Um objeto value é um objeto sem identidade conceitual que descreve um aspeto de domínio. Estes são objetos que você instancia para representar elementos de design que só lhe dizem respeito temporariamente. Você se preocupa com o que eles são, não com quem eles são. Os exemplos incluem números e cadeias de caracteres, mas também podem ser conceitos de nível superior, como grupos de atributos.

Algo que é uma entidade em um microsserviço pode não ser uma entidade em outro microsserviço, porque no segundo caso, o Contexto Delimitado pode ter um significado diferente. Por exemplo, um endereço em um aplicativo de comércio eletrônico pode não ter uma identidade, uma vez que pode representar apenas um grupo de atributos do perfil do cliente para uma pessoa ou empresa. Nesse caso, o endereço deve ser classificado como um objeto de valor. No entanto, em um aplicativo para uma empresa de energia elétrica, o endereço do cliente pode ser importante para o domínio do negócio. Portanto, o endereço deve ter uma identidade para que o sistema de cobrança possa ser diretamente vinculado ao endereço. Nesse caso, um endereço deve ser classificado como uma entidade de domínio.

Uma pessoa com um nome e apelido é geralmente uma entidade porque uma pessoa tem identidade, mesmo que o nome e o apelido coincidam com outro conjunto de valores, como se esses nomes também se referirem a uma pessoa diferente.

Os objetos de valor são difíceis de gerenciar em bancos de dados relacionais e ORMs como o Entity Framework (EF), enquanto em bancos de dados orientados a documentos eles são mais fáceis de implementar e usar.

O EF Core 2.0 e versões posteriores incluem o recurso Entidades Próprias que facilita o manuseio de objetos de valor, como veremos em detalhes mais adiante.

Recursos adicionais

O padrão agregado

Um modelo de domínio contém clusters de diferentes entidades de dados e processos que podem controlar uma área significativa de funcionalidade, como atendimento de pedidos ou inventário. Uma unidade DDD mais refinada é o agregado, que descreve um cluster ou grupo de entidades e comportamentos que podem ser tratados como uma unidade coesa.

Normalmente, você define um agregado com base nas transações de que precisa. Um exemplo clássico é uma ordem que também contém uma lista de itens de ordem. Um item de ordem geralmente será uma entidade. Mas será uma entidade filha dentro da ordem agregada, que também conterá a entidade de ordem como sua entidade raiz, normalmente chamada de raiz agregada.

Identificar agregados pode ser difícil. Uma agregação é um grupo de objetos que devem ser consistentes juntos, mas você não pode simplesmente escolher um grupo de objetos e rotulá-los como agregados. Você deve começar com um conceito de domínio e pensar nas entidades que são usadas nas transações mais comuns relacionadas a esse conceito. As entidades que precisam ser transacionalmente consistentes são o que forma um agregado. Pensar em operações de transação é provavelmente a melhor maneira de identificar agregados.

O padrão Raiz Agregada ou Entidade Raiz

Um agregado é composto por pelo menos uma entidade: a raiz agregada, também chamada entidade raiz ou entidade primária. Além disso, ele pode ter várias entidades filhas e objetos de valor, com todas as entidades e objetos trabalhando juntos para implementar o comportamento e as transações necessárias.

O objetivo de uma raiz agregada é assegurar a consistência do agregado; ele deve ser o único ponto de entrada para atualizações para o agregado através de métodos ou operações na classe raiz agregada. Você deve fazer alterações em entidades dentro da agregação somente por meio da raiz agregada. É o guardião da consistência do agregado, considerando todas as invariantes e regras de consistência que você pode precisar cumprir em seu agregado. Se você alterar uma entidade filho ou objeto de valor independentemente, a raiz agregada não poderá garantir que a agregação esteja em um estado válido. Seria como uma mesa com uma perna solta. Manter a consistência é o principal objetivo da raiz agregada.

Na Figura 7-9, você pode ver agregados de amostra como o agregado comprador, que contém uma única entidade (a raiz agregada Buyer). A ordem agregada contém várias entidades e um objeto value.

Diagram comparing a buyer aggregate and an order aggregate.

Figura 7-9. Exemplo de agregados com entidades múltiplas ou únicas

Um modelo de domínio DDD é composto de agregados, um agregado pode ter apenas uma entidade ou mais e também pode incluir objetos de valor. Observe que o agregado Comprador pode ter entidades filhas adicionais, dependendo do seu domínio, como acontece no microsserviço de pedidos no aplicativo de referência eShopOnContainers. A Figura 7-9 apenas ilustra um caso em que o comprador tem uma única entidade, como um exemplo de um agregado que contém apenas uma raiz agregada.

Para manter a separação de agregados e manter limites claros entre eles, é uma boa prática em um modelo de domínio DDD não permitir a navegação direta entre agregados e ter apenas o campo de chave estrangeira (FK), conforme implementado no modelo de domínio de microsserviço de ordenação em eShopOnContainers. A entidade Order tem apenas um campo de chave estrangeira para o comprador, mas não uma propriedade de navegação EF Core, conforme mostrado no código a seguir:

public class Order : Entity, IAggregateRoot
{
    private DateTime _orderDate;
    public Address Address { get; private set; }
    private int? _buyerId; // FK pointing to a different aggregate root
    public OrderStatus OrderStatus { get; private set; }
    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
    // ... Additional code
}

Identificar e trabalhar com agregados requer pesquisa e experiência. Para obter mais informações, consulte a seguinte lista de recursos adicionais.

Recursos adicionais