Laravel Event Projector и концепция порождения событий

от автора

Перевод статьи подготовлен для студентов профессионального курса «Framework Laravel»


Фрек ван дер Хертен (Freek Van der Herten) и команда Spatie долго трудились над Laravel Event Projector, пакетом, позволяющим применять концепцию порождения событий (Event Sourcing) во фреймворке Laravel. И вот наконец доступна первая стабильная версия (v1.0.0)!

Вы можете установить Event Projector в свой проект при помощи composer и благодаря автоматическому обнаружению пакетов в Laravel приступить к работе сразу же после публикации миграций пакета и конфигурирования!

composer require spatie/laravel-event-projector:^1.0.0

Event Projector требует наличия PHP 7.2, поэтому ваше приложение должно поддерживать последнюю версию PHP, хотя самому фреймворку Laravel достаточно PHP версии не ниже 7.1.3.

Что такое «порождение событий»

В статье Шаблон порождения событий эта концепция описывается следующим образом:

Вместо того, чтобы хранить текущее состояние данных в предметной области (domain), используйте для записи всей последовательности действий, производимых над этими данными, хранилище, работающее в режиме append-only — только добавление данных. Хранилище выступает в роли системы записей (system of record) и может использоваться для материализации объектов предметной области. Это позволяет упростить задачи в сложных предметных областях, поскольку отпадает необходимость в синхронизации модели данных и предметной области, что приводит к улучшению масштабируемости, повышению производительности и оперативности работы. Подобный подход обеспечивает согласованность транзакционных данных, а также позволяет вести полноценные журнал изменений и историю, благодаря чему становится возможным осуществление компенсирующих действий.

Я рекомендую ознакомиться со всей статьей целиком. Там дается прекрасное описание этого шаблона, озвучены соображения по правильному использованию, а также описаны ситуации, где он может оказаться полезным.

Мне также нравится объяснение концепции порождения событий, которое дано во введении к документации по Event Projector:

Порождение событий является по отношению к данным тем же самым, чем Git является по отношению к коду. Большинство приложений хранят в базе данных только свое текущее состояние. Значительный объем полезной информации теряется — вы не знаете, каким образом приложение достигло этого состояния.

Концепция порождения событий — это попытка решить эту проблему путем сохранения всех событий, которые происходят в вашем приложении. Состояние приложения формируется в результате прослушивания этих событий.

Для облегчения понимания рассмотрим небольшой пример из жизни. Представьте, что вы банк. У ваших клиентов имеются счета. Хранить только информацию об остатке на счете недостаточно. Необходимо также запоминать все транзакции. При использовании шаблона порождения событий остаток на счете будет не просто отдельным полем в базе данных, а значением, рассчитанным на основании сохраненной информации о транзакциях. Это только одно из множества преимуществ, которое предлагает концепция порождения событий.

Данный пакет призван познакомить пользователей фреймворка Laravel с концепцией порождения событий и облегчить ее использование на практике.

Похоже, что Event Projector помогает упростить работу с шаблоном порождения событий. Документация также помогла мне разобраться, каким образом следует использовать этот подход в рамках концепций фреймворка Laravel, с которым я уже знаком.

Основы порождения событий в Laravel

Чтобы разобраться, как работает порождение событий в пакете Event Projector и какие компоненты задействованы в этом процессе, вам следует ознакомиться с разделом Создание первого проектора.

На высоком уровне (простите, если не совсем корректен в своей оценке, поскольку порождение событий — это новая для меня концепция) порождение событий с помощью Event Projector включает в себя:

  • Модели Eloquent
  • События, реализующие интерфейс ShouldBeStored
  • Классы проектора

Пример класса модели со статическим методом createWithAttributes, приводимый в документации:

namespace App;  use App\Events\AccountCreated; use App\Events\AccountDeleted; use App\Events\MoneyAdded; use App\Events\MoneySubtracted; use Illuminate\Database\Eloquent\Model; use Ramsey\Uuid\Uuid;  class Account extends Model {     protected $guarded = [];      protected $casts = [         'broke_mail_send' => 'bool',     ];      public static function createWithAttributes(array $attributes): Account     {         /*          * Давайте сгенерируем универсальный уникальный идентификатор (uuid).           */         $attributes['uuid'] = (string) Uuid::uuid4();          /*          * Счет будет создан в этом событии на основе сгенерированного uuid.          */         event(new AccountCreated($attributes));          /*          * Обращение к созданному счету будет происходить по этому uuid.          */         return static::uuid($attributes['uuid']);     }      public function addMoney(int $amount)     {         event(new MoneyAdded($this->uuid, $amount));     }      public function subtractMoney(int $amount)     {         event(new MoneySubtracted($this->uuid, $amount));     }      public function delete()     {         event(new AccountDeleted($this->uuid));     }      /*      * Вспомогательный метод для быстрого обращения к счету по uuid.      */     public static function uuid(string $uuid): ?Account     {         return static::where('uuid', $uuid)->first();     } }

Здесь необходимо обратить внимание на события AccountCreated, MoneyAdded, MoneySubtracted и AccountDeleted, инициируемые соответственно для открытия счета, добавления денег на счет, снятия денег со счета и удаления счета.

Ниже приведен пример события MoneyAdded (добавление денег на счет). Интерфейс ShouldBeStored указывает пакету Event Projector, что это событие должно быть сохранено:

namespace App\Events;  use Spatie\EventProjector\ShouldBeStored;  class MoneyAdded implements ShouldBeStored {     /** @var string */     public $accountUuid;      /** @var int */     public $amount;      public function __construct(string $accountUuid, int $amount)     {         $this->accountUuid = $accountUuid;          $this->amount = $amount;     } }

И наконец, частичный пример обработчика событий внутри класса проектора для пополнения средств на счете (мой любимый тип банковского события):

public function onMoneyAdded(MoneyAdded $event) {     $account = Account::uuid($event->accountUuid);      $account->balance += $event->amount;      $account->save(); }

Чтобы связать все это воедино, рассмотрим пример из документации по созданию нового счета и добавлению средств:

Account::createWithAttributes(['name' => 'Luke']); Account::createWithAttributes(['name' => 'Leia']);  $account = Account::where(['name' => 'Luke'])->first(); $anotherAccount = Account::where(['name' => 'Leia'])->first();  $account->addMoney(1000); $anotherAccount->addMoney(500); $account->subtractMoney(50);

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

Мое внимание также привлекло следующее преимущество порождения событий с использованием этого пакета, указанное в документации:

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

Сама идея создания новых проекторов по завершении событий представляется весьма перспективной. Получается, что вам не обязательно получать все «правильные» данные заранее, а можно приходить к результату итеративно.

Что же касается производительности, то согласно документации обращение к проекторам «происходит очень быстро».

Хотите узнать больше?

Для начала я рекомендую ознакомиться с документацией по Event Projector и убедиться, что у вас установлена свежая версия PHP 7.2. Команда Spatie также выложила пример приложения на Laravel, демонстрирующий возможности пакета Event Projector.

Вы можете загрузить код, отметить репозиторий звездочкой, а также внести свой вклад в развитие проекта Event Projector на GitHub. На момент написания статьи над проектом Event Projector работают уже 15 участников, усилиями которых и стал возможен стабильный релиз 1.0. Отличная работа, Spatie! Уверен, что Event Projector станет еще одним великолепным пакетом, который будет по достоинству оценен сообществом Laravel.


ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/460683/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *