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 Order
ou Customer
nã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 EventMediator
notificação para passar para os observadores que desejam saber quando um novo pedido foi criado ou quando um pedido foi enviado, etc.).
Por exemplo, SalesOrderCheckout
provavelmente 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 SalesOrderCheckout
cumprir 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
, Customer
e Product
como 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 Customer
e 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
, Customer
E Product
estão em risco de crescer em objetos "deus"
- Risco de acabar com um único
Manager
objeto 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
/ set
mé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
, OrderRefundProcessor
nã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 SalesOrderCheckout
conversar com o, PendingOrdersStream
porque o trabalho do primeiro é concluído quando ele adiciona um novo pedido ao Banco de Dados; por outro lado, o PendingOrdersStream
pode 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 EventMediator
e 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.
Quais são os critérios para definir um agregado?
Vamos voltar ao básico do grande livro azul:
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.
Order
eOrder line
definitivamente pertencem a esse cluster. Por exemplo:Order
, exigirá a exclusão de todas as suas linhas.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:
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.
fonte
Antes de ir muito fundo na toca do coelho, revise a discussão de Greg Young sobre consistência do conjunto e, em particular:
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.
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.
fonte
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
CustomerOrderRequest
ouPendingCustomerOrder
ou apenasCustomerOrder
, 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 ocanCustomerCompleteOrder()
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
PendingOrder
pode ser seu próprio agregado separado de umUndeliveredOrder
e novamente separado de umDeliveredOrder
ou umCancelledOrder
ou o que for.fonte
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.
fonte