Многие статьи о CQRS подразумевают, что саги имеют внутреннее состояние и должны быть сохранены в хранилище событий. Я не понимаю, почему это необходимо.Почему саги (ака, менеджеры процессов) содержат внутреннее состояние и почему они сохраняются в хранилище событий?
Например, у меня есть три агрегата: Order
, Invoice
и Shipment
. Когда заказчик отправляет заказ, начинается процесс заказа. Тем не менее, отправка не может быть отправлена до момента оплаты счета-фактуры, и отгрузка сначала была подготовлена.
- Клиент делает заказ с помощью команды
PlaceOrder
. OrderCommandHandler
звонкиOrderRepository::placeOrder()
.- Метод
OrderRepository::placeOrder()
возвращает событиеOrderPlaced
, которое хранится вEventStore
и отправляется по адресуEventBus
. - Событие
OrderPlaced
содержитorderId
и предопределяетinvoiceId
иshipmentId
. OrderProcess
(«сага») получает событиеOrderPlaced
, создавая счет и при необходимости готовя к отправке (достижение идемпотенции в обработчике событий). 6a. В какой-то момент времениOrderProcess
принимает событиеInvoicePaid
. Он проверяет, была ли отгрузка подготовлена путем поиска груза вShipmentRepository
, и если да, отправляет груз. 6b. В какой-то момент времениOrderProcess
принимает событиеShipmentPrepared
. Он chekcs, чтобы узнать, был ли оплачен счет, просмотрев счет-фактуру вInvoiceRepository
, и если да, отправляет груз.
Для всех опытных гуру DDD/CQRS/ES, можете ли вы рассказать мне, какую концепцию мне не хватает, и почему этот дизайн «саги о безгражданстве» не будет работать?
class OrderCommandHandler {
public function handle(PlaceOrder $command) {
$event = $this->orderRepository->placeOrder($command->orderId, $command->customerId, ...);
$this->eventStore->store($event);
$this->eventBus->emit($event);
}
}
class OrderRepository {
public function placeOrder($orderId, $customerId, ...) {
$invoiceId = randomString();
$shipmentId = randomString();
return new OrderPlaced($orderId, $customerId, $invoiceId, $shipmentId);
}
}
class InvoiceRepository {
public function createInvoice($invoiceId, $customerId, ...) {
// Etc.
return new InvoiceCreated($invoiceId, $customerId, ...);
}
}
class ShipmentRepository {
public function prepareShipment($shipmentId, $customerId, ...) {
// Etc.
return new ShipmentPrepared($shipmentId, $customerId, ...);
}
}
class OrderProcess {
public function onOrderPlaced(OrderPlaced $event) {
if (!$this->invoiceRepository->hasInvoice($event->invoiceId)) {
$invoiceEvent = $this->invoiceRepository->createInvoice($event->invoiceId, $event->customerId, $event->invoiceId, ...);
$this->eventStore->store($invoiceEvent);
$this->eventBus->emit($invoiceEvent);
}
if (!$this->shipmentRepository->hasShipment($event->shipmentId)) {
$shipmentEvent = $this->shipmentRepository->prepareShipment($event->shipmentId, $event->customerId, ...);
$this->eventStore->store($shipmentEvent);
$this->eventBus->emit($shipmentEvent);
}
}
public function onInvoicePaid(InvoicePaid $event) {
$order = $this->orderRepository->getOrders($event->orderId);
$shipment = $this->shipmentRepository->getShipment($order->shipmentId);
if ($shipment && $shipment->isPrepared()) {
$this->sendShipment($shipment);
}
}
public function onShipmentPrepared(ShipmentPrepared $event) {
$order = $this->orderRepository->getOrders($event->orderId);
$invoice = $this->invoiceRepository->getInvoice($order->invoiceId);
if ($invoice && $invoice->isPaid()) {
$this->sendShipment($this->shipmentRepository->getShipment($order->shipmentId));
}
}
private function sendShipment(Shipment $shipment) {
$shipmentEvent = $shipment->send();
$this->eventStore->store($shipmentEvent);
$this->eventBus->emit($shipmentEvent);
}
}
Почти одинаковый вопрос был задан вчера в почтовом списке DDD/CQRS https://groups.google.com/forum/#!topic/dddcqrs/danMYZS6kKg –
Это был я. В CQRS/ES «лучшая практика» существует много разных мнений. Чем больше сеть, тем больше рыбы. – magnus