Pull to refresh
Comments 31
CRUD-подобные репозитории изначально задумывались или это дань существующим API в ядре Magento 2? После предыдущих статей неожиданно было их увидеть
Хороший вопрос. Тут на самом деле ряд факторов сыграли.
Начиная с того, что у нас мало что изменилось в концепции UI админ части по сравнению с Magento 1. Так как проектирование CQRS системы не может не затронуть UI. То, что называется Task Based UI, тут в презентации я показывал отличия от CRUD UI.
В Magento привычно иметь UI Grid для редактирования сущностей в админке.
Например, так выглядит грид продуктов

В нем администратор может поменять значения для всех атрибутов сущности. Этот подход более характерен для CRUD UI, нежели для командного UI (где под изменения каждого из атрибутов пришлось бы создавать отдельную команду).
И для многих сущностей такой грид достаточно удобен. Так как мы делаем фреймворк под разные потребности торговцев. У кого-то большой бизнес, и он хранит все товары в ERP или PIM системе и использует эту внешнюю систему как «Source of Truth», а Magento использует как витрину, которую легко кастомизировать для оформления заказов (эта категория пользователей не модифицирует данные в админке Magento, так как изменения делаются во внешней системе и в Magento они попадают при синхронизации). Для такого сценария использование Task Based UI более естественно.

Кто-то наоборот заполняет данные используясь admin-панелью Magento и там же эти данные редактирует. Для последних CRUD подобный интерфейс достаточно удобный.
Так как мы фреймворк, и не знает конкретных бизнес процессов продавца, который будет пользоваться Magento — мы должны покрыть все сценарии использования.
В Таких реалиях CRUD подобные репозитории, которые предоставляют возможности менеджмента сущностей весьма удобны.

С учетом вышеописанного Repository Pattern подходит нам как нельзя хорошо.
A system with a complex domain model often benefits from a layer that isolates domain objects from details of the database access code. This becomes more important when there are a large number of domain classes or heavy querying. In these cases particularly, adding this layer helps minimize duplicate query logic.
Тут стоит уточнить, что мы вводим такие Repository не для каждой сущности в системе, а только для тех сущностей для которых планируем предоставлять UI Grid для редактирования в админ-панеле.
Т.е. в случае MSI проекта мы имеем гриды для Source, SourceItem, Stock — для этих сущностей мы предосталяем репозитории.
Например, для объектов резервирования Reservation мы не предоставляем репозиторий.

Основываясь на всем вышесказанном, я бы точно не воспринимал использование Repository как что-то плохое. Respository это один из тактических паттернов Domain Driven Design.
С учетом вышеописанного Repository Pattern подходит нам как нельзя хорошо.

ИМХО репозиторий как паттерн для изоляции превосходен.
Вот только что у вас доступ к базе данных "спрятан" не в репозитории а в командах


class StockRepository implements StockRepositoryInterface {
...
public function deleteById($stockId)
    {
        $this->commandDeleteById->execute($stockId);
    }
В теле статьи я описал как в Magento 2 выглядят репозитории и почему.
А доступ к базе у нас «спрятан» не в командах, которые должны быть агностичны к Data Storage механизму, а в ресурс моделях.
Пример из статьи:
Команда для MultipleSave опареции SourceItem — Magento\Inventory\Model\SourceItemSave.php

Ресурс модель для этой команды — Magento\Inventory\Model\ResourceModel\SourceItem\SaveMultiple.php

Repository у нас это Фасад, чтобы клиент мог иметь одну зависимость на репозитори для выполнения базовых операций с сущностью. Например, достать сущность по ID или поиском, изменить, сохранить (если он делает CRUD операции над сущностью, например в гриде). По большому счету Repository существует только для удобства и простоты кода клиента, так как мы могли бы просто давать набор команд.
Промахнулся
Это у меня наверное травма, что, при виде репозитория такого формата в Magento2, перед глазами появляется новое системное поле в бд(is_notification_sended), которое добавляется в DTO интерфейс и уже доступно к сохранению через репозиторий (или может сетаться и сохраняться эксплуатируя имплементацию дто через AbstractModel с магическими сеттерами).

Для примера SourceInterface::setEnabled, если возникнет требование выполнения каких-либо бизнес-процессов по отключению/включению источника, то как в данном модуле это будет разруливаться? Наверное на уровне репозитория сравнение current/new field value.
Понятно, что единые подходы на всех слоях системы способствуют скорости и приятностям в разработке, но по-хорошему тот же круд-интерфейс в сторонней системе может быть заменен на CQ и уже ему на уровне операционного слоя предется мапить все команды на круд-апишки.

По вопросу гридов, в Magento2 насколько я видел дата провайдеры гридов не работают с репозиториями, они, пропуская слой репозитория, работают напрямую с коллекциями (сортинг, фильтры, массэкшены). Не тот ли это SmartUI, который Эванс называет антипаттерном для применения в контексте DDD?

Thx
Спасибо за статью и пояснения. Для меня, который все время видел только результат каких-то решений (релизы Magento), интересно понять что стоит за конкретными решениями и направление развития системы. Продолжайте, получается круто.
Для примера SourceInterface::setEnabled, если возникнет требование выполнения каких-либо бизнес-процессов по отключению/включению источника, то как в данном модуле это будет разруливаться?

Так как на текущих мокапах UI, который были оговорен с продакт оунером проекта — у нас нет отдельного бизнес процесса — отключения и включения источника, а есть только Grid, в котором можно редактировать все поля. То пока внедрять отдельные сервисы-команды на включение и выключение источника не будем. А будем обрабатывать изменение состояния, сохраняя сущность Source с измененным состоянием через сервис SourceRepositoryInterface.

Но стороннему разработчику ничего не мешает создать отдельные интерфейсы команд:
interface DisableSource
{
    /**
     * Disable given Source
     *
     * @param int $sourceId
     * @return void
     */
    public function execute($sourceId);
}

interface EnableSource
{
    /**
     * Enable given Source
     *
     * @param int $sourceId
     * @return void
     */
    public function execute($sourceId);
} 

И в реализации этих сервисов-команд можно использовать SourceRepository как-то так:
        /** SourceInterface $source */
        $source = $this->sourceRepository->get($sourceId);
        $source->setEnabled(TRUE);
        $this->sourceRepository->save($source);

Таким образом Plugin-ы которые висят на SourceRepository и запускаются при изменениях будут продолжать отрабатывать, т.е. введя такие сервисы сторонний разработчик, который хочет использовать командные сервисы — не поломает текущие расширения над CRUD API.
По вопросу гридов, в Magento2 насколько я видел дата провайдеры гридов не работают с репозиториями, они, пропуская слой репозитория, работают напрямую с коллекциями (сортинг, фильтры, массэкшены).
Это не правило. Для DataProviders существует Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface

В котором ничего не сказано про коллекции, и то, что вы должны их использовать.
Вот, например, SourceDataProvider и StockDataProvider которые используются для гридов Source и Stock соответсвенно.

    public function getSearchResult()
    {
        $searchCriteria = $this->getSearchCriteria();
        $result = $this->stockRepository->getList($searchCriteria);
        $searchResult = $this->searchResultFactory->create(
            $result->getItems(),
            $result->getTotalCount(),
            $searchCriteria,
            StockInterface::STOCK_ID
        );
        return $searchResult;
    }

Поэтому нет — это нельзя назвать Smart UI, так как разделение на слои c возможностью подменять модель данных присутствует.
а что именно вы ожидали увидеть чего не нашли в описании Service Layer?

Еще раз извиняюсь, но все что я увидел это описание репозиториев и что-то типа command handler.
Если Вас не затруднит, можете ткнуть пальцем, где конкретно в вашем коде "виден" Event Sourcing.

Можете не извиняться, но как на уровне Service Layer должен выглядеть Event Sourcing, чтобы он был заметен?

ну, хотя бы наличием самих Events? или если не наличием, то хотя бы dispatch

Ну давайте начнем с того, что в статье описан Service Layer, который представляет набор доменных сервисов (терминами DDD), соответсвенно название этих сервисов должны представлять бизнес процессы в домене на языке Ubiquitous Language и отображать как пользователи пользуются системой.
Поэтому на этом уровне dispatch у нас быть не может.

По поводу Events. В описанной системе ивентами можно считать объекты резервирования. В данном случае Reservations — это не команды (которые представляли бы собой императивные наклонения — как разместить резервацию), а сущности, которые отражают, что какое-то событие произошло в прошлом (резервирование произошло).
API по размещениею Reservations не мапятся напрямую на Web API (REST/SOAP), т.е. их «нельзя» вызвать из кода бизнес логики приложения напрямую. Они представляют собой SPI, который вызовется как side-effect при обработке какого-то бизнес процесса, например, размещения заказа.
Т.е. размещая заказ, ваш код не должен явно размещать резервации по этому заказу.

Ну и также эти объекты резервации «накатываются» на Rolling Snapshot для того, чтобы получить актуальное в текущий момент времени количество товаров в стоке доступное для продажи.

В статье сознательно не описывается метод хранения резерваций (Event Store если позволите), так как он может быть реализован по-разному. Этому может быть таблица базы данных, очередь Redis, Cassandra и т.д.
представляет набор доменных сервисов (терминами DDD)
Поэтому на этом уровне dispatch у нас быть не может.

Почему? А что с Domain Events? Совсем никак нельзя?


ивентами можно считать объекты резервирования

Так ивент или все таки нет?


/**
 * The entity responsible for reservations, ...
 *
 * @api
 */
interface ReservationInterface

Судя по декларации интерфэйса таки нет.


Вы случайно не знакомы с этой книжкой? https://leanpub.com/ddd-in-php

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

Magento — это eCommerce framework, которые может быть использован продавцами (merchant) разного размера и с разным оборотом. На базе Magento разворачивается конкретный магазин, на который будут приходить клиенты и осуществлять покупки (либо это может быть вообщем Drupal fron-end, а на Magento заказы будут приходить как Web API вызовы, headless Magento). Причем, как правило, каждый из таких магазинов отдельно кастомизируется (добавлятеся бизнес логика, меняется дефолтная и т.д.) чтобы соответствовать бизнес процессам конкртентого мерчанта.

Мы делаем фреймворковое решение, а не конкретную кастомизацию под конкретный бизнесс процесс, выстроенный у какого-то продавца.
Цитируя Грега Янга — не нужно строить Event Sourcing фреймворк (general purpose), так как вы его забросите вскоре, так как он будет ориентирован на бизнес процессы определенного бизнеса. Мы не делаем ES фреймворк.
И именование ES поэтому для нас не всегда уместны.

Мы пользуемся именами из Ubiquitous Language самого домена (Inventory).
Поэтому отвечая на ваш вопрос выше, объекты резерваций — в терминах Event Sourcing — это события (Events). И я выше описал почему.

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

По поводу книги — нет, я эту я не читал.
Я читал Эванса и Implementing Domain Driven Design, Vaughn Vernon
из классики, ну и CQRS and Event Sourcing Грега Янга
а также посещал воркшопы по DDD под руководством Vaughn Vernon
для PHP DDD как-то по другому выглядит?

Должен признаться, что да, опыта работы с Magento у меня нет. Посмотрел — испугался — еле-еле отошел. Поэтому решили сделать свой eCommerce-Framework. В отличии от Magento у меня (лично) на базовый функционал ушло 3-4 месяца (сейчас точно не помню). На основе этого уже фрэймверка поднимали кастомные реализации по заказу клиентов. К сожалению не Open Source.


В одной реализации был конкретный заказ: Мы хотим мониторить все изменения в системе (кто, что, когда, сколько итд). Таким образом фрэймворк с одной стороны работает классически (Энтити сохраняются в конечном состоянии в базе данных). Но параллельно прокидываются ивенты (Application Events, Domain Events итд). Так что по сути двойное хранение тех же самых данных, только форма разная.


Цитируя Грега Янга — не нужно строить Event Sourcing фреймворк

а никто и не говорит или просит, чтобы вы его делали. Но вот наименования как раз то и нужны, чтобы хотя бы разрабы знали и видели, что у них в коде.


Мы пользуемся именами из Ubiquitous Language самого домена

хм… очень хорошо. Но язык не отражающий бизнес-действительность ИМХО больше мешает. Да я понимаю и (на болезненном опыте) знаю, что бизнес-язык отличается иногда от реальности, но что мешает нам называть вещи своими именами (зарезервитовать на складе, вернуть на склад итд)


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

тоже странно. если уж кастомизация, то будте добры делайте сами (не Вы, а кастомайзер). а ваша имплементация как-никак базовый функционал и должна более или менее четко определять, что ваш фрэймворк делает и какие процессы и соответствующие сущности в нем уже есть.


PHP DDD как-то по другому выглядит

нет :) но там доступно и на примерах объясняют и показывают суть, ну хотя бы того самого Event Sourcing

Посмотрел — испугался — еле-еле отошел. Поэтому решили сделать свой eCommerce-Framework.
Ну в это и разница, в том, что Magento — самый популярный в мире eCommerce framework, которым пользуется более 200 000 мерчантов по всему миру. А вы написали решения под себя, заточенное под реализацию одного бизнес процесса. И вы не переживаете ни про расширяемость, ни про настраиваемость, поэтому и создаете объекты через new, и использование билдера выглядит для вас странно. Потому что количество людей, которые будут пользоваться вашей системой — ограничено и известно заранее. В этом и разница между фреймворком и проприентарной системой.
Но вот наименования как раз то и нужны, чтобы хотя бы разрабы знали и видели, что у них в коде.
Оно (наименование) есть, что легко заметить даже по этой статье, вы просто очень хотите, чтобы оно соответствовало тому, что вы прочитали в книге «Domain-Driven Design in PHP».
Да я понимаю и (на болезненном опыте) знаю, что бизнес-язык отличается иногда от реальности, но что мешает нам называть вещи своими именами
Эта фраза противоречит понятию Ubiquitous Language
что ваш фрэймворк делает и какие процессы и соответствующие сущности в нем уже есть.

За это и отвечает слой сервис контрактов, интерфейсы которого здесь представлены.
А то как будут они вызваться и какой будет транспорт. Будет ли использоваться классическая событийная модель, таблица базы данных, транзакционная модель с предпочтением data consistency, или BASE с Eventual Consistency — это уже обусловлено бизнес процессами конкретного мерчанта.
Поэтому и dispatch мы не показываем наружу. Так как диспатч — это часть реализации характерная для определенного бизнес процесса.
Magento — самый популярный в мире eCommerce framework

ну, скажем, начиналось все когда? а на безрыбье, как известно...


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

Извиняюсь за грубость, но замечаю уже не в первый раз, что Вы читать не умеете. Вернее читать — да, но понимание прочитанного явно хромает.
Мы сначала сделали фрэймворк. Делался он как раз с учетом расширяемости и "настраиваемости". И уж потом использовали его при реализации кастомных проектов. Агентура, в которой я работал, "продает" кастомные eCommerce-решения, а не сам фрэймворк. А вот для проектов попроще использовались такие фрэймворки как Magento, потому как найти разраба, умеющего поднять шоп на Magento намного проще.


Эта фраза противоречит понятию Ubiquitous Language

это почему? На сколько я знаю, этот язык как раз и должен решить проблемы понимания. Если разрабы не понимают, что от них хочет бизнес, то как они должны решить задачу правильно. Ubiquitous Languаге должен всем быть понятен. Да, понятно, что придется идти на уступки, но всем, а не односторонне.


Так как диспатч — это часть реализации характерная для определенного бизнес процесса.

Совсем не факт. Тем более если рассматривать dispatch в контексте Event Driven как механизм обеспечивающий внутрисистемный decoupling

public function getStatus();
public function getReservationId();
public function getStockId();
Странное именование методов. почему getStatus? Хотя метод судя по всему возвращает int
метод getStatus возвращает одну из предопределенных констант, статуса в котором может пребывать резервация. Под капотом этот статус хранится как Int.
Если вопрос почему мы не использовали Value-Object для хранения статуса, то мы не заменяем примитивные тимы на типы-обертки по двум причинам:
1. performance, чтобы не создавать дополнительное большое количество объектов.
2. это бы перегрузило и усложнило код бизнес логики (клиента), так как для создания статуса и других атрибутов (которых может быть много) код бизнес логики должен был бы использовать фабрики.
метод getStatus возвращает одну из предопределенных констант, статуса в котором может пребывать резервация. Под капотом этот статус хранится как Int.

Вот как раз один из показателей, что у вас не Event Sourcing. Или по крайней мере не совсем

Вы вероятно пропусти описание механизма работы объектов резервирования. Само состояние в пределах объекта не изменяется. Reservation является immutable сущностью, при этом у него есть статус, который говорит о том в каких условиях (в рамках какого бизнес процесса) создавалась резервация.
По большому счёту это точка расширения для кастомизаторов. У нас вполне могло и не быть статуса, нам хватает набора (stockId, sku, qty) для подсчета актуальности стока.

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

Вам не кажется, что вы сами себе противоречите?
Если у вас резервация immutable сущность, то на каком основании у нее есть статус? Если immutable, то и статус как атрибут сущности не меняется, потому как сущность immutable.
А если таки статус может меняться, то это либо не immutable сущность, либо статус привязан не к той сущности.
В любом случае, следуя вашей реализации, резервация у вас все таки изменяема, хоть и Append Only, как вы пишите.


Добавление чего-то к чему-то изменяет то к чему добавили по умолчанию. Как бы вы это не крутили.


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


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


Если у вас меняется состояние резервации, что приводит к изменению ее статуса, то у вас резервация переходит из одного состояния в другое (было 1, стало 5).

Давайте по порядку. Потому как вы начали спорить с самим собой.
Где вы прочитали, про то, что какой-то из атрибутов сущности резервирования (в частности статус) изменяется?
Из описания статьи:
размещая заказ на 30 единиц товара создаем резервирование:
ReservationID — 1, StockId — 1, SKU — SKU-1, Qty — (-30), Status — CREATED
Обработав этот заказ — создаем другое резервирование
ReservationID — 2, StockId — 1, SKU — SKU-1, Qty — (+30), Status — CANCELLED
То есть, в тот момент, когда пользователь вашей системы хочет узнать текущее состояние резервации

На самом деле пользователь системы не хочет узнавать состояние резервации. Пользователь системы хочет получить актуальное количество товаров доступное для продажи. Для этого существует отдельный сервис. Логика этого сервиса следующая:
получить кол-во товаров в StockItem (агрегации) — отнять кол-во резерваций

Последнее вычисляется приблизительно так:
select 
   SUM(r.qty) as total_reservation_qty
from 
   Reservations as r
where 
  stockId = {%id%} and sku  = {%sku%}

Видите, нет привязки к статусу.
Поэтому я причисляю Reservation к SPI, т.е. напрямую ваш код не работает с ними.
Пользователь системы не должен хотеть работать с объектами резервирования.
он видит конечный автомат. Так как он не знает, что вы собираете состояние резервации. По большому счету ему на это забить, он хочет знать актуальное состояние его резервации или заказа.
Вы даете больше ответственности на сущность резервации чем это делаем мы.
Пользователю интересно — состояние заказа.
Сущность резервации не отвечает за статус заказа и не изменяется вместе с ним. Объект резервации создается для подсчета корректности стока, который используется для вычисления колличества продуктов в стоке.

Да, mea culpa, теперь понятнее. Спасибо.


Но не завидую вашим коллегам. Читабельностю ваш код явно не страдает

ну вот, вы нашли в моей статье и коде то, чего там не было, а в итоге обвинили код и его читабельность :)

Да не то чтобы нашел. Скорее всего наоборот.
Все таки если Reservation это Event, то


  1. $reservated = new ReservateFromStock($stockId, $amount)

    1.1 зачем какой-то builder? конструктор уже не в моде?
    1.2 если уж вы сами говорите, что ``Reservation``` в данном случае и есть те ивенты, то к чему следующий прямой вызов?


    $reservationAppend->execute([$newReservation]);


тем более Вы сами говорите, что


резерв может быть привязан и к другим бизнес операциям

dispatch(new Reservation($stockId, $amount)) и дальше по тексту.


  1. что делает второй тип резервации — не понятно. тем более почему не меняется Quantity. но не в этом суть. тут, конечно вам видней, ваш бизнес.
    1.1
    $reset = new ResetReservated($reservated)
  2. у вас STATUS_ORDER_... запилен в Reservation. это как? тем более, что Reservation не знает заказа

это только пара примеров. на пока что хватит.
но, о что вы называете Event Sourcing по вашей имплементации не более чем Event Driven. В Event Sourcing была бы сборка состояния сущности из соответствующих ивентов.
В вашем примере просто нет (видимого невооруженным глазом) Event Sourcing

Ну давайте по порядку:
Такой вызов нам не подходит:
$reservated = new ReservateFromStock($stockId, $amount)
так как, во-первых, у нас создается отдельная резервация на каждый SKU в рамках стока, а не просто на сток.
зачем какой-то builder? конструктор уже не в моде?
Не конструкторы не в моде, а создание объектов через new не в моде, если вы хотите писать расширяемый код. Для этого существуют Creational Design patterns.
Зачем тогда вводить ReservationInterface если вы создаете объект резервацию через оператор new, и подменить на другую реализацию все равно не сможете.
в данном случае и есть те ивенты, то к чему следующий прямой вызов? $reservationAppend->execute([$newReservation]);
Вопрос не понятен. Этот вызов это SPI, который происходит при какой-то бизнес операции, при которой нужно создать резервации для продуктов (например, размещение заказа).
dispatch(new Reservation($stockId, $amount)) и дальше по тексту.

Насколько я понимаю, тут вы просто рекомендуете использовать именование dispatch. Или использование глобальной функции тоже часть рекомендации?
Класс-команда с методом execute для меня выглядет приблизительно также. Более того, такой стиль сейчас принят на проекте, поэтому для единообразия мы будем придерживаться его.
Использование глобальных или статических ф-ий нежелательно для кастомизации и расширения кода, поэтому их мы использовать не будем.
что делает второй тип резервации — не понятно. тем более почему не меняется Quantity.
А почему вы считаете, что количество для резервации должно меняться?
у вас STATUS_ORDER_… запилен в Reservation. это как?
Здесь прошу прощения, STATUS_ORDER_* быть не должно, копировал код для статьи из IDE, где они были.
На момент написания статьи идея была иметь резервации в OPEN/CLOSED состояниях.
Хотя сейчас видим, что состояние нам не особо нужно и больше требовалось в целях возможного дебага, поэтому решили заменить его Metadata.
В Event Sourcing была бы сборка состояния сущности из соответствующих ивентов. В вашем примере просто нет (видимого невооруженным глазом) Event Sourcing
Давайте я помогу и «вооружу» ваш взгляд.

У нас есть такой сервис
/**
 * Service which returns Quantity of products available to be sold by Product SKU and Stock Id
 *
 * @api
 */
interface GetProductQuantityInStock
{
    /**
     * Get Product Quantity for given SKU in a given Stock
     *
     * @param string $sku
     * @param int $stockId
     * @return float
     */
    public function execute($sku, $stockId);
}

Задача которого посчитать актуальное количество продуктов, которые мы можем продать.
У нас есть StockItem, который хранит Qty как поле. Но это Qty устаревает, так как оно не обновляется моментально с каждым заказом. Оно обновляется во время реиндексации, после того когда мы выбрали склады из которых сделаем доставку, и соответственно обновили SourceItem.
Можно считать, что индекс StockItem — это проекция актуальная на какой-то период времени.
Но чтобы получить точное число продуктов в стоке та текущий момент времени — мы от этого числа отнимаем резервации, полученные условно-говоря таким запросом:
select 
   SUM(r.qty) as total_reservation_qty
from 
   Reservations as r
where 
  stockId = {%id%} and sku  = {%sku%}

* и да резервации не обязательно могут или должны лежать в таблице.
Соответсвенно у нас есть Snapshot и ивенты — чем вам не Event Sourcing?
так как, во-первых, у нас создается отдельная резервация на каждый SKU в рамках стока, а не просто на сток.

извиняюсь, профакапил один параметр


Зачем тогда вводить ReservationInterface если вы создаете объект резервацию через оператор new, и подменить на другую реализацию все равно не сможете.

Да, согласен, шаблоны существуют. Но в данном случае:


  1. Interface нужен сугубо вашему фрэймворку, чтобы ядро могло работать
  2. Ваша реализация ReservationInterface имеет определенный Вами смысл в рамках Вашего фрэймворка.
  3. Подмена реализации оправдывается лишь в рамках процесса. То есть в одном контексте одна конкретная, а в другом уже что-то другое. я создаю объект CustomReservation в контексте CustomReservationProcess
  4. Ваш ReservationBuilder не выдаст ничего кроме запиленной в него реализации Reservation. Где тут заменяемость? Вам придется заменять сам Builder который будет билдить кастомную резервацию.
  5. При вашем подходе придется мэйнтэйнить два класса чтобы получить результат, хотя Use Case, породивший надобность новой реализации, требует всего лишь одну новую CustomReservation

Вопрос не понятен

$reservationAppend->execute([$newReservation]);

Насколько я понимаю, тут вы просто рекомендуете использовать именование dispatch.

Нет, я не рекомендую само наименование. Если хотите, я рекомендую decoupling более соотвествующий Вашим же бизнес-требованиям.
Вы работаете напрямую с $reservationAppend. То есть, вы конкретно знаете кто и как отвечает за резервирование. На эту зависимость указывает само наименование переменной. В случае с dispatch, Вы не знаете кто и тем более как этот ивент отработает. Может там еще куча слушателей? А может быть надобность в этой операции отпала вообще.


А почему вы считаете, что количество для резервации должно меняться?

Вы не поняли вопроса.


точное число (Quantity) товара доступное для продажи…

Обработав этот заказ — создаем другое резервирование...

Не понятно следующее: Почему после обработки заказа резервация, как вы говорите, "гасится"? Или это такой интересный Business Case?


У нас есть StockItem, который хранит Qty как поле

Соответсвенно у нас есть Snapshot и ивенты — чем вам не Event Sourcing?

Теперь (частично) увидел. Спасибо. И это число вы сохраняете в StockItem, называя его Snapshot?


Но вы любите ссылаться на Грега Янга? Вот что он говорит насчет Snapshot.


https://youtu.be/8JKjvY4etTY?t=27m24s


Сравните с вашей реализацией. replaying events у вас сводится до примитивного агрегирующего SQL-запроса?


Quantity StockItem-a обновляется с задержкой (latency) вызванной природой Event Sourcing

Вы путаете Event Driven с Event Sourcing. Как раз при Event Sourcing задержки обновления никакой нет, потому что обновления как такового нет.


https://youtu.be/8JKjvY4etTY?t=9m18s


Приятного времени дня Вам :) Жду дальнейших публикаций.

Вы знаете, после общения с вами, решил немного изменить интерфейс резервации. Так как по факту статусами мы особо не пользовались. Это был больше механизм отладки для поиска потерянных резерваций. Но если он так сильно наталкивает на мысли о Finite State Machine, то лучше убрать его вообще и добавить поле Metadata.
github.com/magento-engcom/magento2/wiki/Reservations

/**
 * The entity responsible for reservations, created to keep inventory amount (product quantity) up-to-date.
 * It is created to have a state between order creation and inventory deduction (deduction of specific SourceItems)
 *
 * @api
 */
interface ReservationInterface
{
    /**
     * Constants for keys of data array. Identical to the name of the getter in snake case
     */
    const RESERVATION_ID = 'reservation_id';
    const STOCK_ID = 'stock_id';
    const SKU = 'sku';
    const QUANTITY = 'quantity';
    const METADATA = 'metadata';

    /**
     * Get Reservation id
     *
     * @return int|null
     */
    public function getReservationId();

    /**
     * Get stock id
     *
     * @param int $stockId
     * @return void
     */
    public function getStockId($stockId);

    /**
     * Get Product SKU
     *
     * @return string
     */
    public function getSku();

    /**
     * Get Product Qty
     *
     * @return float
     */
    public function getQuantity();

    /**
     * Get Reservation Metadata
     *
     * @return string|null
     */
    public function getMetadata();
}

пожалуй так даже лучше, так как следуя separation of concerns наличие статуса, который как-то связан с размещения заказа или обработкой — можно считать leaky abstraction
Only those users with full accounts are able to leave comments. Log in, please.