Permettez-moi de commencer par m'excuser pour la longueur du message, mais je voulais vraiment transmettre autant de détails à l'avance, donc je ne prends pas votre temps à faire des allers-retours dans les commentaires.
Je suis en train de concevoir une application suivant une approche DDD et je me demande quels conseils je peux suivre pour déterminer si une racine agrégée doit contenir un autre AR ou si elles doivent être laissées en tant qu'AR «autonomes» distincts.
Prenons le cas d'une application d'horloge simple qui permet aux employés de se synchroniser pour la journée. L'interface utilisateur leur permet de saisir leur ID d'employé et leur code PIN qui sont ensuite validés et l'état actuel de l'employé récupéré. Si l'employé est actuellement synchronisé, l'interface utilisateur affiche un bouton "Clock Out"; et, inversement, s'ils ne sont pas synchronisés, le bouton affiche "Clock In". L'action effectuée par le bouton correspond également à l'état du salarié.
L'application est un client Web qui appelle un serveur principal exposé via une interface de service RESTful. Mon premier passage à créer des URL intuitives et lisibles a abouti aux deux points de terminaison suivants:
http://myhost/employees/{id}/clockin
http://myhost/employees/{id}/clockout
REMARQUE: ceux-ci sont utilisés après la validation de l'ID et du code PIN de l'employé et la transmission d'un "jeton" représentant "l'utilisateur" dans un en-tête. Cela est dû au fait qu'il existe un «mode gestionnaire» qui permet à un gestionnaire ou à un superviseur de pointer ou de quitter un autre employé. Mais, pour le bien de cette discussion, j'essaie de rester simple.
Sur le serveur, j'ai un ApplicationService qui fournit l'API. Mon idée initiale pour la méthode ClockIn est quelque chose comme:
public void ClockIn(String id)
{
var employee = EmployeeRepository.FindById(id);
if (employee == null) throw SomeException();
employee.ClockIn();
EmployeeRepository.Save();
}
Cela semble assez simple jusqu'à ce que nous réalisions que les informations de carte de temps de l'employé sont réellement conservées sous forme de liste de transactions. Cela signifie que chaque fois que j'appelle ClockIn ou ClockOut, je ne change pas directement l'état de l'employé mais, au lieu de cela, j'ajoute une nouvelle entrée dans la feuille de temps de l'employé. L'état actuel de l'employé (cadencé ou non) est dérivé de l'entrée la plus récente de la feuille de temps.
Donc, si je choisis le code ci-dessus, mon référentiel doit reconnaître que les propriétés persistantes de l'employé n'ont pas changé, mais qu'une nouvelle entrée a été ajoutée dans la feuille de temps de l'employé et effectuer une insertion dans le magasin de données.
D'un autre côté (et voici la question ultime de l'article), TimeSheet semble être à la fois une racine agrégée et une identité (l'ID et la période de l'employé) et je pourrais tout aussi facilement implémenter la même logique que TimeSheet. (employeeId).
Je me retrouve à débattre du bien-fondé des deux approches et, comme indiqué dans le premier paragraphe, je me demande quels critères je devrais évaluer pour déterminer quelle approche convient le mieux au problème.
la source
Réponses:
Je serais enclin à mettre en place un service de suivi du temps:
Je ne pense pas que ce soit la responsabilité de la feuille de temps de pointer à l'arrivée ou au départ, ni la responsabilité des employés.
la source
Les racines d'agrégat ne doivent pas se contenir (bien qu'elles puissent contenir des ID à d'autres).
Premièrement, TimeSheet est-il vraiment une racine agrégée? Le fait qu'il ait une identité en fait une entité, pas nécessairement un AR. Découvrez l'une des règles:
Vous définissez l'identité de la feuille de temps comme l'ID de l'employé et la période de temps, ce qui suggère que la feuille de temps fait partie de l'employé avec la période de temps étant l'identité locale. Puisqu'il s'agit d'une application Time Clock , l'employé a-t-il pour principal objectif d'être un conteneur pour TimeSheets?
En supposant que vous ayez des AR, ClockIn et ClockOut ressemblent plus à des opérations TimeSheet qu'à celles des employés. Votre méthode de couche de service pourrait ressembler à ceci:
Si vous avez vraiment besoin de suivre l'état synchronisé à la fois dans Employee et TimeSheet, jetez un œil aux événements de domaine (je ne pense pas qu'ils figurent dans le livre d'Evans, mais il existe de nombreux articles en ligne). Cela ressemblerait à quelque chose comme: Employee.ClockIn () déclenche l'événement EmployeeClockedIn, qu'un gestionnaire d'événements récupère et appelle à son tour TimeSheet.LogEmployeeClockIn ().
la source
Une racine agrégée ne doit jamais contenir une autre racine agrégée.
Dans votre description, vous avez une entité Employé et également une entité Feuille de temps. Ces deux entités sont distinctes, mais pourraient inclure des références l'une à l'autre (ex: c'est la feuille de temps de Bob).
C'est beaucoup la modélisation de base.
La question racine globale est légèrement différente. Si ces deux entités sont transactionnellement distinctes l'une de l'autre, alors elles peuvent être correctement modélisées comme deux agrégats distincts. Par transactionnellement distinct, je veux dire qu'il n'y a pas d'invariant commercial qui nécessite de connaître simultanément l'état actuel de l'employé et l'état actuel de TimeSheet.
En fonction de ce que vous décrivez, Timesheet.clockIn et Timesheet.clockOut n'ont jamais besoin de vérifier les données dans Employee pour déterminer si la commande est autorisée. Donc, pour une grande partie du problème, deux AR différents semblent raisonnables.
Une autre façon de considérer les limites de l'AR serait de demander quels types de modifications sont autorisés simultanément. Le gestionnaire est-il autorisé à chronométrer l'employé alors que les RH jouent avec le profil de l'employé?
L'identité signifie uniquement qu'il s'agit d'une entité - c'est l'invariant métier qui détermine s'il doit être considéré comme une racine agrégée distincte.
Peut-être - l'agrégat devrait. Cela ne signifie pas nécessairement que la feuille de temps devrait.
Autrement dit, si les règles de modification d'une feuille de temps dépendent de l'état actuel de l'entité Employé, alors l'employé et la feuille de temps doivent définitivement faire partie du même agrégat, et l'agrégat dans son ensemble est responsable de s'assurer que les règles sont suivi.
Un agrégat a une entité racine; l'identifier fait partie du puzzle. Si un employé a plus d'une feuille de temps et qu'ils font tous deux partie du même agrégat, alors la feuille de temps n'est certainement pas la racine. Ce qui signifie que l'application ne peut pas directement modifier ou envoyer des commandes à la feuille de temps - elles doivent être envoyées à l'objet racine (vraisemblablement l'employé), qui peut déléguer une partie de la responsabilité.
Une autre vérification consisterait à déterminer comment les feuilles de temps sont créées. S'ils sont créés implicitement lorsqu'un employé arrive, alors c'est un autre indice qu'ils sont une entité subordonnée dans l'ensemble.
Soit dit en passant, il est peu probable que vos agrégats aient leur propre sens du temps. Au lieu de cela, le temps devrait leur être passé
la source