Sembra esserci un accordo diffuso nella comunità OOP sul fatto che il costruttore della classe non dovrebbe lasciare un oggetto in parte o addirittura completamente non inizializzato.
What do I mean by "initialization"? Roughly speaking, the atomic process that brings a newly created object into a state where all of its class invariants hold. It should be the first thing that happens to an object, (it should only run once per object,) and nothing should be permitted to get hold of an un-initialized object. (Thus the frequent advice to perform object initialization right in the class constructor. For the same reason,
Initialize
methods are often frowned upon, as these break apart the atomicity and make it possible to get hold of, and use, an object that is not yet in a well-defined state.)
Problema: Quando CQRS è combinato con il sourcing di eventi (CQRS + ES), in cui tutte le modifiche di stato di un oggetto vengono catturate in una serie ordinata di eventi (flusso di eventi), mi chiedo quando un oggetto raggiunge effettivamente uno stato completamente inizializzato: alla fine del costruttore della classe, o dopo che il primo evento è stato applicato all'oggetto?
Note: I'm refraining from using the term "aggregate root". If you prefer, substitute it whenever you read "object".
Esempio di discussione: Supponiamo che ogni oggetto sia identificato in modo univoco da un valore opaco di Id
(pensa GUID). Un flusso di eventi che rappresenta le modifiche di stato di quell'oggetto può essere identificato nello store eventi con lo stesso valore di Id
: (Non preoccupiamoci dell'ordine di evento corretto.)
interface IEventStore
{
IEnumerable<IEvent> GetEventsOfObject(Id objectId);
}
Supponiamo inoltre che ci siano due tipi di oggetto Customer
e ShoppingCart
. Concentriamoci su ShoppingCart
: una volta creati, i carrelli degli acquisti sono vuoti e devono essere associati esattamente a un cliente. L'ultimo bit è un invariante di classe: un oggetto ShoppingCart
non associato a Customer
è in uno stato non valido.
Nel tradizionale OOP, si potrebbe modellare questo nel costruttore:
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;
}
}
Tuttavia non riesco a capire come modellare questo in CQRS + ES senza finire con l'inizializzazione posticipata. Poiché questo semplice bit di inizializzazione è effettivamente un cambiamento di stato, non dovrebbe essere modellato come un evento?:
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.
Questo dovrebbe ovviamente essere il primo evento in qualsiasi flusso di eventi di ShoppingCart
dell'oggetto, e quell'oggetto sarebbe inizializzato solo una volta che l'evento è stato applicato ad esso.
Quindi se l'inizializzazione diventa parte del flusso di eventi "playback" (che è un processo molto generico che probabilmente funzionerebbe allo stesso modo, sia per un oggetto Customer
o un ShoppingCart
o qualsiasi altro tipo di oggetto per quella materia ) ...
- Il costruttore deve essere parametrico e non fare nulla, lasciando tutto il lavoro a un metodo
void Apply(CreatedEmptyShoppingCart)
(che è molto simile alInitialize()
accigliato)? - Oppure il costruttore dovrebbe ricevere un flusso di eventi e riprodurlo (il che rende di nuovo l'inizializzazione atomica, ma significa che ogni costruttore di classi contiene la stessa logica generica di "riproduzione e applicazione", ovvero la duplicazione indesiderata del codice)?
- O dovrebbe esserci sia un costruttore OOP tradizionale (come mostrato sopra) che inizializza correttamente l'oggetto, sia quindi tutti gli eventi ma il primo è
void Apply(…)
-ed ad esso?
Non mi aspetto che la risposta fornisca un'implementazione demo pienamente funzionante; Sarei già molto felice se qualcuno potesse spiegare dove il mio ragionamento è difettoso, o se l'inizializzazione dell'oggetto è un "punto di dolore" nella maggior parte delle implementazioni di CQRS + ES.