Encontre a raiz agregada DDD

10

Vamos jogar o jogo favorito de todos, encontrar a raiz de agregação. Vamos usar o domínio canônico de problema do cliente / pedido / linhas de pedidos / produto. Tradicionalmente, Cliente, pedido e produto são os ARs, com OrderLines sendo entidades sob o Pedido. A lógica por trás disso é que você precisa identificar clientes, pedidos e produtos, mas um OrderLine não existiria sem um pedido. Portanto, em nosso domínio de problemas, temos uma regra comercial dizendo que um Cliente pode ter apenas um pedido não entregue por vez.

Isso move o pedido para a raiz agregada do cliente? Eu acho que sim. Mas, ao fazer isso, isso torna o AR do cliente bastante grande e sujeito a problemas de concorrência posteriormente.

Ou então, e se tivéssemos uma regra comercial declarando que um cliente só pode solicitar um produto específico uma vez na vida. Essa é mais uma evidência que exige que o cliente seja o proprietário do pedido.

Mas quando se trata de remessa, eles executam todas as suas ações no Pedido, não no cliente. É meio bobo ter que carregar o cliente inteiro para marcar um Pedido individual como entregue.

Isto é o que estou propondo:

class Customer
{
    public Guid Id {get;set;}
    public string Name { get; set; }
    public Address Address { get; set; }
    public IEnumerable<Order> Orders { get; set; }
    public void PlaceOrder(ThingsInTheOrder thingsInTheOrder)
    {
        // Make sure there aren't any pending orders already.
        // Make sure they aren't ordering a Widget if they've already ordered a Widget in the past.
        // Create an Order object and add it to the collection.  Raise a domain event to trigger emails and other stuff
    }
}

class Order
{
    public Guid Id { get; set; }
    public IEnumerable<OrderLine> OrderLines { get; set; }
    public ShippingData {get;set;}
    public void Ship(ShippedByPerson shippedByPerson, string trackingCode)
    {
         // Create a new ShippingData object and assign it from the data passed in.  
         // Publish a domain event
    }
}

Minha maior preocupação é a questão da concorrência e o fato de a própria Ordem ter características de uma raiz agregada.

Darthg8r
fonte

Respostas:

12

Versão curta

A lógica do DDD é que os Objetos de Domínio são abstrações que devem atender aos seus requisitos funcionais de domínio - se os Objetos de Domínio não puderem atender facilmente a esses requisitos, isso sugere que você pode estar usando a abstração errada.

A nomeação de objetos de domínio usando substantivos de entidade pode fazer com que esses objetos fiquem fortemente acoplados entre si e se tornem objetos "deuses" inchados e podem gerar problemas como o desta pergunta, como "Onde é o lugar certo para colocar o objeto" Método CreateOrder? ".

Para facilitar a identificação da Raiz Agregada 'certa', considere uma abordagem diferente em que os Objetos de Domínio se baseiam nos requisitos funcionais de negócios de alto nível - ou seja, escolha substantivos que aludam a requisitos funcionais e / ou comportamentos que os usuários do sistema precisam executar.


Versão longa

O DDD é uma abordagem do OO Design, cujo objetivo é resultar em um gráfico de objetos de domínio na camada de negócios do seu sistema - os objetos de domínio são responsáveis ​​por satisfazer seus requisitos de negócios de alto nível e, idealmente, devem poder confiar na camada de dados para coisas como o desempenho e a integridade do armazenamento de dados persistentes subjacente.

Outra maneira de ver isso pode ser os pontos desta lista

  • Os substantivos de entidade geralmente sugerem atributos de dados.
  • Substantivos de domínio devem sugerir comportamento
  • A modelagem DDD e OO se preocupa com abstrações baseadas em requisitos funcionais e na lógica principal de domínio / negócios.
  • A camada lógica de negócios é responsável por atender aos requisitos de domínio de alto nível

Um dos equívocos comuns a respeito do DDD é que os Objetos de Domínio devem se basear em alguma "coisa" física do mundo real (ou seja, algum substantivo que você possa apontar no mundo real, atribuído a todos os tipos de dados / propriedades), mas os dados Os atributos dessas coisas do mundo real não necessariamente são um bom ponto de partida ao tentar definir requisitos funcionais.

Obviamente, a lógica de negócios deve usar esses dados, mas os próprios objetos de domínio devem ser abstrações que representam requisitos e comportamentos funcionais de domínio.

Por exemplo; substantivos como Orderou Customernão implicam qualquer comportamento e, portanto, geralmente são abstrações inúteis para representar a lógica comercial e os Objetos de Domínio.

Ao procurar os tipos de abstrações que podem ser úteis para representar a lógica de negócios, considere os requisitos típicos que você pode esperar que um sistema cumpra:

  • Como vendedor, desejo criar um pedido para um novo cliente para gerar uma fatura dos produtos a serem vendidos com seus preços e quantidade.
  • Como consultor de atendimento ao cliente, desejo cancelar um pedido pendente para que o pedido não seja atendido por um operador de armazém.
  • Como consultor de atendimento ao cliente, quero devolver uma linha de pedido para que o produto possa ser ajustado no estoque e o pagamento seja reembolsado novamente pelo método de pagamento original do cliente.
  • Como operador de armazém, desejo exibir todos os produtos em um pedido pendente e as informações de remessa para que eu possa escolher os produtos e enviá-los pelo correio.
  • etc.

Modelando requisitos de domínio com uma abordagem DDD

Com base na lista acima, considere alguns objetos de domínio em potencial para esse sistema de pedidos:

SalesOrderCheckout
PendingOrdersStream
WarehouseOrderDespatcher
OrderRefundProcessor 

Como objetos de domínio, eles representam abstrações que se apropriam de vários requisitos de domínio comportamental; de fato, seus substantivos sugerem fortemente os requisitos funcionais específicos que eles atendem.

(Também pode haver infraestrutura adicional, como uma EventMediatornotificação para passar para os observadores que desejam saber quando um novo pedido foi criado ou quando um pedido foi enviado, etc.).

Por exemplo, SalesOrderCheckoutprovavelmente precisa lidar com dados sobre Clientes, Remessa e Produtos, no entanto, não se preocupa com nada com o comportamento de pedidos de remessa, classificação de pedidos pendentes ou emissão de reembolsos.

Para SalesOrderCheckoutcumprir seus requisitos de domínio, é impor essas regras de negócios, como impedir que os clientes façam pedidos em excesso de itens, possivelmente executando alguma validação e talvez levantando notificações para outras partes do sistema - ele pode fazer tudo isso sem precisar depender necessariamente de qualquer dos outros objetos.

DDD usando substantivos de entidade para representar objetos de domínio

Existem vários perigos em potencial ao tratar substantivos simples como Order, Customere Productcomo Objetos de Domínio; Entre esses problemas estão aqueles a que você alude na pergunta:

  • Se um método lida com a Order, a Customere a Product, a qual objeto de domínio ele pertence?
  • Onde está a raiz agregada para esses três objetos?

Se você escolher Substantivos da entidade para representar objetos de domínio, várias coisas podem acontecer:

  • Order, CustomerE Productestão em risco de crescer em objetos "deus"
  • Risco de acabar com um único Managerobjeto divino para amarrar tudo.
  • Esses objetos correm o risco de ficar fortemente acoplados entre si - pode ser difícil atender aos requisitos de domínio sem passar this(ou self)
  • Um risco de desenvolver abstrações "vazadas" - ou seja, espera-se que objetos do domínio exponham dezenas de get/ setmétodos que enfraquecem o encapsulamento (ou, se não o fizer, algum outro programador provavelmente mais tarde ..).
  • Risco de objetos de domínio ficarem inchados com uma mistura complexa de dados corporativos (por exemplo, entrada de dados do usuário por meio de uma interface do usuário) e estado transitório (por exemplo, um 'histórico' de ações do usuário quando o pedido foi modificado).

DDD, Design OO e Modelos Simples

Um equívoco comum sobre o DDD e o OO Design é que os modelos "simples" são de alguma forma 'ruins' ou 'antipadrão'. Martin Fowler escreveu um artigo descrevendo o Modelo de Domínio Anêmico - mas, como ele deixa claro no artigo, o próprio DDD não deve 'contradizer' a abordagem da separação limpa entre camadas

"Também vale enfatizar que colocar o comportamento nos objetos de domínio não deve contradizer a abordagem sólida do uso de camadas para separar a lógica do domínio de coisas como responsabilidades de persistência e apresentação. A lógica que deve estar em um objeto de domínio é lógica de domínio - validações, cálculos , regras de negócios - como você quiser chamá-lo ".

Em outras palavras, o uso de modelos simples para armazenar dados corporativos transferidos entre outras camadas (por exemplo, um modelo de pedido passado por um aplicativo do usuário quando o usuário deseja criar um novo pedido) não é a mesma coisa que um "modelo de domínio anêmico". modelos de dados 'simples' geralmente são a melhor maneira de rastrear dados e transferir dados entre camadas (como um serviço da Web REST, um armazenamento de persistência, um aplicativo ou interface do usuário etc.).

A lógica de negócios pode processar os dados nesses modelos e rastreá-los como parte do estado de negócios - mas não necessariamente se apropria desses modelos.

A raiz agregada

Olhando novamente para o exemplo de domínio Objects - SalesOrderCheckout, PendingOrdersStream, WarehouseOrderDespatcher, OrderRefundProcessornão há ainda nenhum óbvio Aggregate Raiz; mas isso realmente não importa, porque esses objetos de domínio têm responsabilidades muito separadas que parecem não se sobrepor.

Funcionalmente, não há necessidade de SalesOrderCheckoutconversar com o, PendingOrdersStreamporque o trabalho do primeiro é concluído quando ele adiciona um novo pedido ao Banco de Dados; por outro lado, o PendingOrdersStreampode recuperar novos pedidos do banco de dados. Na verdade, esses objetos não precisam interagir diretamente (talvez um mediador de eventos possa fornecer notificações entre os dois, mas eu esperaria que qualquer acoplamento entre esses objetos fosse muito solto)

Talvez a raiz agregada seja um contêiner de IoC que injete um ou mais desses objetos de domínio em um controlador de interface do usuário, fornecendo também outras infraestruturas como EventMediatore Repository. Ou talvez seja algum tipo de serviço leve do orquestrador localizado no topo da camada de negócios.

A raiz agregada não precisa necessariamente ser um objeto de domínio. Para manter a Separação de Preocupações entre objetos do Domínio, geralmente é bom quando a raiz agregada é um objeto separado sem lógica de negócios.

Ben Cottrell
fonte
3
Fiz uma votação baixa porque sua resposta combina conceitos do Entity Framework, que é uma tecnologia específica da Microsoft, com Domain Driven Design, que é de um livro escrito por Eric Evans com o mesmo nome. Você tem algumas declarações em sua resposta que estão em contradição direta com o livro DDD e esta pergunta faz nenhuma menção ao Entity Framework, mas está especificamente marcada com DDD. Também não há menção de persistência na pergunta, então não vejo onde as tabelas de banco de dados são relevantes.
precisa saber é o seguinte
@RibaldEddie Obrigado por reservar um tempo para revisar a resposta e comentar, eu concordo que a menção de dados persistentes não precisa realmente estar na resposta, então eu a reformulei para removê-la. O foco principal da resposta pode ser resumido como "Substantivos de entidade geralmente não são muito bons nomes de classe de Objeto de Domínio devido à sua tendência a se tornarem objetos divinos inchados fortemente acoplados"; ?
Ben Cottrell
O livro DDD não tem esse conceito IIRC. Possui Entidades, que são meramente classes que, quando instanciadas, têm uma identidade persistente e única, de modo que duas instâncias separadas implicam duas coisas únicas e passíveis de persistência, o que contrasta com os Objetos de Valor que não têm identidade e não persistem com o tempo. . No livro, os objetos Entidades e Valor são objetos de domínio.
precisa saber é o seguinte
10

Quais são os critérios para definir um agregado?

Vamos voltar ao básico do grande livro azul:

Agregado: um cluster de objetos associados que são tratados como uma unidade com a finalidade de alterações de dados . As referências externas são restritas a um membro do AGGREGATE, designado como raiz. Um conjunto de regras de consistência se aplica dentro dos limites da AGGREGATE'S.

O objetivo é manter os invariantes. Mas é também para gerenciar adequadamente a identidade local, ou seja, a identificação de objetos que não têm um significado sozinho.

Ordere Order linedefinitivamente pertencem a esse cluster. Por exemplo:

  • Excluir um Order, exigirá a exclusão de todas as suas linhas.
  • A exclusão de uma linha pode exigir a renumeração das seguintes linhas
  • Adicionar uma nova linha exigiria determinar o número de linhas com base em todas as outras linhas da mesma ordem.
  • A alteração de algumas informações do pedido, como por exemplo a moeda, pode afetar o significado do preço nos itens de linha (ou exigir a recalculação dos preços).

Portanto, aqui é necessário o agregado completo para garantir regras de consistência e invariantes.

Quando parar?

Agora, você descreve algumas regras de negócios e argumenta que, para garanti-las, seria necessário considerar o cliente como parte do agregado:

Temos uma regra comercial dizendo que um cliente pode ter apenas um pedido não entregue por vez.

Claro, por que não. Vamos ver as implicações: o pedido sempre seria acessado via cliente. Isso ea vida real ? Quando os trabalhadores estiverem preenchendo as caixas para entrega do pedido, eles precisarão ler o código de barras do cliente e o código de barras do pedido para acessar o pedido? De fato, em geral, a identidade de um Pedido não é global para um cliente e essa relativa independência sugere mantê-lo fora do agregado.

Além disso, essas regras de negócios parecem mais políticas: é uma decisão arbitrária da empresa executar o processo com essas regras. Se as regras não forem respeitadas, o chefe pode ficar insatisfeito, mas os dados não são realmente inconsistentes. Além disso, durante a noite "por cliente, um pedido não entregue de cada vez" poderia se tornar "dez pedidos não entregues por cliente" ou mesmo "independentemente do cliente, cem pedidos não entregues por armazém", para que o agregado não fosse mais justificado.

Christophe
fonte
1

em nosso domínio do problema, temos uma regra comercial dizendo que um Cliente pode ter apenas um pedido não entregue por vez.

Antes de ir muito fundo na toca do coelho, revise a discussão de Greg Young sobre consistência do conjunto e, em particular:

Qual é o impacto comercial de uma falha?

Porque em muitos casos, a resposta certa não é tentar impedir que algo errado aconteça, mas gerar relatórios de exceção quando houver um problema.

Mas, presumindo que vários pedidos não entregues sejam uma responsabilidade significativa para o seu negócio ...

Sim, se você deseja garantir que haja apenas um pedido não entregue, é preciso haver algum agregado que possa ver todos os pedidos de um cliente.

Esse agregado não é necessariamente o agregado do cliente .

Pode ser algo como uma fila de pedidos ou um histórico de pedidos, em que todos os pedidos de um cliente específico entram na mesma fila. Pelo que você disse, ele não precisa de todos os dados de perfil do cliente, portanto, isso não deve fazer parte desse agregado.

Mas quando se trata de remessa, eles executam todas as suas ações no Pedido, não no cliente.

Sim, quando você está realmente trabalhando com preenchimento e extração de folhas, a exibição do histórico não é particularmente relevante.

A exibição do histórico, para impor sua invariável, precisa apenas do ID do pedido e do status atual do processamento. Isso não precisa necessariamente fazer parte do mesmo agregado da ordem - lembre-se, os limites agregados são sobre gerenciamento de mudanças, não estruturação de visualizações.

Portanto, pode ser que você lide com o pedido como um agregado, e o histórico do pedido como um agregado separado, e coordene a atividade entre os dois.

VoiceOfUnreason
fonte
1

Você configurou um exemplo de pessoa palha. É muito simplista e duvido que reflita um sistema do mundo real. Eu não modelaria essas entidades e seu comportamento relacionado da maneira que você especificou por causa disso.

Suas classes precisam modelar o estado de um pedido de uma maneira que seja refletida em várias agregações. Por exemplo, quando o cliente coloca o sistema no estado em que a solicitação de pedido do cliente precisa ser processada, eu posso criar um agregado de objeto de entidade de domínio chamado CustomerOrderRequestou PendingCustomerOrderou apenas CustomerOrder, ou qualquer idioma que a empresa use, e pode conter um ponteiro para ambos o Customer e os OrderLines e, em seguida, tenha um método como o canCustomerCompleteOrder()que é chamado a partir da camada de serviço.

Esse objeto de domínio conteria a lógica de negócios para determinar se o pedido era ou não válido.

Se o pedido fosse válido e processado, eu teria uma maneira de fazer a transição desse objeto para outro objeto que representasse o pedido processado.

Acho que o problema com o seu entendimento é que você está usando um exemplo simplificado de agregados. A PendingOrderpode ser seu próprio agregado separado de um UndeliveredOrdere novamente separado de um DeliveredOrderou um CancelledOrderou o que for.

RibaldEddie
fonte
Embora sua tentativa de linguagem neutra em termos de gênero seja divertida, eu observaria que as mulheres nunca ficam nos campos para espantar os corvos.
21717 Robert
@RobertHarvey é uma coisa estranha de se focar no meu post. Espantalhos e efígies têm aparecido regularmente na forma feminina ao longo da história.
precisa saber é o seguinte
Você não faria a distinção em sua postagem se não a considerasse importante. Por uma questão de linguística, o termo é "homem de palha"; quaisquer reservas sobre sexismo são quase certamente superadas pelo fator "do que diabos ele está falando" criado pela invenção de seu próprio termo.
Robert Harvey
5
@RobertHarvey, se alguém souber o que significa palha, tenho certeza de que pode descobrir o que significa palha se não ouviu esse termo. Podemos nos concentrar na substância da minha postagem, por favor, escreva um software?
precisa saber é o seguinte
1

Vaughn Vernon menciona isso em seu livro "Implementing Domain-Driven Design" no início do capítulo 7 (Serviços):

"Geralmente, a melhor indicação de que você deve criar um serviço no modelo de domínio é quando a operação que você precisa executar parece deslocada como um método em um objeto agregado ou de valor".

Portanto, nesse caso, pode haver um serviço de domínio chamado "CreateOrderService" que recebe uma instância do Cliente e a lista de itens do pedido.

class CreateOrderService
{
    public Order CreateOrder(Customer customer, ThingsInTheOrder thingsInTheOrder)
    {
        // Get all the orders for the customer
        // Check if any of the things to be ordered exist in previous orders   
        // If none have been previously ordered, create the order and return it
        // Otherwise return null 
    }
}
claudius
fonte
1
Você pode explicar mais como o serviço de domínio pode ajudar a lidar com a preocupação com a concorrência na pergunta?
X u u