Supposons que nous ayons un système de journalisation des tâches, lorsqu'une tâche est journalisée, l'utilisateur spécifie une catégorie et la tâche par défaut a le statut 'En suspens'. Supposons dans ce cas que la catégorie et le statut doivent être implémentés en tant qu'entités. Normalement, je ferais ceci:
Couche d'application:
public class TaskService
{
//...
public void Add(Guid categoryId, string description)
{
var category = _categoryRepository.GetById(categoryId);
var status = _statusRepository.GetById(Constants.Status.OutstandingId);
var task = Task.Create(category, status, description);
_taskRepository.Save(task);
}
}
Entité:
public class Task
{
//...
public static void Create(Category category, Status status, string description)
{
return new Task
{
Category = category,
Status = status,
Description = descrtiption
};
}
}
Je le fais comme ça, car on me dit constamment que les entités ne devraient pas accéder aux référentiels, mais il serait beaucoup plus logique pour moi de le faire:
Entité:
public class Task
{
//...
public static void Create(Category category, string description)
{
return new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
}
}
Le référentiel de statuts est de toute façon injecté dans la dépendance, donc il n'y a pas de réelle dépendance, et cela me semble davantage que c'est le domaine qui prend la décision qu'une tâche par défaut est en suspens. La version précédente donne l'impression que c'est le calque d'application qui prend cette décision. Pourquoi les contrats de référentiel sont-ils souvent dans le domaine si cela ne devrait pas être possible?
Voici un exemple plus extrême, ici le domaine décide de l'urgence:
Entité:
public class Task
{
//...
public static void Create(Category category, string description)
{
var task = new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
if(someCondition)
{
if(someValue > anotherValue)
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
Il n'y a aucun moyen que vous souhaitiez passer dans toutes les versions possibles d'Urgence, et aucun moyen que vous ne souhaitiez calculer cette logique métier dans la couche application, alors ce serait sûrement le moyen le plus approprié?
Est-ce donc une raison valable pour accéder aux référentiels à partir du domaine?
EDIT: Cela pourrait également être le cas sur les méthodes non statiques:
public class Task
{
//...
public void Update(Category category, string description)
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
if(someCondition)
{
if(someValue > anotherValue)
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
la source
Status = _statusRepository.GetById(Constants.Status.OutstandingId)
est une règle commerciale , une règle que vous pourriez lire comme "L'entreprise dicte que le statut initial de toutes les tâches sera exceptionnel" et c'est pourquoi cette ligne de code n'appartient pas à un référentiel dont les seules préoccupations sont la gestion des données via les opérations CRUD.Je ne sais pas si votre exemple de statut est du vrai code ou ici juste à des fins de démonstration, mais il me semble étrange que vous devez implémenter le statut en tant qu'entité (sans parler d'une racine agrégée) lorsque son ID est une constante définie dans le code -
Constants.Status.OutstandingId
. Cela ne va-t-il pas à l'encontre de l'objectif des statuts "dynamiques" que vous pouvez ajouter autant de fois que vous le souhaitez dans la base de données?J'ajouterais que dans votre cas, la construction d'un
Task
(y compris le travail d'obtenir le bon statut de StatusRepository si besoin est) mériterait unTaskFactory
plutôt que de rester enTask
soi, car il s'agit d'un assemblage d'objets non trivial.Mais :
Cette déclaration est imprécise et trop simpliste au mieux, trompeuse et dangereuse au pire.
Il est assez communément admis dans les architectures de domaine qu'une entité ne devrait pas savoir comment se stocker - c'est le principe de l'ignorance de la persistance. Donc, pas d'appels à son référentiel pour s'ajouter au référentiel. Doit-elle savoir comment (et quand) stocker d'autres entités ? Encore une fois, cette responsabilité semble appartenir à un autre objet - peut-être un objet qui connaît le contexte d'exécution et la progression globale du cas d'utilisation actuel, comme un service de couche application.
Une entité pourrait-elle utiliser un référentiel pour récupérer une autre entité ? 90% du temps, cela ne devrait pas être nécessaire, car les entités dont il a besoin se trouvent généralement dans la portée de son agrégat ou peuvent être obtenues par la traversée d'autres objets. Mais il y a des moments où ils ne le sont pas. Si vous prenez une structure hiérarchique, par exemple, les entités doivent souvent accéder à tous leurs ancêtres, un petit-enfant particulier, etc. dans le cadre de leur comportement intrinsèque. Ils n'ont pas de référence directe à ces parents éloignés. Il serait gênant de leur faire passer ces proches comme paramètres de l'opération. Alors pourquoi ne pas utiliser un référentiel pour les obtenir - à condition qu'il s'agisse de racines agrégées?
Il y a quelques autres exemples. Le problème est que, parfois, il y a un comportement que vous ne pouvez pas placer dans un service de domaine car il semble s'intégrer parfaitement dans une entité existante. Et pourtant, cette entité doit accéder à un référentiel pour hydrater une racine ou une collection de racines qui ne peuvent pas lui être transmises.
Ainsi, l'accès à un référentiel à partir d'une entité n'est pas mauvais en soi , il peut prendre différentes formes qui résultent d'une variété de décisions de conception allant de catastrophique à acceptable.
la source
C'est une des raisons pour lesquelles je n'utilise pas d'énumérations ou de tables de recherche pures dans mon domaine. L'urgence et le statut sont tous deux des états et il existe une logique associée à un état qui appartient directement à l'état (par exemple, vers quels états puis-je passer en fonction de mon état actuel). De plus, en enregistrant un état en tant que valeur pure, vous perdez des informations telles que la durée de la tâche dans un état donné. Je représente les statuts comme une hiérarchie de classes comme ça. (En C #)
L'implémentation de CompletedTaskStatus serait à peu près la même.
Il y a plusieurs choses à noter ici:
Je protège les constructeurs par défaut. C'est ainsi que le framework peut l'appeler lors de l'extraction d'un objet de la persistance (EntityFramework Code-first et NHibernate utilisent des proxys dérivés de vos objets de domaine pour faire leur magie).
De nombreux dépositaires de biens sont protégés pour la même raison. Si je veux changer la date de fin d'un intervalle, je dois appeler la fonction Interval.End () (cela fait partie de la conception pilotée par le domaine, fournissant des opérations significatives plutôt que des objets de domaine anémiques.
Je ne le montre pas ici, mais la tâche cacherait également les détails de la façon dont elle stocke son état actuel. J'ai généralement une liste protégée d'États historiques que j'autorise le public à interroger s'il est intéressé. Sinon, j'expose l'état actuel en tant que getter qui interroge HistoricalStates.Single (state.Duration.End == null).
La fonction TransitionTo est importante car elle peut contenir une logique sur les états valides pour la transition. Si vous avez juste une énumération, cette logique doit se trouver ailleurs.
J'espère que cela vous aidera à mieux comprendre l'approche DDD.
la source
J'essaie de résoudre le même problème depuis un certain temps, j'ai décidé que je voulais pouvoir appeler Task.UpdateTask () comme ça, même si je préfère que ce soit spécifique au domaine, dans votre cas, je l'appellerais Task.ChangeCategory (...) pour indiquer une action et pas seulement CRUD.
de toute façon, j'ai essayé votre problème et j'ai trouvé ça ... prenez mon gâteau et mangez-le aussi. L'idée est que les actions se déroulent sur l'entité mais sans injection de toutes les dépendances. Au lieu de cela, le travail est effectué dans des méthodes statiques afin qu'ils puissent accéder à l'état de l'entité. L'usine met tout en œuvre et aura normalement tout ce dont elle a besoin pour faire le travail que l'entité doit faire. Le code client semble désormais propre et clair et votre entité ne dépend d'aucune injection de référentiel.
la source