Wydaje się, że istnieje powszechna zgoda w społeczności OOP, że konstruktor klasy nie powinien pozostawiać obiektu częściowo, a nawet całkowicie niezainicjowanego.
Co rozumiem przez „inicjalizację”? Z grubsza mówiąc, proces atomowy , który wprowadza nowo utworzony obiekt w stan, w którym utrzymują się wszystkie niezmienniki jego klasy. Powinna być pierwszą rzeczą, która przytrafia się obiektowi (powinna działać tylko raz na obiekt) i nic nie powinno mieć możliwości przechwycenia niezainicjowanego obiektu. (Dlatego częsta rada dotycząca inicjowania obiektu bezpośrednio w konstruktorze klasy. Z tego samego powodu
Initialize
metody są często odrzucane, ponieważ rozbijają one atomowość i umożliwiają uzyskanie i użycie obiektu, który nie jest jeszcze w dobrze zdefiniowanym stanie).
Problem: Kiedy CQRS jest połączone z pozyskiwaniem zdarzeń (CQRS + ES), gdzie wszystkie zmiany stanu obiektu są wychwytywane w uporządkowanej serii zdarzeń (strumień zdarzeń), zastanawiam się, kiedy obiekt faktycznie osiągnie w pełni zainicjowany stan: Na końcu konstruktora klasy, czy po zastosowaniu do obiektu pierwszego zdarzenia?
Uwaga: powstrzymuję się od używania terminu „agreguj root”. Jeśli wolisz, zamień go za każdym razem, gdy czytasz „obiekt”.
Przykład do dyskusji: Załóżmy, że każdy obiekt jest jednoznacznie identyfikowany przez jakąś nieprzezroczystą Id
wartość (pomyśl GUID). Strumień zdarzeń reprezentujący zmiany stanu tego obiektu można zidentyfikować w magazynie zdarzeń na podstawie tej samej Id
wartości: (Nie martwmy się o prawidłową kolejność zdarzeń).
interface IEventStore
{
IEnumerable<IEvent> GetEventsOfObject(Id objectId);
}
Załóżmy ponadto, że istnieją dwa typy obiektów Customer
i ShoppingCart
. Skupmy się na ShoppingCart
: Po utworzeniu koszyki są puste i muszą być powiązane z dokładnie jednym klientem. Ten ostatni bit jest niezmiennikiem klasy: ShoppingCart
Obiekt, który nie jest powiązany z, Customer
jest w niepoprawnym stanie.
W tradycyjnym OOP można modelować to w konstruktorze:
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;
}
}
Brakuje mi jednak sposobu modelowania tego w CQRS + ES bez odroczonej inicjalizacji. Ponieważ ta prosta inicjalizacja jest w rzeczywistości zmianą stanu, czy nie trzeba jej modelować jako zdarzenia ?:
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.
To oczywiście musi być pierwsze zdarzenie w ShoppingCart
strumieniu zdarzeń dowolnego obiektu, a obiekt ten zostanie zainicjowany dopiero po zastosowaniu zdarzenia do niego.
Więc jeśli inicjalizacja stanie się częścią „odtwarzania” strumienia zdarzeń (co jest bardzo ogólnym procesem, który prawdopodobnie działałby tak samo, czy to dla Customer
obiektu, ShoppingCart
obiektu czy innego typu obiektu w tym przypadku)…
- Czy konstruktor powinien być pozbawiony parametrów i nic nie robić, pozostawiając całą pracę jakiejś
void Apply(CreatedEmptyShoppingCart)
metodzie (która jest prawie taka sama jak zmarszczone brwiInitialize()
)? - A może konstruktor powinien odbierać strumień zdarzeń i odtwarzać go (co powoduje, że inicjalizacja staje się atomowa, ale oznacza, że konstruktor każdej klasy zawiera tę samą ogólną logikę „odtwarzaj i stosuj”, tj. Niechciane powielanie kodu)?
- A może powinien istnieć zarówno tradycyjny konstruktor OOP (jak pokazano powyżej), który poprawnie inicjuje obiekt, a następnie wszystkie zdarzenia oprócz pierwszego są
void Apply(…)
z nim powiązane?
Nie oczekuję, że odpowiedź zapewni w pełni działającą implementację demonstracyjną; Byłbym bardzo szczęśliwy, gdyby ktoś mógł wyjaśnić, w jaki sposób moje rozumowanie jest wadliwe, lub czy inicjalizacja obiektu naprawdę jest „przeszkodą” w większości implementacji CQRS + ES.
Initialize
które zajmowałyby agregatory konstruktorów (+ być może metoda). To prowadzi mnie do pytania, jak mogłaby wyglądać twoja fabryka?