Encontre o DDD Aggregate Root
On Fevereiro 1, 2021 by adminVamos jogar o jogo favorito de todos, encontre o Aggregrate Root. Vamos usar o domínio canônico de Cliente / Pedido / OrderLines / 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 problema, temos uma regra de negócios que diz que um Cliente só pode ter um pedido não entregue por vez.
Isso move o pedido sob a raiz agregada do cliente? Acho que sim. Mas, ao fazer isso, torna o AR do cliente bastante grande e sujeito a problemas de simultaneidade posteriormente.
Ou, se tivéssemos uma regra de negócios declarando que um cliente só pode pedir um determinado produto uma vez na vida. Isso é mais uma evidência de que o cliente é o proprietário do pedido.
Mas quando chegar para o envio, eles realizam todas as ações no pedido, não no cliente. É meio burro ter que carregar todo o cliente para marcar um pedido individual como entregue.
Isso é 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 que o próprio pedido tem características s de uma raiz agregada.
Resposta
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 para fins de alteração 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 AGGREGATES.
O objetivo é manter os invariantes. Mas é também para gerenciar adequadamente a identidade local, ou seja, a identificação de objetos que não possuem um significado por si só.
Order
e Order line
definitivamente pertencem a tal 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
- A adição de uma nova linha exigiria a determinação do número de linha com base em todas as outras linhas da mesma ordem.
- Alterar algumas informações do pedido, como por exemplo a moeda, pode afetar o significado do preço nos itens de linha (ou exigir o recálculo dos preços).
Então, aqui está o agregado completo é necessário para garantir regras de consistência e invariantes.
Quando parar?
Agora, você descreve algumas regras de negócios e argumenta que, para garanti-las, você precisa considerar o cliente como parte do agregado:
Temos um negócio s regra que diz que um cliente só pode ter um pedido não entregue por vez.
Claro, por que não. Vejamos as implicações: o pedido sempre seria acessado por meio do cliente. Isso é vida real? Quando os trabalhadores estiverem enchendo 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 ? Na verdade, em geral, a identidade de um Pedido é global, não local para um cliente, e essa independência relativa 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 seu processo de acordo com essas regras. Se as regras não forem respeitadas, o chefe pode ficar descontente, mas os dados não são realmente inconsistentes. Além disso, durante a noite ” por cliente, um pedido não entregue por vez ” pode 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 seja mais justificado.
Resposta
Versão curta
A justificativa para DDD é que os objetos de domínio são abstrações que devem cumprir seus requisitos de domínio funcional – se os objetos de domínio não forem capazes de cumprir esses requisitos facilmente, isso sugere que você pode estar usando a abstração errada.
Nomenclatura Objetos de Domínio usando Substantivos de Entidade podem levar esses objetos a se tornarem fortemente acoplados uns aos outros e se tornarem objetos “deuses” inchados, e eles podem lançar questões como esta nesta questão, como como “Onde é o lugar certo para p ut o método CreateOrder? “.
Para facilitar a identificação da raiz agregada “certa”, considere uma abordagem diferente em que os objetos de domínio são baseados nos requisitos de negócios funcionais de alto nível – ou seja,escolha substantivos que aludem a requisitos funcionais e / ou comportamentos que os usuários do sistema precisam realizar.
Versão Longa
DDD é uma abordagem de Design OO que se destina a resultar em um gráfico de Objetos de Domínio no Negócios Camada de 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 ser capazes de contar com a camada de dados para coisas como o desempenho e a integridade do armazenamento de dados persistentes subjacente.
Outra maneira de ver isso poderia ser os marcadores nesta lista
- Substantivos de entidade geralmente sugerem atributos de dados.
- Substantivos de domínio devem sugerir comportamento
- DDD e OO Modeling estão preocupados com abstrações baseadas em requisitos funcionais e domínio central / lógica de negócios.
- A Business Logic Layer é responsável por satisfazer os requisitos de domínio de alto nível
Um dos equívocos comuns sobre DDD é que os objetos de domínio devem ser baseados em algum tipo de realidade física mundo “coisa” (ou seja, algum substantivo que você pode apontar no mundo real, atribuído a todos os tipos de dados / propriedades), no entanto, os dados / atributos dessas coisas do mundo real não são necessariamente um bom ponto de partida ao tentar acertar 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 os requisitos e comportamentos funcionais do domínio.
Por exemplo; substantivos como Order
ou Customer
não implicam em nenhum comportamento e, portanto, geralmente são abstrações inúteis para representar lógica de negócios e 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ê espera que um sistema cumpra:
- Como vendedor, eu desejo criar um pedido para um novo cliente para que eu possa gerar uma fatura para os produtos a serem vendidos com seus preços e quantidade.
- Como consultor de atendimento ao cliente, desejo cancelar um pedido pendente para que o O pedido não é atendido por um operador de armazém.
- Como um consultor de atendimento ao cliente, desejo devolver uma linha de pedido para que o produto possa ser ajustado ao estoque e o pagamento seja devolvido através do pagamento original do cliente método.
- Como operador de armazém, desejo visualizar todos os produtos em um pedido pendente e as informações de remessa para poder 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 possíveis objetos de domínio ts para tal 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 comportamentais; na verdade, seus substantivos sugerem fortemente os requisitos funcionais específicos que atendem.
(Pode haver uma infraestrutura adicional lá também, como um EventMediator
para passar notificações para observadores que desejam saber quando um novo pedido foi criado ou quando um pedido foi enviado, etc.).
Por exemplo, SalesOrderCheckout
provavelmente precisa lida com dados sobre clientes, frete e produtos, no entanto, não está preocupado com nada a ver com o comportamento de pedidos de envio, classificação de pedidos pendentes ou emissão de reembolsos.
Para SalesOrderCheckout
para cumprir seus requisitos de domínio inclui a aplicação dessas regras de negócios, como evitar que os clientes peçam muitos itens, possivelmente executando alguma validação e talvez gerando notificações para outras partes do sistema – ele pode fazer todas essas coisas sem necessariamente precisar depender de qualquer um dos outros objetos.
DDD usando substantivos de entidade para representar objetos de domínio
Ther Existem vários perigos potenciais ao tratar substantivos simples, como Order
, Customer
e Product
como Objetos de Domínio; entre esses problemas estão aqueles aos quais você alude na pergunta:
- Se um método lida com um
Order
, umCustomer
e umProduct
, a qual objeto de domínio ele pertence? - Onde está a raiz agregada para esses três objetos?
Se você escolher Substantivos de Entidade para representar Objetos de Domínio, várias coisas podem acontecer:
-
Order
,Customer
eProduct
correm o risco de se transformarem em objetos” deuses “ - Risco de terminar com um único
Manager
objeto-deus para unir tudo. - Esses objetos correm o risco de se tornarem fortemente acoplados uns aos outros – pode ser difícil cumprir os requisitos de domínio sem passar em
this
(ouself
) - Um risco de desenvolver abstrações “vazadas” – ou seja,espera-se que os objetos de domínio exponham dezenas de
get
/set
métodos que enfraquecem o encapsulamento (ou, se você não o fizer, então algum outro programador provavelmente mais tarde …). - O risco de objetos de domínio se tornarem inchados com uma mistura complexa de dados de negócios (por exemplo, entrada de dados do usuário por meio de uma IU) e estado transitório (por exemplo, um “histórico” de ações do usuário quando o pedido foi modificado).
DDD, OO Design e Modelos Simples
Um equívoco comum em relação ao DDD e OO Design é que os modelos “simples” são de alguma forma ” ruim “ou um” 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 deveria não “contradiz” a abordagem de separação limpa entre camadas
“Também vale a pena enfatizar que colocar o comportamento nos objetos do domínio não deve contradizer a abordagem sólida de usar camadas para separar classifique a lógica do domínio de coisas como persistência e responsabilidades de apresentação. A lógica que deve estar em um objeto de domínio é a lógica de domínio – validações, cálculos, regras de negócios – como você quiser chamá-la. “
Em outras palavras, usar Modelos simples para manter dados de negócios 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” costumam ser a melhor maneira de rastrear 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 o dados nesses modelos e pode rastreá-los como parte do estado do negócio – mas não necessariamente assumirá a propriedade desses modelos.
A raiz agregada
Olhando novamente para os objetos de domínio de exemplo – SalesOrderCheckout
, PendingOrdersStream
, WarehouseOrderDespatcher
, OrderRefundProcessor
ainda não há nenhuma raiz agregada óbvia; mas isso Na verdade, isso não importa porque esses Objetos de Domínio têm responsabilidades totalmente separadas que não parecem se sobrepor.
Funcionalmente, “não há necessidade do SalesOrderCheckout
falar com o PendingOrdersStream
porque o trabalho do primeiro está completo quando adiciona um novo pedido ao banco de dados; por outro lado, o PendingOrdersStream
pode recuperar novos pedidos do banco de dados. Esses objetos não precisam realmente interagir com cada um outro diretamente (talvez um mediador de eventos possa fornecer notificações entre os dois, mas eu esperaria que qualquer acoplamento entre esses objetos fosse muito frouxo)
Talvez a raiz agregada seja um contêiner IoC que injeta um ou mais esses objetos de domínio em um controlador de IU, também fornecendo outra infraestrutura como EventMediator
e Repository
. Ou talvez seja algum tipo de serviço de orquestrador leve localizado no topo da camada de negócios.
A raiz do agregado não necessariamente precisa ser um objeto de domínio. Para manter a separação de preocupações entre os objetos de domínio, geralmente é uma boa coisa quando o agregado root é um objeto separado sem lógica de negócios.
Comentários
- Eu votei negativamente porque sua resposta confunde 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 afirmações em sua resposta que estão em contradição direta com o livro DDD e esta pergunta não faz nenhuma menção ao Entity Framework, mas especificamente está marcada com DDD. Também ‘ não há menção à persistência na pergunta, então não ‘ não vejo onde as tabelas do banco de dados são relevantes.
- @RibaldEddie Obrigado por reservar um tempo para revisar a resposta e comentar, eu concordo que a menção de dados persistentes não ‘ realmente precisa estar na resposta, então eu ‘ nós o reformulamos para remover isso. O foco principal da resposta poderia ser resumido como ” Substantivos de entidade frequentemente não ‘ nomes de classe de Objeto de Domínio muito bons devido à sua tendência a tornam-se objetos divinos fortemente acoplados e inchados “, espero que a mensagem e o raciocínio requisitos / comportamento funcionais do WRT estejam mais claros agora?
- O livro DDD não ‘ t temos esse conceito IIRC. Tem Entidades, que são meramente classes que quando instanciadas têm uma identidade persistente e única, de forma que duas instâncias separadas implicam em duas coisas únicas e persistentes, o que contrasta com Objetos de Valor que não ‘ t tenha qualquer identidade e não ‘ t persista ao longo do tempo. No livro, os objetos Entities e Value são objetos de domínio.
Resposta
em nosso domínio do problema, temos uma regra de negócios dizendo que um cliente só pode ter um pedido não entregue por vez.
Antes de ir muito fundo nessa toca do coelho, você deve revisar Greg Discussão de Young sobre consistência definida e, em particular:
Qual é o impacto comercial de um fracasso?
Porque, em muitos casos, a resposta certa não é tentar prevenir algo errado aconteça, mas em vez de gerar relatórios de exceção quando houver um problema.
Mas, presumindo que vários pedidos não entregues são uma responsabilidade significativa para o seu negócio ….
Sim, se você deseja garantir que haja apenas um pedido não entregue, então deve 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, onde todos os pedidos de um determinado cliente entrar na mesma fila. Pelo que você disse, ele não precisa de todos os dados de perfil do cliente, de modo que não deve fazer parte deste agregado.
Mas quando se trata de envio, eles realizam todas as ações no pedido, não o cliente.
Sim, quando você está realmente trabalhando com atendimento e pull sheet, a visão de histórico não é particularmente relevante.
A visão de histórico, para impor sua invariante, só precisa do id do pedido e seu status de processamento atual. Isso não precisa necessariamente ser parte do mesmo agregado que o pedido – lembre-se de que os limites agregados tratam do gerenciamento de mudanças, não da estruturação de visualizações.
Então, pode ser que você trate o pedido como um agregado e o histórico de pedidos como um agregado separado, e coordena a atividade entre os dois.
Resposta
Você configurou um exemplo de palhaço. É muito simplista e duvido que reflita um sistema do mundo real. Eu não modelaria essas Entidades e seus comportamentos relacionados da maneira que você especificou por causa disso.
Suas classes precisam modelar o estado de um pedido de forma que seja refletido em vários agregados. Por exemplo, quando o cliente coloca o sistema no estado em que a solicitação de pedido do cliente precisa ser processada, posso criar um agregado de objeto de entidade de domínio chamado CustomerOrderRequest
ou PendingCustomerOrder
ou mesmo apenas CustomerOrder
, ou qualquer linguagem que a empresa use, e poderia conter um ponteiro para o Cliente e as OrderLines e, em seguida, ter um método como canCustomerCompleteOrder()
que é chamado a partir da camada de serviço.
Este objeto de domínio conteria a lógica de negócios para determinar se o pedido era válido ou não.
Se o pedido fosse válido e processado, eu teria alguma 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. Um PendingOrder
pode ser seu próprio agregado separado de um UndeliveredOrder
e novamente separado de um ou um CancelledOrder
ou qualquer outro.
Comentários
- Embora sua tentativa se a linguagem neutra de gênero for divertida, eu observaria que as mulheres nunca ficam nos campos para assustar os corvos.
- @RobertHarvey que ‘ é uma coisa estranha para se focar na minha postagem. Espantalhos e efígies têm aparecido regularmente em forma feminina ao longo da história.
- Você não ‘ não teria feito a distinção em sua postagem se não ‘ não considero isso importante. Por uma questão de linguística, o termo é ” espantalho; ” quaisquer reservas sobre sexismo são quase certamente superadas pelo ” do que diabos ele está falando ” fator criado pela invenção do seu próprio termo.
- @RobertHarvey se alguém sabe que palha homem quer dizer, eu ‘ tenho certeza de que eles podem descobrir o que pessoa palhaço quer dizer se não ‘ não ouviram esse termo. Podemos nos concentrar no conteúdo da minha postagem, por favor, escreva o software?
Resposta
Vaughn Vernon menciona isso em seu livro “Implementing Domain-Driven Design” no início do Capítulo 7 (Services):
“Freqüentemente, 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 fora de lugar como um método em um agregado ou objeto de valor “.
Portanto, neste caso, pode haver um serviço de domínio chamado “CreateOrderService” que leva 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 } }
Comentários
- Você pode explicar mais como o serviço de domínio pode ajudar a abordar a questão da simultaneidade?
Deixe uma resposta