Laravel Event Sourcing (Spatie) - Как работать с более сложными бизнес-правилами - PullRequest
0 голосов
/ 03 октября 2019

Я в настоящее время начинаю в мире источников событий, используя пакет spaties spatie / laravel-event-sourcing .

После выполнения базовой настройки и руководств, а также чтения/ просматривая множество других руководств по сбору событий, у меня есть базовое понимание, но я пытаюсь понять, как следует реализовывать более сложные бизнес-правила.

Простое правило - пример баланса банка

Пример нижепрямо вперед и имеет смысл. Вы вычитаете деньги из баланса за каждое событие. Если вы опускаетесь ниже порогового значения, возникает исключение до того, как ему разрешается опускаться ниже.

public function subtractMoney(int $amount)
{
    if (!$this->hasSufficientFundsToSubtractAmount($amount)) {
        $this->recordThat(new AccountLimitHit());

        if ($this->needsMoreMoney()) {
            $this->recordThat(new MoreMoneyNeeded());
        }

        $this->persist();

        throw CouldNotSubtractMoney::notEnoughFunds($amount);
    }

    $this->recordThat(new MoneySubtracted($amount));
}

protected function applyMoneySubtracted(MoneySubtracted $event)
{
    $this->balance -= $event->amount;

    $this->accountLimitHitInARow = 0;
}

Более сложный - дополнительные атрибуты

В приведенном выше примере у нас есть только одинАтрибут (количество).

В моем случае мой общий корень - это товарный запас. Я хочу, чтобы агрегат мог фиксировать все движения инвентаря в разных местах хранения. т. е.

  • запас поступает в местоположение
  • запас перемещается из одного местоположения A в местоположение B
  • запас присваивается заказу 1 из местоположения A
  • запас выбирается для заказа 1 из местоположения A
  • запас упаковывается в пакет отгрузки Z для заказа 1

Выполнение прогнозов на самом деле довольно просто. Например, мой проектор инвентаризации показывает каждый продукт в каждом месте, и сколько получено, выделено и т. Д. ..

public function onStockReceived(StockReceived $event, string $aggregateUuid)
{
    $inventory = Inventory::firstOrNew([
        'product_id' => $aggregateUuid,
        'location_id' => $event->locationId
    ]);

    $inventory->received += $event->amount;

    $inventory->save();
}

Моя проблема с этим снова в сводном корне при попытке определить бизнес-правила. Такое ощущение, что мне нужно продублировать код в моем проекторе, чтобы иметь возможность сверяться с данными в событиях.

В приведенном ниже примере я хочу убедиться, что я не делаю больше запасов, чем естьбыл получен. т.е. не опускайтесь ниже 0. Это начинает казаться довольно сложным и дублирующим счетчиком, который уже сохранен в моем проекторе.

public function makeStockAvailable(int $amount, $locationId)
{
    if($this->hasInsufficientStockToMakeAvailable($amount, $locationId)){
        throw CouldNotMakeStockAvailable::insufficientStock($amount, $this->locations[$locationId]['received']);
    }

    $this->recordThat(new StockMadeAvailable($amount, $locationId));

    return $this;
}

public function applyStockMadeAvailable(StockMadeAvailable $event)
{
    $this->stockMadeAvailableInLocation($event->amount, $event->locationId);

    $this->availableStockTotal($event->amount);
}

private function stockMadeAvailableInLocation($amount, $locationId)
{
    $this->locations[$locationId]['received'] = $this->locations[$locationId]['received'] ?? 0;

    $this->locations[$locationId]['received'] -= $amount;

    $this->locations[$locationId]['available'] = $this->locations[$locationId]['available'] ?? 0;

    $this->locations[$locationId]['available'] += $amount;
}

private function availableStockTotal($amount)
{
    $this->received -= $amount;

    $this->available += $amount;
}

private function hasInsufficientStockToMakeAvailable($amount, $locationId): bool
{
    if(isset($this->locations[$locationId]['received'])){
        return $this->locations[$locationId]['received'] - $amount < 0;
    }

    return false;
}

Я предполагаю, что использование Eloquent в моем совокупном корне не разрешено (поискпротив проекторов), поскольку это вызовет огромное количество запросов к базе данных, и я не уверен, что AR должен зависеть от проекции для принятия решения?

Кроме того, невозможно добавить какие-либо бизнес-правила в мои проекторы, так как этопосле того, как событие уже утверждено.

Мне очень нравится пакет spaties, поскольку он облегчает основы создания событий, но кажется, что есть довольно большой скачок, чтобы сделать его работоспособным с более сложными решениями.

Пример бизнес-логики для решения

public function moveAvailableStock(int $amount, $locationIdFrom, $locationIdTo)
{
    if(($available = $this->locations[$locationIdFrom]['available'] ?? 0) < $amount){
        throw CouldNotMoveStock::insufficientStock($amount, $available, $locationIdFrom);
    }

    $this->recordThat(new StockMoved($amount, $locationIdFrom, $locationIdTo));

    return $this;
}

Когда я воспроизводю события, мне нужно хранить каждое событие запаса по его статусу (получен, доступен, выделен), а также по месту, в котором онихранятся. В настоящее время я делаю это с массивом, хранящимся в закрытой переменной ($ location), и он работает, но кажется, что:

a) он не будет масштабироваться. Массив может увеличиваться до тысяч элементов в зависимости от того, сколько мест используется.

b) он может очень быстро усложниться. Это лишь одно из многих правил.

1 Ответ

1 голос
/ 03 октября 2019

Я думаю, что главная проблема здесь в том, что вы пропускаете важный факт в event-sourcing, который является events хранилищем.

Внутри вашей проекции похоже, что вы храните сущности. Проекции - это, в основном, Listeners, которые реагируют на что-то происшедшее.

В общем случае вам нужно будет применить следующий сценарий

  1. Составьте свой Совокупный корень из списка событий (пуст, если нетсобытий пока нет)
  2. Примените бизнес-логику внутри своего AR (это должно быть источником правды для бизнес-инварианта)
  3. Запишите событие наподобие StockMadeAvailable
  4. Сохраните ваши событияв Event Store -> который может быть MySQL
  5. Вы можете использовать свои проекции для прослушивания ваших событий или внешних событий и обновления ваших моделей чтения, они в основном являются таблицами БД, разработанными таким образом, чтобы обеспечить высокуюзапросы производительности

Кроме того, Spatie предоставляет инфраструктуру для Event Sourcing, но, тем не менее, состав ваших инвариантов Aggregates / Entities / Business зависит от потребностей вашего бизнеса

Update #1

Относительно этого пункта - а) он не будет масштабироваться. Массив может увеличиваться до тысяч элементов в зависимости от того, сколько мест используется.

  • Я бы лучше поговорил с деловыми парнями / экспертами по доменам, чтобы определить пиковое и среднее количество мест
  • Использование набора сущностей и объектов-значений может дать вам гибкость, а не array $locations, вы можете использовать хеш-карты или другие структуры данных, которые соответствуют вашим потребностям

b) это может очень быстро усложниться. Это только одно из многих правил. - Как правило, у вас будут разные функции для каждого правила. - Если оно становится слишком сложным, почему бы не иметь Домен Rules с той же идеей, что и Домен Exceptions

Это просто идеи, которые могут помочь.

...