Formato curto da pergunta
Está dentro das melhores práticas de DDD e OOP injetar serviços em chamadas de método de entidade?
Exemplo de formato longo
Digamos que temos o caso clássico Order-LineItems no DDD, onde temos uma Entidade de Domínio chamada Order, que também atua como Raiz Agregada, e que a Entidade é composta não apenas pelos Objetos de Valor, mas também por uma coleção de Itens de Linha Entidades.
Suponha que desejemos sintaxe fluente em nosso aplicativo, para que possamos fazer algo assim (observando a sintaxe na linha 2, onde chamamos o getLineItems
método):
$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
...
}
Não queremos injetar nenhum tipo de LineItemRepository no OrderEntity, pois isso é uma violação de vários princípios em que posso pensar. Mas, a fluência da sintaxe é algo que realmente queremos, porque é fácil de ler e manter, além de testar.
Considere o seguinte código, observando o método getLineItems
em OrderEntity
:
interface IOrderService {
public function getOrderByID($orderID) : OrderEntity;
public function getLineItems(OrderEntity $orderEntity) : LineItemCollection;
}
class OrderService implements IOrderService {
private $orderRepository;
private $lineItemRepository;
public function __construct(IOrderRepository $orderRepository, ILineItemRepository $lineItemRepository) {
$this->orderRepository = $orderRepository;
$this->lineItemRepository = $lineItemRepository;
}
public function getOrderByID($orderID) : OrderEntity {
return $this->orderRepository->getByID($orderID);
}
public function getLineItems(OrderEntity $orderEntity) : LineItemCollection {
return $this->lineItemRepository->getLineItemsByOrderID($orderEntity->ID());
}
}
class OrderEntity {
private $ID;
private $lineItems;
public function getLineItems(IOrderServiceInternal $orderService) {
if(!is_null($this->lineItems)) {
$this->lineItems = $orderService->getLineItems($this);
}
return $this->lineItems;
}
}
Essa é a maneira aceita de implementar sintaxe fluente nas Entidades sem violar os princípios básicos de DDD e OOP? Para mim, parece bom, pois estamos apenas expondo a camada de serviço, não a camada de infraestrutura (que está aninhada no serviço)
Não, você não deve injetar nada na sua camada de domínio (isso inclui entidades, objetos de valor, fábricas e serviços de domínio). Essa camada deve ser independente de qualquer estrutura, bibliotecas ou tecnologia de terceiros e não deve fazer chamadas de E / S.
Isso está errado, pois o agregado não deve precisar de mais nada além de si próprio para devolver os itens do pedido. O toda Aggregate já deve ser carregada antes de sua chamada de método. Se você acha que isso deve ser carregado preguiçosamente, existem duas possibilidades:
Seus limites de agregados estão incorretos, são muito grandes.
Neste caso de uso, você usa o agregado apenas para leitura. A melhor solução é separar o modelo de gravação do modelo de leitura (por exemplo, use CQRS ). Nesta arquitetura mais limpa , você não tem permissão para consultar o modelo Agregado, mas um modelo de leitura.
fonte
A ideia principal nos padrões táticos DDD: o aplicativo acessa todos os dados no aplicativo, agindo em uma raiz agregada. Isso implica que as únicas entidades acessíveis fora do modelo de domínio são as raízes agregadas.
A raiz agregada do pedido nunca produziria uma referência à sua coleção de itens de linha que permitiria modificar a coleção, nem produziria uma coleção de referências a qualquer item de linha que permitiria modificá-la. Se você deseja alterar o agregado do pedido, aplica-se o princípio de hollywood: "Diga, não pergunte".
Retornar valores de dentro do agregado é bom, porque os valores são inerentemente imutáveis; você não pode alterar meus dados alterando sua cópia deles.
Usar um serviço de domínio como argumento, para ajudar o agregado a fornecer os valores corretos, é uma coisa perfeitamente razoável de se fazer.
Normalmente, você não usaria um serviço de domínio para fornecer acesso aos dados que estão dentro do agregado, porque o agregado já deve ter acesso a ele.
Portanto, essa ortografia é estranha, se estivermos tentando acessar a coleção de valores de itens de linha desse pedido. A ortografia mais natural seria
Obviamente, isso pressupõe que os itens de linha já foram carregados.
O padrão usual é que a carga do agregado inclua todo o estado necessário para o caso de uso específico. Em outras palavras, você pode ter várias maneiras diferentes de carregar o mesmo agregado; seus métodos de repositório são adequados para a finalidade .
Essa abordagem não é algo que você encontrará no Evans original, onde ele assumiu que um agregado teria um único modelo de dados associado a ela. Ele cai mais naturalmente no CQRS.
fonte
lineItems()
e à pré-carga após a primeira recuperação da Raiz Agregada.De um modo geral, objetos de valor pertencentes a agregados não têm repositório por si mesmos. É responsabilidade agregada da raiz preenchê-los. No seu caso, é responsabilidade do seu OrderRepository preencher os objetos de entidade Order e OrderLine.
Em relação à implementação da infraestrutura do OrderRepository, no caso do ORM, é um relacionamento de um para muitos, e você pode optar por carregar ansiosamente ou com preguiça o OrderLine.
Não sei ao certo o que seus serviços significam exatamente. É bem próximo do "Serviço de Aplicativo". Se for esse o caso, geralmente não é uma boa ideia injetar os serviços no objeto agregado raiz / entidade / valor. O Serviço de Aplicativo deve ser o cliente do Objeto Raiz / Entidade / Valor Agregado e Serviço de Domínio. Outra coisa sobre seus serviços é: expor objetos de valor no Application Service também não é uma boa ideia. Eles devem ser acessados por raiz agregada.
fonte
A resposta é: definitivamente NÃO, evite passar serviços nos métodos de entidade.
A solução é simples: basta permitir que o repositório de pedidos retorne o pedido com todos os seus itens de linha. No seu caso, o agregado é Order + LineItems; portanto, se o repositório não retornar um agregado completo, ele não fará seu trabalho.
O princípio mais amplo é: manter os bits funcionais (por exemplo, lógica do domínio) separados dos bits não funcionais (por exemplo, persistência).
Mais uma coisa: se você puder, tente evitar fazer isso:
Faça isso
No design orientado a objetos, tentamos evitar a pesca nos dados de um objeto. Preferimos pedir ao objeto para fazer o que queremos.
fonte