Format court de la question
Est-il conforme aux meilleures pratiques de DDD et OOP d'injecter des services sur les appels de méthode d'entité?
Exemple de format long
Disons que nous avons le cas classique Order-LineItems dans DDD, où nous avons une entité de domaine appelée une commande, qui agit également en tant que racine agrégée, et cette entité est composée non seulement de ses objets de valeur, mais également d'une collection d'éléments de ligne Entités.
Supposons que nous voulons une syntaxe fluide dans notre application, afin de pouvoir faire quelque chose comme ça (en notant la syntaxe à la ligne 2, où nous appelons la getLineItems
méthode):
$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
...
}
Nous ne voulons injecter aucune sorte de LineItemRepository dans OrderEntity, car c'est une violation de plusieurs principes auxquels je peux penser. Mais, la maîtrise de la syntaxe est quelque chose que nous voulons vraiment, car il est facile à lire et à maintenir, ainsi qu'à tester.
Considérez le code suivant, en notant la méthode getLineItems
dans 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;
}
}
Est-ce la façon acceptée de mettre en œuvre une syntaxe fluide dans les entités sans violer les principes fondamentaux de DDD et OOP? Pour moi, cela semble bien, car nous exposons uniquement la couche service, pas la couche infrastructure (qui est imbriquée dans le service)
Non, vous ne devez rien injecter dans votre couche de domaine (cela inclut les entités, les objets de valeur, les usines et les services de domaine). Cette couche doit être indépendante de tout framework, bibliothèque ou technologie tierce et ne doit effectuer aucun appel d'E / S.
C'est faux car l'agrégat ne devrait avoir besoin que d'autre chose que lui-même pour retourner les articles de la commande. L' ensemble de l' agrégat doit être déjà chargé avant son appel de méthode. Si vous pensez que cela devrait être paresseux, deux possibilités s'offrent à vous:
Vos limites d'agrégats sont erronées, elles sont trop grandes.
Dans ce cas d'utilisation, vous utilisez l'agrégat uniquement pour la lecture. La meilleure solution consiste à séparer le modèle d'écriture du modèle de lecture (c'est-à-dire utiliser CQRS ). Dans cette architecture plus propre , vous n'êtes pas autorisé à interroger l'agrégat mais un modèle de lecture.
la source
L'idée clé des schémas tactiques DDD: l'application accède à toutes les données de l'application en agissant sur une racine agrégée. Cela implique que les seules entités accessibles en dehors du modèle de domaine sont les racines agrégées.
La racine agrégée Order ne fournirait jamais une référence à sa collection d'élément de ligne qui vous permettrait de modifier la collection, pas plus qu'elle ne générerait une collection de références à un élément de campagne qui vous permettrait de la modifier. Si vous souhaitez modifier l'agrégat Order, le principe hollywoodien s'applique: "Dites, ne demandez pas".
Renvoyer des valeurs à partir de l'agrégat est très bien, car les valeurs sont intrinsèquement immuables; vous ne pouvez pas modifier mes données en changeant votre copie.
L'utilisation d'un service de domaine comme argument pour aider l'agrégat à fournir les valeurs correctes est une chose parfaitement raisonnable à faire.
Normalement, vous n'utiliseriez pas un service de domaine pour fournir un accès aux données qui se trouvent à l'intérieur de l'agrégat, car l'agrégat devrait déjà y avoir accès.
L'orthographe est donc bizarre si nous essayons d'accéder à la collection de valeurs de l'élément de campagne de cette campagne. L'orthographe la plus naturelle serait
Bien sûr, cela présuppose que les éléments de campagne ont déjà été chargés.
Le schéma habituel est que la charge de l'agrégat comprendra tout l'état requis pour le cas d'utilisation particulier. En d' autres termes, vous pouvez avoir plusieurs différents façons de charger le même agrégat; vos méthodes de référentiel sont adaptées à l'usage .
Cette approche n'est pas quelque chose que vous trouverez dans l'Evans original, où il supposait qu'un agrégat aurait un seul modèle de données associé. Il sort plus naturellement du CQRS.
la source
lineItems()
et le préchargement lors de la première récupération de la racine agrégée.De manière générale, les objets de valeur appartenant à l'agrégat n'ont pas de référentiel par eux-mêmes. C'est la responsabilité globale de la racine de les remplir. Dans votre cas, il est de la responsabilité de votre OrderRepository de remplir à la fois les objets d'entité Order et de valeur OrderLine.
En ce qui concerne l'implémentation de l'infrastructure du OrderRepository, dans le cas ORM, c'est une relation un-à-plusieurs, et vous pouvez choisir de charger la OrderLine avec impatience ou paresseux.
Je ne sais pas exactement ce que signifient vos services. C'est assez proche de "Application Service". Si tel est le cas, ce n'est généralement pas une bonne idée d'injecter les services dans la racine agrégée / l'entité / l'objet valeur. Le service d'application doit être le client du service racine / entité / objet de valeur agrégée et du service de domaine. Une autre chose à propos de vos services est que l'exposition d'objets de valeur dans Application Service n'est pas non plus une bonne idée. Ils doivent être accessibles par racine agrégée.
la source
La réponse est: certainement NON, évitez de passer des services dans les méthodes d'entité.
La solution est simple: laissez simplement le référentiel de commandes renvoyer la commande avec tous ses éléments de ligne. Dans votre cas, l'agrégat est Order + LineItems, donc si le référentiel ne renvoie pas un agrégat complet, il ne fait pas son travail.
Le principe plus large est le suivant: garder les bits fonctionnels (par exemple, la logique de domaine) séparés des bits non fonctionnels (par exemple, la persistance).
Encore une chose: si vous le pouvez, essayez d'éviter de faire cela:
Faites cela à la place
Dans la conception orientée objet, nous essayons d'éviter de pêcher dans les données d'un objet. Nous préférons demander à l'objet de faire ce que nous voulons.
la source