• Разгоняем Magento Rest API c помощью RoadRunner
    0
    у нас в om можем оставаться \Magento\Store\Model\StoreManager::getStore, или текущая категория, или значения из регистра, которые были сохранены в рамках выполнения предыдущего запроса.
    В идеальном мире — если сам сервис stateless, то да. Но мы, к сожалению не в идеальном мире и у нас попрежнему есть состояния.
    Было бы интересно, в частности запустить Magento web api тесты, на таком сетапе и посмотреть проходят ли они.
  • Разгоняем Magento Rest API c помощью RoadRunner
    +1
    проблема со стейтом, к сожалению остается и с Web API, так как тот же ObjectManager и его состояние будет переиспользоваться между запросами. И если в случае одного потока это незаметно, то когда потоков будет много, и они будут в разных контекстах (сторов), то результаты могут быть неконсистентны.

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

    Но поддержку PSR-7, мы точно хотим добавить. Если у вас есть время/желание — можете помочь Magento это сделать в виде контрибьюшена.
  • Декларативная схема и что с ней не так в Magento 2
    0
    Возможно, в обозримом будущем, когда M2 полностью перейдет на декларативную схему и отпадет надобность писать патчи, это будет супер удобно. Но будет ли это и когда случится, вопрос остается открытым.


    Вполне себе решенный вопрос, Magento 2.1 уже не поддерживается, а Мадженто 2.2 будет поддерживаться до декабря 2019 года, т.е. остался последний патч релиз 2.2.10 который выйдет в ближайший месяц.

    magento.com/sites/default/files/magento-software-lifecycle-policy.pdf

    Так что можете восприниматься, что М2 уже фактически полностью перешла на декларативную схему. Вопрос остается только с кастомизациями и экстеншенам, но с момента EOF 2.2 и они подтянутся.
  • Magento 2: импорт продуктов из внешних источников
    0
    Метода, который вы описали для работы с инвентори
    /** @var \Magento\Catalog\Model\Product $prodEntity */
    /** @var \Magento\Catalog\Api\ProductRepositoryInterface $repoProd */
    $inventory = [
        'is_in_stock' => true,
        'qty' => 1234
    ];
    $prodEntity->setData('quantity_and_stock_status', $inventory);
    $repoProd->save($prodEntity);
    

    Является устаревшим в Magento 2.3.
    C появлением нового механизма инвентори (Magento MSI) данные инвентори отвязаны от сущности продукта и сохранять их предлагается через отдельные API эндпоинты:

    https://github.com/magento-engcom/msi/tree/2.3-develop/app/code/Magento/InventoryApi/Api

    Здесь можно почитать подробней о новом механизме инвентори — https://devdocs.magento.com/guides/v2.3/inventory/
  • Magento 2 REST API на примере простого модуля
    0
    нет, это будет новый Entity Manager, мы добавили для старого \Magento\Framework\EntityManager\EntityManager такой комментарий в коде: github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/EntityManager/EntityManager.php#L15-L24

    Основная причина отказаться от него — неэффективная работа с экстеншен атрибутами, работа с разными хранилищами данных, отсутствие эффективного механизма Query API.

    >В какой версии предполагается релиз этого функционала?
    Какие-то части функционала нового ORM мы уже зарелизим в 2.3.*
    например декларативную схему — community.magento.com/t5/Magento-DevBlog/A-Declarative-Approach-for-Database-Schema-Upgrades/ba-p/70763
    которая уже есть в 2.3-alpha

    но полность функционал нового ORM должен быть доступен в версии 2.4
  • Magento 2 REST API на примере простого модуля
    +1
    По поводу пункта 3.

    Вы тоже правы, что extension_attributes это не о персистенсе, более того на этом уровне сущность должна быть описана persistence-agnostic.
    И у нас по прежнему есть описание типа «join» в XSD Schema — github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/etc/extension_attributes.xsd#L48-L55

    но по факту вы не найдете его использования в модулях Magento, только в тестах.
    Этот механизм, который назывался Join Directives был обьявлен как deprecated из-за причин описанных Вами и его поддержка остается только по причинам Backward Compatibility, поэтому сейчас обязанности по сохранению и извлечению атрибута возложены на программиста, зачастую это делается с помощью механизма плагинизации, например сервиса Repository::save($entity).

    Более элегантное решение с точки зрения описания Entity, а также два типа декларации: persistence-agnostic высокоуровневой декларации, и низкоуровневой декларации, которая декларативно опишет загрузку данных (для ускорения производительности, так как сейчас с этим есть определенные пробелемы, так как дополнительные атрибуты хранятся в отдельных таблицах и нужно сохранять и загружать их отдельными запросами) появятся вместе с новым ORM в Magento

  • Magento 2 REST API на примере простого модуля
    +1
    сорри, только сейчас заметил ваш диалог.
    В целом Вы isxam все правильно написали.

    Magento отказалась от наследования DTO классов от Magento\Framework\Api\AbstractExtensibleObject, и сейчас новый код, в частности новые модули Inventory, используют наследование от Magento\Framework\Model\AbstractExtensibleModel.

    Основную неразбериху, думаю вызвало то, что класс AbstractExtensibleObject помечен как `@api`, в то время как AbstractExtensibleModel не имеет соответствующей пометки.

    Вчера на внутреннем архитектурном митинге, где мы в частности обсуждали этот вопрос приняли решение:
    • Добавить PHP аннотацию `@api` для AbstractExtensibleModel
    • Добавить PHP аннотацию `@deprecated` для AbstractExtensibleObject и `@see` которая ссылается на AbstractExtensibleModel


  • DDD на практике. Проектирование списка желаний
    +1
    Ну и судя по статье вы сделали классический God object из своего объекта Wish. В нем зашили вообще всю логику домена, нарушив при этом Single Responsibility принцип множество раз.
    Само количество публичных методов у этого объекта (а их там 20) толжно было дать вам почувствовать «smell» в вашем коде.
  • DDD на практике. Проектирование списка желаний
    +4
    у вас даже начало статьи принципиально не DDD-шное, и показывает, что вы мыслите CRUD подходом.
    Потому, что статью вы начинаете с того, как создаете проект и базу данных для него.
    Для Domain Drive Design — это абсолютно не важно. Детали реализации, которые должны быть вынесены за рамки статьи. Так как один из главных принципов DDD, который отражается в проектировании это persistence ignorance.

    Дальше вы выбираете набор атрибутов, которые нужно хранить в классе. Не рассмотрев домен и как действуют сущности в нем.
    Отталкиваясь от того какие ключи в Базу повесить.
    Это не DDD, это CRUD c его подходом — Forms over Data.
    Процесс проектирования в DDD начинается с чего-то вроде Event Stroming, где основная идея это выделить основные доменные события (Domain Events) и то как посредством их взаимодействуют доменные сущности, а также определиться с именованием этих сущностей (Ubiquitous Language)
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    Посмотрел — испугался — еле-еле отошел. Поэтому решили сделать свой eCommerce-Framework.
    Ну в это и разница, в том, что Magento — самый популярный в мире eCommerce framework, которым пользуется более 200 000 мерчантов по всему миру. А вы написали решения под себя, заточенное под реализацию одного бизнес процесса. И вы не переживаете ни про расширяемость, ни про настраиваемость, поэтому и создаете объекты через new, и использование билдера выглядит для вас странно. Потому что количество людей, которые будут пользоваться вашей системой — ограничено и известно заранее. В этом и разница между фреймворком и проприентарной системой.
    Но вот наименования как раз то и нужны, чтобы хотя бы разрабы знали и видели, что у них в коде.
    Оно (наименование) есть, что легко заметить даже по этой статье, вы просто очень хотите, чтобы оно соответствовало тому, что вы прочитали в книге «Domain-Driven Design in PHP».
    Да я понимаю и (на болезненном опыте) знаю, что бизнес-язык отличается иногда от реальности, но что мешает нам называть вещи своими именами
    Эта фраза противоречит понятию Ubiquitous Language
    что ваш фрэймворк делает и какие процессы и соответствующие сущности в нем уже есть.

    За это и отвечает слой сервис контрактов, интерфейсы которого здесь представлены.
    А то как будут они вызваться и какой будет транспорт. Будет ли использоваться классическая событийная модель, таблица базы данных, транзакционная модель с предпочтением data consistency, или BASE с Eventual Consistency — это уже обусловлено бизнес процессами конкретного мерчанта.
    Поэтому и dispatch мы не показываем наружу. Так как диспатч — это часть реализации характерная для определенного бизнес процесса.
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    Ну давайте по порядку:
    Такой вызов нам не подходит:
    $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?
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    Вы знаете, после общения с вами, решил немного изменить интерфейс резервации. Так как по факту статусами мы особо не пользовались. Это был больше механизм отладки для поиска потерянных резерваций. Но если он так сильно наталкивает на мысли о 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
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    ну вот, вы нашли в моей статье и коде то, чего там не было, а в итоге обвинили код и его читабельность :)
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    Давайте опишу более популярно, так как мне кажется, что вы не имели опыта работы с 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 как-то по другому выглядит?
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    Давайте по порядку. Потому как вы начали спорить с самим собой.
    Где вы прочитали, про то, что какой-то из атрибутов сущности резервирования (в частности статус) изменяется?
    Из описания статьи:
    размещая заказ на 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, т.е. напрямую ваш код не работает с ними.
    Пользователь системы не должен хотеть работать с объектами резервирования.
    он видит конечный автомат. Так как он не знает, что вы собираете состояние резервации. По большому счету ему на это забить, он хочет знать актуальное состояние его резервации или заказа.
    Вы даете больше ответственности на сущность резервации чем это делаем мы.
    Пользователю интересно — состояние заказа.
    Сущность резервации не отвечает за статус заказа и не изменяется вместе с ним. Объект резервации создается для подсчета корректности стока, который используется для вычисления колличества продуктов в стоке.
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    Вы вероятно пропусти описание механизма работы объектов резервирования. Само состояние в пределах объекта не изменяется. Reservation является immutable сущностью, при этом у него есть статус, который говорит о том в каких условиях (в рамках какого бизнес процесса) создавалась резервация.
    По большому счёту это точка расширения для кастомизаторов. У нас вполне могло и не быть статуса, нам хватает набора (stockId, sku, qty) для подсчета актуальности стока.

    Т.е. Если вы представили конечный автомат, и резервацию, которая переходит из одного состояние в другое — то это предположение ошибочно
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    В теле статьи я описал как в Magento 2 выглядят репозитории и почему.
    А доступ к базе у нас «спрятан» не в командах, которые должны быть агностичны к Data Storage механизму, а в ресурс моделях.
    Пример из статьи:
    Команда для MultipleSave опареции SourceItem — Magento\Inventory\Model\SourceItemSave.php

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

    Repository у нас это Фасад, чтобы клиент мог иметь одну зависимость на репозитори для выполнения базовых операций с сущностью. Например, достать сущность по ID или поиском, изменить, сохранить (если он делает CRUD операции над сущностью, например в гриде). По большому счету Repository существует только для удобства и простоты кода клиента, так как мы могли бы просто давать набор команд.
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    метод getStatus возвращает одну из предопределенных констант, статуса в котором может пребывать резервация. Под капотом этот статус хранится как Int.
    Если вопрос почему мы не использовали Value-Object для хранения статуса, то мы не заменяем примитивные тимы на типы-обертки по двум причинам:
    1. performance, чтобы не создавать дополнительное большое количество объектов.
    2. это бы перегрузило и усложнило код бизнес логики (клиента), так как для создания статуса и других атрибутов (которых может быть много) код бизнес логики должен был бы использовать фабрики.
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    Ну давайте начнем с того, что в статье описан Service Layer, который представляет набор доменных сервисов (терминами DDD), соответсвенно название этих сервисов должны представлять бизнес процессы в домене на языке Ubiquitous Language и отображать как пользователи пользуются системой.
    Поэтому на этом уровне dispatch у нас быть не может.

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

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

    В статье сознательно не описывается метод хранения резерваций (Event Store если позволите), так как он может быть реализован по-разному. Этому может быть таблица базы данных, очередь Redis, Cassandra и т.д.
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    По вопросу гридов, в 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 возможностью подменять модель данных присутствует.
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    Для примера 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.
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    Можете не извиняться, но как на уровне Service Layer должен выглядеть Event Sourcing, чтобы он был заметен?
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    а что именно вы ожидали увидеть чего не нашли в описании Service Layer?
  • Система управления складом с использованием CQRS и Event Sourcing. Service Layer
    0
    Хороший вопрос. Тут на самом деле ряд факторов сыграли.
    Начиная с того, что у нас мало что изменилось в концепции 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.
  • Microservices и Модель Акторов (Actor Model)
    0
    А можно ссылку на пруф?
    Не помню в какой именно из презентаций слышал именно эту формулировку.
    Но вот достаточно близкая по смыслу тоже от него
    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

  • Microservices и Модель Акторов (Actor Model)
    +1
    Так же попробуем обозначиться с тем, что же на самом деле 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. О чем он постоянно говорит на своих докладах.

    ну как-то так.
  • Magento Dare to Share — Открытая Площадка для докладов о Magento, PHP и eCommerce
    0
    То как вы описали,
    preference for=«Magento\Framework\Logger\Monolog» type=«Coolryan\PreferenceExample\Model\Log»

    Это не рекомендованый подход. В данном случае вы будете использовать механизм Duck Typing.
    Который не безопасный и не объектно ориентированный по своей сути.
  • Magento Dare to Share — Открытая Площадка для докладов о Magento, PHP и eCommerce
    0
    И раз пошла такая петрушка, куда я могу оформить баг, при котором админка мадженты уходит в цыклический редирект?

    Вы можете завести GitHub issue
    Описав проблему и предоставив шаги, чтобы ее воспроизвести и кто-то из команды Magento или из сообщества вам поможет в решении этой проблемы.
  • Magento Dare to Share — Открытая Площадка для докладов о Magento, PHP и eCommerce
    0
    Немного смешно звучит учитывая что при создании модуля мы постоянно делаем рутинные действия, например создание модели, с ресурс моделью и коллекцией.
    Не вижу здесь ничего смешного. Это называется разделение ответственностей в многослойной архитектуре. Или вы хотите, чтобы модель попрежнему себя сохраняла? Вы же сами выше писали про 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 который только может быть, и потом уже ничем его не подменишь

  • Microservices и Модель Акторов (Actor Model)
    –2
    И создали систему супервизоров, которой в модели акторов нет.
    Система супервизоров это как производная от трех пунктов выше, т.е. это уже конкретный механизм, который создан используя все три пункта.
    Ну и в современных реализациях модели Акторов супервизоры стали стандартом де-факто.
  • Microservices и Модель Акторов (Actor Model)
    +1
    теперь будет создавать команды, а не работать с ресурсами.
    Да, вы правильно поняли, именно это и есть принципиальное отличие двух подходов.

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

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


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

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

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

  • Microservices и Модель Акторов (Actor Model)
    –1
    На самом же деле 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 сервис, и является своего рода Фасадом, состоящим из большого количества объектов (или Акторов).
  • Microservices и Модель Акторов (Actor Model)
    0
    честно-говоря, по вашему описанию я смутно понял, что вас резануло по уху.
    Но в 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 (в терминологии Эванса) вы гораздо точней описываете доменную область и процессы в ней.

  • Microservices и Модель Акторов (Actor Model)
    +1
    Основная идея в том, что 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
  • Microservices и Модель Акторов (Actor Model)
    0
    Я не читал ничего по данной тематике на русском языке, и не знаю корректных переводов определённых терминов (i.e. coarse grained service) потому что привык говорить их по-английски. Этот доклад был рассчитан на англо говорящую аудиторию, и ей он и презентовался. Запись на видео — dev talk внутри компании после конференции.

    Жаль, что у вас нет замечаний по сути, Вы очень упорный могла бы получиться интересная дискуссия. А так обсуждаем некорректный перевод уже который комментарий.
    Кстати, всегда думал, что на Хабре хорошим тоном принято считать уведомление автора статьи об ошибках в личном сообщении.
  • Microservices и Модель Акторов (Actor Model)
    0
    Да, есть, но еще не залит на YouTube. Появится в ближайшее время.
  • Magento Dare to Share — Открытая Площадка для докладов о Magento, PHP и eCommerce
    0
    Интересно, шаблонный код это явно указание зависимости, например, в 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.*
  • Magento Dare to Share — Открытая Площадка для докладов о Magento, PHP и eCommerce
    0
    когда пишешь код в режиме Develop (а именно в этом режиме обычно пишут код программисты) нет надобности перегенеривать после любых изменений все код-генерированные сущности.

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

    Вероятно сейчас после каждых изменений Вы запускаете DI компиляцию всего. Этого делать не нужно
  • Magento Dare to Share — Открытая Площадка для докладов о Magento, PHP и eCommerce
    0
    мы сделали auto-injection в DI, в отличие от Symfony опять же, чтобы избавить программиста от написания шаблонного кода.

    И чтобы он конфигурировал, только то, что ему нужно конфигурировать. А не все зависимости класса.
  • Magento Dare to Share — Открытая Площадка для докладов о Magento, PHP и eCommerce
    0
    Если вы хотите добавить кастомную фабрику, т.е. ее логика будет отличатсья от кодогенерируемой. Вы ее просто пишете, и ничего код-генериться не будет.

    Мадженто работает по-разному, в зависимости от режима: 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