Cenário:
Um cliente faz um pedido e, depois de receber o produto, fornece feedback sobre o processo do pedido.
Suponha as seguintes raízes agregadas:
- Cliente
- Ordem
- Comentários
Aqui estão as regras de negócios:
- Um cliente pode apenas fornecer feedback sobre seu próprio pedido, não sobre o de outra pessoa.
Um cliente pode fornecer feedback apenas se o pedido tiver sido pago.
class Feedback { public function __construct($feedbackId, Customer $customer, Order $order, $content) { if ($customer->customerId() != $order->customerId()) { // Error } if (!$order->isPaid()) { // Error } $this->feedbackId = $feedbackId; $this->customerId = $customerId; $this->orderId = $orderId; $this->content = $content; } }
Agora, suponha que a empresa queira uma nova regra:
Um cliente pode fornecer feedback apenas se as
Supplier
mercadorias do pedido ainda estiverem em operação.class Feedback { public function __construct($feedbackId, Customer $customer, Order $order, Supplier $supplier, $content) { if ($customer->customerId() != $order->customerId()) { // Error } if (!$order->isPaid()) { // Error } // NEW RULE HERE if (!$supplier->isOperating()) { // Error } $this->feedbackId = $feedbackId; $this->customerId = $customerId; $this->orderId = $orderId; $this->content = $content; } }
Coloquei a implementação das duas primeiras regras no Feedback
próprio agregado. Sinto-me à vontade para fazer isso, especialmente porque o
Feedback
agregado faz referência a todos os outros agregados por identidade. Por exemplo, as propriedades do Feedback
componente indicam que ele conhece a
existência dos outros agregados, então me sinto confortável em saber também o estado somente leitura desses agregados.
No entanto, com base em suas propriedades, o Feedback
agregado não tem conhecimento da existência do
Supplier
agregado; portanto, ele deve ter conhecimento do estado somente leitura desse agregado?
A solução alternativa para a implementação da regra 3 é mover essa lógica para o apropriado CommandHandler
. No entanto, parece que está afastando a lógica do domínio do "centro" da minha arquitetura baseada em cebola.
Supplier
o estado operacional de um agregado não seria consultado por meio de umOrder
repositório;Supplier
eOrder
são dois agregados separados. Em segundo lugar, havia uma pergunta na lista de discussão DDD / CQRS sobre a passagem de raízes e repositórios agregados para outros métodos raiz agregados (incluindo o construtor). Havia uma variedade de opiniões, mas Greg Young mencionou que transmitir raízes agregadas como parâmetros é comum, enquanto outra pessoa disse que os repositórios estão mais intimamente relacionados à infraestrutura do que ao domínio. Por exemplo, repositórios "abstraem coleções de memória" e não têm lógica.Customer
só pode fornecer feedback sobre um de seus próprios pedidos ($order->customerId() == $customer->customerId()
), também precisamos comparar o ID do fornecedor ($order->supplierId() == $supplier->supplierId()
). A primeira regra protege contra o usuário que fornece valores incorretos. A segunda regra protege contra o programador que fornece valores incorretos. No entanto, a verificação sobre se o fornecedor está operando deve ser naFeedback
entidade ou no manipulador de comandos. Onde está a questão?Respostas:
Se a correção transacional exigir que um agregado saiba sobre o estado atual de outro agregado, seu modelo está errado.
Na maioria dos casos, a correção transacional não é necessária . As empresas tendem a ter tolerância em relação à latência e aos dados obsoletos. Isso se aplica especialmente às inconsistências fáceis de detectar e remediar.
Portanto, o comando será executado pelo agregado que muda de estado. Para executar a verificação não necessariamente correta, é necessária uma cópia não necessariamente da última versão do estado do outro agregado.
Para comandos em um agregado existente, o padrão usual é passar um Repositório para o agregado, e o agregado passará seu estado para o repositório, o que fornece uma consulta que retorna um estado imutável / projeção do outro agregado
Mas os padrões de construção são estranhos - quando você cria o objeto, o chamador já conhece o estado interno, porque está fornecendo. O mesmo padrão funciona, apenas parece inútil
Estamos seguindo as regras, mantendo toda a lógica do domínio nos objetos de domínio, mas não estamos protegendo os negócios invariantes de nenhuma maneira útil (porque todas as mesmas informações estão disponíveis para o componente do aplicativo). Para o padrão de criação, seria tão bom escrever
fonte
SupplierOperatingQuery
consulta é enganosa no modelo de leitura ou "Consulta" no nome? 2. A consistência transacional não é necessária. Não importa se o fornecedor interrompe as operações um segundo antes de um cliente deixar o feedback, mas isso significa que não devemos verificar isso de qualquer maneira? 3. No seu exemplo, o fornecimento de um "serviço de consulta" em vez do próprio objeto impõe consistência transacional? Se sim, como? 4. Como o uso desses serviços de consulta afeta o teste de unidade?Sei que essa é uma pergunta antiga, mas gostaria de ressaltar que o problema decorre diretamente de uma premissa incorreta. Ou seja, as raízes agregadas que devemos assumir que existem são simplesmente incorretas.
Há apenas uma raiz agregada no sistema que você descreveu: Cliente. Tanto um Pedido quanto um Feedback, embora possam ser agregados por si mesmos, dependem da existência do Cliente, portanto não são eles mesmos raízes agregadas. A lógica que você fornece no seu construtor de feedback parece indicar que um Pedido DEVE ter um ID do cliente e o Feedback também deve estar relacionado a um Cliente. Isso faz sentido. Como um Pedido ou Feedback não pode estar relacionado a um Cliente? Além disso, o Fornecedor parece estar logicamente relacionado ao Pedido (estaria dentro desse agregado).
Com o exposto acima, todas as informações que você deseja já estão disponíveis na raiz agregada do Cliente e fica claro que você está aplicando suas regras no lugar errado. Os construtores são lugares terríveis para impor regras de negócios e devem ser evitados a todo custo. É assim que deve ser (Observação: não incluirei construtores para Cliente e Pedido, porque provavelmente as Fábricas devem ser usadas. Também não mostramos todos os métodos de interface).
OK. Vamos dividir isso um pouco. A primeira coisa que você notará é o quão mais declarativo esse modelo é. Tudo é uma ação, fica claro ONDE as regras de negócios devem ser aplicadas. O design acima não apenas "faz" a coisa certa, mas "diz" a coisa certa.
O que levaria alguém a assumir que as regras estão sendo executadas na linha a seguir?
Segundo, você pode ver que toda a lógica referente à validação de regras de negócios é realizada o mais próximo possível dos modelos aos quais elas pertencem. No seu exemplo, o construtor (um único método) está executando várias validações em diferentes modelos. Isso quebra o design do SOLID. Onde adicionaríamos uma verificação para garantir que o conteúdo do Feedback não contenha palavrões? Outra verificação no construtor? E se diferentes tipos de Feedback precisarem de verificações de conteúdo diferentes? Feio.
Terceiro, olhando as interfaces, você pode ver que existem lugares naturais para estender / modificar as regras através da composição. Por exemplo, diferentes tipos de pedidos podem ter regras diferentes sobre quando o feedback pode ser fornecido. O pedido também pode fornecer diferentes tipos de feedback, que por sua vez podem ter regras diferentes para validação.
Você também pode ver várias interfaces do ICustomer *. Eles são usados para compor o agregado do Cliente que precisamos aqui (provavelmente não apenas chamado de Cliente). A razão para isso é simples. É MUITO provável que um cliente seja uma raiz agregada ENORME que se espalha por todo o seu domínio / banco de dados. Usando interfaces, podemos decompor esse agregado (que provavelmente é muito grande para carregar) em várias raízes agregadas que fornecem apenas determinadas ações (como pedir ou fornecer feedback). Você pode ver que o agregado em minha implementação pode fazer pedidos e fornecer feedback, mas não pode ser usado para redefinir uma senha ou alterar um nome de usuário.
Portanto, a resposta para sua pergunta é que os agregados devem se validar. Se eles não puderem, provavelmente você tem um modelo deficiente.
fonte