Sembra esserci un ampio consenso nella comunità OOP sul fatto che il costruttore della classe non dovrebbe lasciare un oggetto parzialmente o addirittura completamente non inizializzato.
Cosa intendo per "inizializzazione"? In parole povere, il processo atomico che porta un oggetto appena creato in uno stato in cui si trovano tutti gli invarianti della sua classe. Dovrebbe essere la prima cosa che accade a un oggetto (dovrebbe essere eseguito una sola volta per oggetto) e nulla dovrebbe essere autorizzato a prendere un oggetto non inizializzato. (Pertanto, il consiglio frequente di eseguire l'inizializzazione degli oggetti proprio nel costruttore della classe. Per lo stesso motivo, i
Initialize
metodi sono spesso disapprovati, poiché rompono l'atomicità e consentono di afferrare e utilizzare un oggetto che non è ancora stato in uno stato ben definito.)
Problema: quando CQRS è combinato con il sourcing di eventi (CQRS + ES), in cui tutte le modifiche di stato di un oggetto vengono rilevate in una serie ordinata di eventi (flusso di eventi), mi viene da chiedermi quando un oggetto raggiunga effettivamente uno stato completamente inizializzato: Alla fine del costruttore della classe o dopo che il primo evento è stato applicato all'oggetto?
Nota: mi sto trattenendo dall'usare il termine "radice aggregata". Se preferisci, sostituiscilo ogni volta che leggi "oggetto".
Esempio di discussione: supponiamo che ogni oggetto sia identificato in modo univoco da un Id
valore opaco (pensa GUID). Un flusso di eventi che rappresenta i cambiamenti di stato di quell'oggetto può essere identificato nell'archivio eventi con lo stesso Id
valore: (Non preoccupiamoci del corretto ordine degli eventi).
interface IEventStore
{
IEnumerable<IEvent> GetEventsOfObject(Id objectId);
}
Supponiamo inoltre che ci siano due tipi di oggetti Customer
e ShoppingCart
. Concentriamoci su ShoppingCart
: una volta creati, i carrelli della spesa sono vuoti e devono essere associati esattamente a un cliente. L'ultimo bit è un invariante di classe: un ShoppingCart
oggetto che non è associato a Customer
è in uno stato non valido.
In OOP tradizionale, 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;
}
}
Sono comunque in perdita su come modellarlo in CQRS + ES senza finire con l'inizializzazione differita. 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 ovviamente dovrebbe essere il primo evento nel ShoppingCart
flusso di eventi di qualsiasi oggetto e quell'oggetto verrà inizializzato solo dopo 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 Customer
oggetto o un ShoppingCart
oggetto o qualsiasi altro tipo di oggetto per quella materia) ...
- Il costruttore dovrebbe essere senza parametri e non fare nulla, lasciando tutto il lavoro su un
void Apply(CreatedEmptyShoppingCart)
metodo (che è molto simile a quello accigliatoInitialize()
)? - O il costruttore dovrebbe ricevere un flusso di eventi e riprodurlo (il che rende di nuovo atomica l'inizializzazione, ma significa che ogni costruttore di classi contiene la stessa logica generica di "riproduzione e applicazione", ovvero duplicazione di codice indesiderata)?
- O dovrebbe esserci sia un costruttore OOP tradizionale (come mostrato sopra) che inizializza correttamente l'oggetto, e quindi tutti gli eventi, tranne il primo, sono
void Apply(…)
interessati ad esso?
Non mi aspetto che la risposta fornisca un'implementazione demo completamente funzionante; Sarei già molto felice se qualcuno potesse spiegare dove sono sbagliati i miei ragionamenti o se l'inizializzazione degli oggetti è davvero un "punto critico" nella maggior parte delle implementazioni di CQRS + ES.
Initialize
metodo) avrebbero occupato. Questo mi porta alla domanda: come potrebbe essere una tua fabbrica del genere?