Похоже, что в сообществе ООП широко распространено мнение, что конструктор класса не должен оставлять объект частично или даже полностью неинициализированным.
Что я имею в виду под «инициализацией»? Грубо говоря, атомарный процесс, который приводит вновь созданный объект в состояние, в котором содержатся все его классовые инварианты. Это должно быть первое, что происходит с объектом (он должен запускаться только один раз для каждого объекта), и ничто не должно иметь права завладеть неинициализированным объектом. (Таким образом, часто советуют выполнять инициализацию объекта прямо в конструкторе класса. По той же причине
Initialize
методы часто не одобряются, поскольку они разбивают атомарность и позволяют получить и использовать объект, который еще не создан. в четко определенном состоянии.)
Проблема: Когда CQRS объединяется с источником событий (CQRS + ES), где все изменения состояния объекта попадают в упорядоченную серию событий (поток событий), мне остается только удивляться, когда объект фактически достигает полностью инициализированного состояния: В конце конструктора класса или после того, как к объекту было применено самое первое событие?
Примечание: я воздерживаюсь от использования термина «совокупный корень». Если вы предпочитаете, заменяйте его всякий раз, когда читаете «объект».
Пример для обсуждения: предположим, что каждый объект уникально идентифицируется по некоторому непрозрачному Id
значению (например, GUID). Поток событий, представляющий изменения состояния этого объекта, может быть идентифицирован в хранилище событий по тому же Id
значению: (Давайте не будем беспокоиться о правильном порядке событий.)
interface IEventStore
{
IEnumerable<IEvent> GetEventsOfObject(Id objectId);
}
Предположим далее, что есть два типа объектов Customer
и ShoppingCart
. Давайте сосредоточимся на том, что ShoppingCart
: корзины для покупок пусты и должны быть связаны только с одним клиентом. Этот последний бит является инвариантом класса: ShoppingCart
объект, который не связан с, Customer
находится в недопустимом состоянии.
В традиционном ООП это можно смоделировать в конструкторе:
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;
}
}
Однако я не знаю, как смоделировать это в CQRS + ES, не заканчивая отложенной инициализацией. Поскольку этот простой бит инициализации фактически является изменением состояния, не нужно ли его моделировать как событие ?:
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.
Очевидно, это должно быть самое первое событие в ShoppingCart
потоке событий любого объекта, и этот объект будет инициализирован только после того, как событие будет применено к нему.
Поэтому, если инициализация становится частью потока воспроизведения «Воспроизведение» (это очень общий процесс, который, вероятно, будет работать одинаково, будь то для Customer
объекта или ShoppingCart
объекта или любого другого типа объекта в этом отношении)…
- Должен ли конструктор быть без параметров и ничего не делать, оставляя всю работу какому-либо
void Apply(CreatedEmptyShoppingCart)
методу (который почти такой же, как нахмурившийсяInitialize()
)? - Или же конструктор должен принять поток событий и воспроизвести его (что делает инициализацию снова атомарной, но означает, что каждый конструктор класса содержит одну и ту же универсальную логику «воспроизведения и применения», то есть нежелательное дублирование кода)?
- Или должен существовать традиционный конструктор ООП (как показано выше), который правильно инициализирует объект, а затем все события, кроме первого,
void Apply(…)
связаны с ним?
Я не ожидаю ответа, чтобы обеспечить полностью работающую демонстрационную реализацию; Я уже был бы очень рад, если бы кто-то мог объяснить, где мои рассуждения ошибочны, или действительно ли инициализация объекта является «болевым пунктом» в большинстве реализаций CQRS + ES.
Initialize
занимали бы конструкторы совокупности (+ возможно, метод). Это подводит меня к вопросу, на что может быть похожа такая ваша фабрика?