Pull to refresh
148
0
Игорь Миняйло @maghamed

Lead Architect, Magento an Adobe

Send message
Для примера 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.
Можете не извиняться, но как на уровне Service Layer должен выглядеть Event Sourcing, чтобы он был заметен?
а что именно вы ожидали увидеть чего не нашли в описании Service Layer?
Хороший вопрос. Тут на самом деле ряд факторов сыграли.
Начиная с того, что у нас мало что изменилось в концепции 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.
А можно ссылку на пруф?
Не помню в какой именно из презентаций слышал именно эту формулировку.
Но вот достаточно близкая по смыслу тоже от него
https://www.youtube.com/watch?v=LDW0QWie21s&feature=youtu.be&t=7m36s
Но CRUD это и есть пример имплементации CQS.
Так говорить некорректно.
CRUD может следовать, а может и не следовать CQS.

Сам принцип CQS очень прост, каждый метод класса может быть Query или Command (нельзя одновременно совмещать и то и другое):
  • Queries: возвращают результат и не изменяют наблюдаемое состояние системы, т.е. не влекут сайд эффектов, т.е. безопасны и идемпотентны по своей сути.
  • Commands: изменяют состояние системы, но при этом не возвращают данные

В вашей реализации CRUD могут быть методы, которые на риде изменяют состояние. Например, операция чтения из стека — с одной стороны операция чтения, где данные вам возвращаются, с другой — она изменяет состояние наблюдаемого объекта в системе.
Т.е. эта операция не соответствует CQS.
Изменять состояние на риде — вообще одно из частых явлений во многих CRUD реализациях.

Потому как, что нам мешает сохранять Domain Events не используя CRQS а тот же CRUD?
По большому счету — ничего.
Но использовать одновременно DDD и CRUD вы теряете большинство преимуществ, которые дает вам DDD

Так же попробуем обозначиться с тем, что же на самом деле CRUD и чем он отличается от CQRS.
CRUD и CQRS всего лишь разный подход к организации и уровень инкапсуляции кода. Буква S из SOLID.

CRUD — разделение на уровне методов
CQRS — разделение на уровне классов
Нет, я на этом этапе с вами не соглашусь.
То о чем вы говорите это не CRUD, это
  • Command Query Separation (CQS) термин введенный Бертраном Мейером, который как раз заключается в разделении ответственностей, а соответственно и кода на уровне методов.
  • Command Query Responsibility Segregation (CQRS)

Если Вам будет интересно у меня есть отдельный доклад про CQS/CQRS — https://www.youtube.com/watch?v=V24L4a9FFps
CRUD как базовый концепт не говорит вам о том как организовывать код. CRUD это такое себе связующее звено на уровне кода, связывающее команды HTTP (GET, POST, PUT, DELETE) с SQL запросами (INSERT, SELECT, UPDATE, DELETE).

Дальше вы вступаете в спор с самим собой. Тезисно:
Зачем вам вообще тогда HTTP-глаголы?
пример показывал как меняются RESTful API, при переходе с CRUD -> CQRS
HTTP в этом случае это всего лишь транспорт.
Если вы даете RESTful API, то вы вынуждены использовать глаголы HTTP.

оба примера API:
POST /product/{id}/order
и
POST /place-order

соответствуют требованиям CQRS, и их можно назвать command API. Все зависит от того, что в конкретном бизнесе представляет собой операция Order и как лучше ее выразить.
Я не вижу разницы между двумя этими примерами с точки зрения демострации Command API колов. А если не вижу, то почему я должен приводить ваш пример?
А вот теперь вернемся к Event Sourcing. При чем тут CQRS вообще? Что это вообще?

CQRS здесь при том, что вы не сможете построить Event Sourcing систему без использования CQRS. И это даже не мои слова, это слова Грега Янга (парня, который ввел термин CQRS).
Грег вообще считает, что CQRS без EventSourcing особо не нужен, и воспринимает его как первый шаг к EventSouring. О чем он постоянно говорит на своих докладах.

ну как-то так.
То как вы описали,
preference for=«Magento\Framework\Logger\Monolog» type=«Coolryan\PreferenceExample\Model\Log»

Это не рекомендованый подход. В данном случае вы будете использовать механизм Duck Typing.
Который не безопасный и не объектно ориентированный по своей сути.
И раз пошла такая петрушка, куда я могу оформить баг, при котором админка мадженты уходит в цыклический редирект?

Вы можете завести GitHub issue
Описав проблему и предоставив шаги, чтобы ее воспроизвести и кто-то из команды Magento или из сообщества вам поможет в решении этой проблемы.
Немного смешно звучит учитывая что при создании модуля мы постоянно делаем рутинные действия, например создание модели, с ресурс моделью и коллекцией.
Не вижу здесь ничего смешного. Это называется разделение ответственностей в многослойной архитектуре. Или вы хотите, чтобы модель попрежнему себя сохраняла? Вы же сами выше писали про Single Responsibility.

Для большей гибкости и кастомизации, которая предоставит возможность подменять минимальное кол-во логики для стороннего разработчика (а не переписывать весь модуль) мы и делаем это разделение.
Например, вот хороший пример написания новой логики в Magento 2 — новая реализация Inventory.
Вы видите здесь примеры boilerplate кода?

А это в каком случае? В случае использования Context? В случаях в preference или в случаях virtual types?
Это во всех случаях.
Например, есть класс А, который имеет внешнюю зависимость на XInterface, у XInterface есть две реализации X1 и Х2.
Есть базовый preference XInerface -> X2
и есть настройка Type для класса А (XInerface -> X1)
Когда существует класс B, который наследуется от А, то ему прийдет в зависимость для XInerface -> реализация X1

Опять же это одно из проявлений Liskov Substitution

Ну для начала parent в Symfony это не более чем механизм который позволяет избавится от дублирующего кода,
Еще раз — сравните сколько вы пишите конфигурации в Magento и сколько бы написали в Symfony для большого проекта. В Magento вы пишите меньше, потому что декларируете только то, что вам действительно нужно сконфигурировать.

Ну и потом, звучит немного странно на фоне того, что при создании модуля мы все так же наследуем базовые классы(раз, два, три, четыре, пять, шесть),
Не странно, то что вы указали — это примеры legacy кода, который мы пока не меняем, чтобы не нарушить обратную совместимость в новых релизах и не поломать текущие инсталяции.
Базовые абстрактные класса такие как AbstractBlock, AbstractModel, AbstractController это яркий пример layer super type
Например, вот одна из задача из нашего беклога, которой мы делимся с сообществом, чтобы избавиться от абстрактного контроллера
Eliminate the need for inheritance for action controllers
при том что ничего плохого в наследовании нет, поскольку композиция не всегда является гибким решением.
В самом по себе в наследовании нет ничего плохого, но ошибочно используют наследование очень многие. Наследование вводит самый жестки coupling который только может быть, и потом уже ничем его не подменишь

И создали систему супервизоров, которой в модели акторов нет.
Система супервизоров это как производная от трех пунктов выше, т.е. это уже конкретный механизм, который создан используя все три пункта.
Ну и в современных реализациях модели Акторов супервизоры стали стандартом де-факто.
теперь будет создавать команды, а не работать с ресурсами.
Да, вы правильно поняли, именно это и есть принципиальное отличие двух подходов.

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

Смотрите, есть такой набор API


в CRUD API у вас все крутится вокруг ресурса. И ваши API — это фактически аксессоры которые меняют какие-то из полей этого ресурса (изменяют его состояние).
И если в случае изменения статуса или уценки продукта вам все еще кажется, что это просто изменения состояния ресурса, хотя на самом деле это бизнес операция, которая влечет за собой изменение состояния.
То в случае операции заказа продукта (Place Order) — у вас нет ресурса заказ в системе, чье состояние вы могли бы изменить. Это бизнес операция, которая повлечет за собой изменения ряда сущностей.

Здесь, кстати, очень показательный момент
юзер говорит вам конкретно, что он хочет сделать с ресурсом.
Вы как программист привыкли следить за состоянием сущностей и их переходом из одного в другое. Во время отладки, при дебаге и т.д.
На самом деле в ООП сущности меняют свое состояние исключительно как side-effect выполнения каккой-то команды. Основный принцип ООП — tell don't ask .
Поэтому у вас не может быть просто изменения статуса (Out of Stock) — у вас есть команда, которая создали и которую нужно обработать.

Поэтому юзер никогда не скажет, что он хочет сделать с ресурсом, юзер просто скажет — что он хочет сделать , для него это первично. Не ресурс, а операция.

На самом же деле Actor Model вовсе не гарантирует, что разработанные на ее базе приложения будут иметь более-менее нормальную отзывчивость и/или масштабируемость.

Здесь не соглашусь, так как именно эти свойства являются в модели Акторов основопологающими.
И многие системы применяют модель Акторов, чтобы достичь масштабируемости. Например Twitter использует Actor-ов для отсылки твитов именно для масштабирования

Notably, a queuing system, kestrel, that mediates between Twitter’s web user interface and the processing of “Tweet following” and sending tweets was written in Scala and implements Actor. This implementation is much simplified from other implementations, is more reliable, and scales to handle billions of tweets.

Сам же Erlang создавался без оглядки на Actor Model (автор Erlang-а описывая историю разработки языка не говорил, что использовал модель акторов).

В Erlang Actor-ы называются process, но при этом они не перестают быть Акторами.
Так как основные три ответственности Акторов у process соблюдаются:
— создавать новые акторы
— посылать сообщения
— устанавливать, как следует реагировать на последующие полученные сообщения

По поводу Actors vs Microservices я с вами соглашусь. Пример с Microservices и SOA, который вы взяли из моей терминологии — очень удачный.

Для меня основная разница между Акторами и Микросервисами это гранулярность.
Actor Model достаточно fine-grained, так как Актор представляет собой эквивалент одного объекта в терминах ООП.

Микросервис представляет из себя coarse-grained сервис, и является своего рода Фасадом, состоящим из большого количества объектов (или Акторов).
честно-говоря, по вашему описанию я смутно понял, что вас резануло по уху.
Но в CQRS особенно в сочетации с Event Sourcing, команды именно создаются после выполнения какого-либо действия.
Действие порождает создание команды, которая ложится в очередь команд на обработку. И будет обработана вне основного потока выполнения программы.

Так как сама природа CQRS + Event Sourcing — это Eventual Consitency
ACID (Atomicity, Consistency, Isolation, Durability) заменяется на -> BASE (Basically Available, Soft state, Eventual consistency).

Если я вас понял правильно, то
Вы продолжаете рассуждать с точки зрения работы с ресурсом и его модицикации (т.е. вы рассуждаете в стиле CRUD). Например, чтобы пометить продукт как Out of Stock вы делаете следующие шаги
  • достали продукт по айдишнику
  • изменили статус продукту
  • сохранили продукт

но бизнес процессы у вас другие. Для человека из бизнеса есть операция — вывести продукт из стока:
  • вывести продукт из стока — для этого действия создается операция

Создавая такие API, которые соответсвуют ubiquitous language (в терминологии Эванса) вы гораздо точней описываете доменную область и процессы в ней.

Основная идея в том, что REST зачастую используют для CRUD операций, поэтому API выглядят как

действие, которые определяется HTTP Request методом (POST, GET, PUT, DELETE, PATCH и т.д.) и путь к сущности над которой совершается действие.
Например,
PUT  /products/{id} - редактировать продукт
POST /products       - создать продукт


В случае Task Based UI и CQRS — мы всегда создаем команды для выполнения, т.е. у нас по факту получается для
Query операций используется GET метод и для Command операций используется всегда POST метод (т.к. мы создаем команду в идеологии REST для этого используется POST). Остальные методы нам по сути не нужны.

За счет этого мы уходим от концепции CRUD, которая в основном удобна при RAD (Rapid application development), когда мы хотим быстро развернуть приложение и сложность этого приложения не велика, таким образом сущности и их API зачастую представляют собой отображение того как эти сущности хранятся в базе. И переходим ближе к Domain Driven API (Проблемно-ориентированные API), которые лучше отражают бизнес процессы, происходящие в системе.

Собственно этот слайд показывал как меняется REST API с переходом на CQRS
Я не читал ничего по данной тематике на русском языке, и не знаю корректных переводов определённых терминов (i.e. coarse grained service) потому что привык говорить их по-английски. Этот доклад был рассчитан на англо говорящую аудиторию, и ей он и презентовался. Запись на видео — dev talk внутри компании после конференции.

Жаль, что у вас нет замечаний по сути, Вы очень упорный могла бы получиться интересная дискуссия. А так обсуждаем некорректный перевод уже который комментарий.
Кстати, всегда думал, что на Хабре хорошим тоном принято считать уведомление автора статьи об ошибках в личном сообщении.
Да, есть, но еще не залит на YouTube. Появится в ближайшее время.
Интересно, шаблонный код это явно указание зависимости, например, в yml?
Мне больше нравится как оно звучит на английском языке, лучше передает суть — boilerplate code.
Да, именно такого кода, который программист обычно copy-paste-ит мы хотим избегать.

Magento по-факту сама вставит вам кастомизированную зависимость в Child, если кто-то кастомизировал ее для Parent, а для Child она не переопределена. Вам для этого не нужно ничего указывать.

Декларацию parent, как в Symfony мы также хотим избегать, во-первых, по причине описанной выше, а во-вторых, потому что Magento не рекомендует использовать Inheritance Based API, т.е. расширение путем наследования в целом. Мадженто для этого предоставляет достатоно других механизмов взамен. И рекомендованным путем является композиция объектов.

Существующие цепочки наследования, например существование Абстрактных: модели, контроллера, блока — можно расценивать как легаси код в системе, который мы пока не убрали в первую очередь из-за требований обратной совместимости, которые мы соблюдаем в 2.* релизах.
Собственно именно поэтому мы сделали deprecated методы save и load на абстрактной модели, как вы заметили.

По поводу документации, сейчас требования к документированию кода очень высоки.
Не зря вы заметили, что добавилось описание к deprecation save и load.

Вот так, например, комментиуется новый код.

Но Magento — это Open Source проект, об этом была моя первая презентация, видео которой тут выложено. Поэтому если Вы видите как Вы можете что-то улучшить в коде или в документации, Вы можете поставить Pull Request, и если он соответствует нашим требованиям, то мы его обязательно приймем, а если не соответсвует, но идея покажется нам полезной — поможем доработать его до вида, чтобы влить его в мейнлайн.
Это касается и автоматического отслеживания изменений и тегов @ see, которые мы пока не бекпортировали в 2.1.*
когда пишешь код в режиме Develop (а именно в этом режиме обычно пишут код программисты) нет надобности перегенеривать после любых изменений все код-генерированные сущности.

Достатчоно удалить, только нужные файлы, которые были изменены. И система пересоздаст только их. Не нужно удалять всю папку 'generated' при этом. Это не должно занять много времени.

Вероятно сейчас после каждых изменений Вы запускаете DI компиляцию всего. Этого делать не нужно
мы сделали auto-injection в DI, в отличие от Symfony опять же, чтобы избавить программиста от написания шаблонного кода.

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

Мадженто работает по-разному, в зависимости от режима: develop и production
в develop код генерация происходит на лету, в production подразумевается, что вы заранее произвели всю код генерацию и ничего больше генерировать не надо, таким образом производительность будет выше. Т.е. в production режиме не будет механизма по отслеживанию изменений, потому что в нем нет надобности — вы не должны изменять код в решиме production.

Теперь по поводу настройки IoC контейнера (ObjectManager).
В Мадженто система настройки гораздо гибче, чем в Symfony
у нас это di.xml файлы, которые хранят описание
preference, type, virtualType, plugin
здесь можно почитать подробней об этом — http://devdocs.magento.com/guides/v2.0/extension-dev-guide/build/di-xml-file.html

Information

Rating
Does not participate
Location
Киев, Киевская обл., Украина
Date of birth
Registered
Activity