Il semble y avoir un large consensus dans la communauté OOP selon lequel le constructeur de classe ne doit pas laisser un objet partiellement ou même non initialisé.
Qu'est-ce que j'entends par "initialisation"? Grosso modo, le processus atomique qui amène un objet nouvellement créé dans un état où tous ses invariants de classe tiennent. Cela devrait être la première chose qui arrive à un objet (il ne devrait s'exécuter qu'une seule fois par objet) et rien ne devrait être autorisé à mettre la main sur un objet non initialisé. (D'où les conseils fréquents pour effectuer une initialisation d'objet directement dans le constructeur de classe. Pour la même raison, les
Initialize
méthodes sont souvent désapprouvées, car elles séparent l'atomicité et permettent de saisir et d'utiliser un objet qui n'est pas encore dans un état bien défini.)
Problème: lorsque CQRS est combiné avec le sourcing d'événements (CQRS + ES), où tous les changements d'état d'un objet sont capturés dans une série ordonnée d'événements (flux d'événements), je me demande quand un objet atteint réellement un état entièrement initialisé: À la fin du constructeur de classe, ou après que le tout premier événement a été appliqué à l'objet?
Remarque: je m'abstiens d'utiliser le terme "racine agrégée". Si vous préférez, remplacez-le chaque fois que vous lisez "objet".
Exemple de discussion: Supposons que chaque objet est identifié de manière unique par une Id
valeur opaque (pensez GUID). Un flux d'événements représentant les changements d'état de cet objet peut être identifié dans le magasin d'événements par la même Id
valeur: (Ne nous inquiétons pas du bon ordre des événements.)
interface IEventStore
{
IEnumerable<IEvent> GetEventsOfObject(Id objectId);
}
Supposons en outre qu'il existe deux types d'objet Customer
et ShoppingCart
. Concentrons-nous sur ShoppingCart
: Une fois créés, les paniers sont vides et doivent être associés à exactement un client. Ce dernier bit est un invariant de classe: un ShoppingCart
objet qui n'est pas associé à un Customer
est dans un état non valide.
Dans la POO traditionnelle, on pourrait modéliser cela dans le constructeur:
partial class ShoppingCart
{
public Id Id { get; private set; }
public Customer Customer { get; private set; }
public ShoppingCart(Id id, Customer customer)
{
this.Id = id;
this.Customer = customer;
}
}
Cependant, je ne sais pas comment modéliser cela dans CQRS + ES sans se retrouver avec une initialisation différée. Puisque ce simple bout d'initialisation est effectivement un changement d'état, ne devrait-il pas être modélisé comme un événement?:
partial class CreatedEmptyShoppingCart
{
public ShoppingCartId { get; private set; }
public CustomerId { get; private set; }
}
// Note: `ShoppingCartId` is not actually required, since that Id must be
// known in advance in order to fetch the event stream from the event store.
Cela devrait évidemment être le tout premier événement dans ShoppingCart
le flux d'événements d' un objet, et cet objet ne serait initialisé qu'une fois que l'événement lui aurait été appliqué.
Donc, si l'initialisation fait partie du flux d'événements "lecture" (qui est un processus très générique qui fonctionnerait probablement de la même manière, que ce soit pour un Customer
objet ou un ShoppingCart
objet ou tout autre type d'objet d'ailleurs) ...
- Le constructeur devrait-il être sans paramètre et ne rien faire, laissant tout le travail à une
void Apply(CreatedEmptyShoppingCart)
méthode (qui est à peu près la même que celle désapprouvéeInitialize()
)? - Ou le constructeur doit-il recevoir un flux d'événements et le lire (ce qui rend l'initialisation atomique à nouveau, mais signifie que le constructeur de chaque classe contient la même logique générique "lecture et application", c'est-à-dire duplication de code indésirable)?
- Ou devrait-il y avoir à la fois un constructeur OOP traditionnel (comme indiqué ci-dessus) qui initialise correctement l'objet, puis tous les événements sauf les premiers lui sont
void Apply(…)
liés?
Je ne m'attends pas à ce que la réponse fournisse une implémentation de démonstration pleinement fonctionnelle; Je serais déjà très heureux si quelqu'un pouvait expliquer où mon raisonnement est défectueux, ou si l'initialisation d'objet est vraiment un "point douloureux" dans la plupart des implémentations CQRS + ES.
Initialize
qu'auraient occupé les constructeurs d'agrégats (+ peut-être une méthode). Cela m'amène à la question: à quoi pourrait ressembler une telle usine?