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

Lead Architect, Magento an Adobe

Send message
прелесть в том, что вы Можете его не использовать, если вам он не нравится, или вам кажется это магией. В M2 Unit тесты могут писаться и без его использования. Кстати, таких достаточно много, так как у нас внутри нет требования его использовать, и многие программисты считают, что лучше создавать все «вручную».

Его задача — избавить вас от надобности нагенеривать кучу моков вручную. Опять же к разговору как Magento борется с шаблонным кодом (boilerplate code).
Про прмер принципа Лисков https://youtu.be/FquSm_LOmS4?t=1041
Чисто ради интереса, понимает ли докладчик, что показывает прмер использования Symfony Console Component и почему он считает что именно это пример принципа подстановки Барбары Лисков?
Принцип Лисков в первую очередь говорит о том, что Наследование это очень сложное отношение между объектами, которое добавляет большой coupling, и многие его используют не правильно. Большинство использует его только чтобы избавиться от дублирования кода. Но для наследования должно еще выполняться отношение is_a (является).
То что докладчик выбрал механизм консольных команд представленный в Magento 2 (которые действительно расширяют Symfony\Component\Console\Command\Command) не есть проблемой.
В данном примере, новая команда чистит кеш, но при этом отношение is_a по отношению к родительской команде соблюдается.

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

Написанному можно верить :)
Почему в magento2 была выбрана стратегия кодогенерации?
Вы про код генерацию в принципе или про что-то конкретное Interceptor, Factory, Proxy?

Мы используем код генерацию там, где считаем она избавит программиста от написания шаблонного кода (boilerplate code).
Например, логика Factory и Proxy в шаблонном случае одинакова — поэтому мы кодгенерим классы просто когда программист указывает нужный суфикс (Factory или Proxy) добавляя его к имени сущности, которую он хочет создать.

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

Я так понимаю, вопрос продиктован некоторой сложностью дебага кода с интерсептерами. Но уже существуют плагины к PHP Storm, которые решают эту проблему и убирают из стека служебные классы, для удобста чтения.
Почему в magento2 не сделали явное описание создания сервисов, как например в symfony? Зачем нужна эта магия?
В Magento 2 используется концепт Service Layer, и API находятся в папках каждого модуля. На эти API мапятся Web API. Например, здесь можно почитать как API добавлялись для одного из модулей.
Я не понял какая именно магия имеется в виду.

ну если вы Unit-ом назовете всю систему, то да, это Unit-тесты :)

Как я говорил выше, тестирование достигалось путем определенной инициализации Mage::app класса.
Где определенные базовые сущности, например Layout, Session объекты подменялись на предопределенные моки, т.е. Всю систему конфигурировали так, что она запускалась в тестовом режиме.
Собственно две статьи выше демонстрируют именно эту технику.
Почувствуйте разницу — протестировать объект в изоляции от всей системы vs сконфигурировать систему, что она запускается в режиме для тестирования.

Кстати, код теста из статьи
    	/* You'll have to load Magento app in any test classes in this method */
    	$app = Mage::app('default');
    	/* You will need a layout for block tests */
        $this->_layout = $app->getLayout();
        /* Let's create the block instance for further tests */
        $this->_block = new Company_Module_Block_Blockname;
        /* We are required to set layouts before we can do anything with blocks */
        $this->_block->setLayout($this->_layout);


четко показывает, что здесь мокается только зависимость, которая передается через сеттерный метод.

Очень непрозрачно отслеживать и «мокать» зависимости, которые приходят не через сетеры, а создаются на ходу через Lazy Loading внутри методов, так как в этих подходах нет механизма фикстур и нет возможности подменить дополнительные сущности на моки для определенного теста.

Обратите еще внимание, что класс Mage был объявлен как final
https://github.com/engineyard/magento-ce-1.9/blob/master/app/Mage.php

Теоретически, вы могли написать свой фреймворк, где бы вы создали тестовое окружение (environment), в котором по-другому инициализировали бы Mage object
https://github.com/engineyard/magento-ce-1.9/blame/master/app/Mage.php#L606
через _setConfigModel($options = array())

Но опять таки — это не Unit тестирование в классическом понимании. Более того, это настолько не просто и не очевидно. Что так никто не делал.
не буду отвечать пока за автора на вопросы адресованные ему.
Отвечу на замечание по поводу Unit тестов в Magento 1.

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

в Magento 1 вам
никто не запрещал писать тесты
но эти тесты сложно было назвать Unit тестами.
Потому что M1 активно использовала шаблон Service Locator для создания сущностей внутри системы. Сущности создавались по мере выполнения логики класса, этим достигался эффект Lazy Loading внешних зависимостей. В коде это было реализовано с помощью God-класса Mage и его статических методов getModel, getSingleton, getHelper. Из-за отсутствия Dependency Injection, а также из-за использования статических методов для инстанциации внешних зависимостей — разработчики были лишены возможности создать моки (Mock) или стабы (Stub) для внешних зависимостей, чтобы покрыть тестами класс в изоляции от внешних зависимостей.

Поэтому тесты, которые писались под М1 были либо функциональными либо интеграционными — тестирующие код в интеграции с системой и не использующие моки для внешних зависимостей.
используюя SalesChannel некий резолвер (то что вы выше назвали helper/manager) определяет Stock, и для этого стока уже берутся StockItems

Фактически да, шестеренка в передаточном механизме.
А что делать, если Website (SaleChannel) связан со многими Стоками? Покупатель выбирает отдельный Сток в рамках Канала? Да, получается так.

Собственно в этом и задумка, что для определенного контекста у вас должен быть всего один Stock.
Т.е. в вашем случае, насколько я понимаю, хочется чтобы Store View определяло Stock, а не Website.
Это не проблема, как я описывал выше сейчас все наши API работают в контексте store_code, т.е. Store View

Т.е. где-то в недрах приложения есть данные о привязке Website/Store/StoreView/Country/… к КаналамПродаж. Некий helper/manager анализирует текущую сессию клиента и на основании данных о привязках определяет, какой Канал предоставить клиенту.

да, так и есть.
После чего клиент выбирает из доступных Стоков тот, с которого он хочет приобрести продукт?
это делает все тот же резолвер, так как для определенного контекста у нас есть всего один Stock
Еще один очень праильный вопрос.

Да, Вы правы, когда физический склад (Ф) завязан на несколько виртуальных (А, Б, В), то даже механизм резерваций (Reservation) нам не поможет гарантировать на 100%, что мы продали по факту больше чем у нас есть на складе.

Так как в этом случае оформив заказ через сток А и создав резервацию по продукту SKU-1 для Stock A, мы не знаем по факту с какого Source она спишется (т.е. какой физический склад выполнит доставку). Поэтому не совсем корректно уменьшать количество товара SKU-1 для Stock Б, используя резервацию на стоке А.

Из-за этого пока первый заказ не обработан и находится, скажем в очереди, когда второй покупатель выполняет покупку через Stock Б, он будет видеть первоначальное число товаров (без учета продажи через Stock А).

Система выполнит при этом продажу, и спишет деньги у покупателя, но на момент обработки второго заказа — первый будет уже обработан и система будет знать точные данные по товарам на физических складах. Поэтому выбросит ошибку, что не может обработать второй заказ, если товара на складе Ф будет не достаточно, и его также не будет на других складах.
По большому счету такое поведение может быть приемлемым, например, тот же Amazon делает похожим способом. Используя Eventual Consistency для обновления товаров на складе. И если происходит такая «накладка» — возвращает деньги покупателю и отменяет заказ.

Мы используем много-много между Складами (Source) и Вирутальным агрегациями (Stock) по двум причинам:
1. Мы строим фреймворк и не должны искусственно ограничивать мерчатнов в построении их бизнес модели. Т.е. если для вашего бизнеса это абсолютно неприемлемо продать больше чем есть на складе — просто не мапте один сорс на несколько виртуальных агрегаций и используйте связь как один-ко-многим.

2. КДПВ в посте (первая диаграмма) это фактически бизнес требования для нас, которые основаны на том, какие есть сейчас клиенты у нашего Magento Commerce Order Management. И сейчас они сталкиваются с тем, что один физический склад присвоен нескольким виртуальным Stock.

Здесь на видео, кстати, можете послушать нашего VP технологий и продукта, который рассказывает об Omni Channel на примере Франкфуртского аэропорта.
Так вот там Stock динамический и всегда определяется под клиента, например, если вы летите только через один терминал аэропорта, то ваш сток это сумма товаров только в этом терминале. Если вы находитесь в двух терминалах, например летите транзитом, то сток — это сумма по двум терминалам. В этом случае если у вас склад (Source) в каждом из терминалов, то этот склад Source будет использоваться в разных виртуальных агрегациях Stock
https://www.youtube.com/watch?v=MlDWsJugF78&feature=youtu.be&t=1m5s

Кстати, на проекте MCOM они решают описанную выше проблему тем, что используюь threthhold пороги продаж по Source и Stock. На первой диаграмме это хорошо видно. За это отвечает поле Safety Stock, если оно например равно 10, то система не будет продавать дальше товары если их количество в StockItem достигнет или опустится ниже 10. Safety Stock выставляется на каждый Stock и Source, т.е. вы можете поставить не нулевое значение для тех Stock-ов которые переиспользуют один и тот же Source.

Отличный вопрос!
Есть ощущение, что программные объекты не совсем корретно отображают отношения соответствующих сущностей в структурах данных (таблицах).
Да, именно так. В струтурах данных связь один (Product) ко многим (StockItems) колличество продуктов в разных виртуальных агрегациях.

Но в программных интерфейсах связь 1-к-1 как вы правильно заметили.

Дело в том, что у нас Stock всегда определяется в рамках определенного контекста (Sales Channel), т.е. для текущего контекста, например Store View = default у нас есть только одно представление Stock. А если Stock определен, то мы можем получить только один StockItem для определенного SKU.

Здесь есть и определенные ограничения, так как контекст Stock должен быть точно такой же как и контекст Product.
Сейчас продукт извлекается в таком контексте.
http://<magento_host>/rest/<store_code>/schema&services=<serviceName1,serviceName2,..>
rest/<store_code>/V1/products
The value of `store_code` can be one of the following:

default
The assigned store code
all. This value only applies to the CMS and Product modules. If this value is specified, the API call affects all the merchant's stores. GEToperations cannot be performed when you specify all.

http://devdocs.magento.com/guides/v2.0/rest/rest_endpoints.html

Так как мы делаем Coarse-grained API для продукта, т.е. через вызов API продукта мы должны получить StockItem для этого продукта в том же контексте в котором запрашивается сам продукт.
Т.е. мы уходим от один-к-многим за счет определения контекста, в рамках которого был выполнен вызов API получения продукта.
Но! Ужасно хочется попробовать сделать что-нибудь такое самому.
Не сдерживайте себя :) мы привлекаем разработчиков из комьюнити поучаствовать в проекте как идеями, так и кодом.
Не можете пояснить что это такое?
Вы можете послушать мою презентацию на эту тему.
Что означает «масшатбирование операции»?
Представим простой пример с базой данных СУРБД.
Какую бы вы предметную область не выбрали у вас всегда будут сценарии чтения и сценарии записи.
Если взять все тот же каталог товаров, то операциями чтения у вас будут — рендеринг страницы категорий, рендеринг страницы продуктов, рендеринг шопинг карты. В этих сценариях мы не изменяем данные, а просто читаем их из базы. Очень сильно упрощая и утрируя можно сказать, что выполняются SELECT запросы

Когда у вас совершаются покупки, или администратор добавляет новый продукт в админ панеле. Совершается операция записи. Опять же утрируя можно сказать, что в данных случая выполняются INSERT/UPDATE запросы.

Обычно соотношение запросов чтения/записи сильно в пользу чтения. Скажем, 80% на 20% как закон Парето.
Поэтому с увеличением нагрузки на сайт в какой-то момент вам нужно масштабировать ваши операции чтения.
Например, в СУРБД для этого используют механизм репликации, когда у одного мастера есть несколько слейвов, с которых данные читаются.
Когда же bottleneck выступает операции записи становится сложней, так как масштабировать запись в СУРБД всегда сложней чем чтение. До какого-то момента можно использовать все ту же мастер-мастер репликацию, но в принципе запись масштабируется хуже.

А когда код приложения написан таким образом, что одна модель (класс модели) объединяет в себе и операции чтения и записи, то на уровне кода удобно такое разделение не сделаешь.
Поэтому основная идея заключается в разделение интерфейсов по ответственностям на чтение и запись.
И такое разделение приводит к тому, что запись у нас может происходить в инфраструктуре оптимизированной под быструю запись включая сам адаптер для хранения, а чтение будет выполняться из адаптера оптимизированного под быстрое чтение, например Elasticsearch.
И если вам понадобиться оптимизировать/масштабировать отдельно чтение — вы сможете этого добиться не меняя код, ответственный за операции записи.

Выполнение такой сегрегации на уровне интерфейсов в коде всегда трудозатратно и требует тщательного проектирования.

Нет, не лучше. Что значит ориентировочную дату? Это значит ваши даты релизов должны быть и в коде тоже. А что если вы не успеете по каким-то причинам и релиз отложится? Все даты в коде станут неактуальны.
Программисту достаточно знать с какой версии изменения стали deprecated. Для того чтобы сторонний программист начал готовиться заранее и перестал использовать deprecated апи в новом коде и тем самым перестал накапливать технический долг, который должен будет в любом случае исправлен позже. Для всего остального есть SemVer — и при апгрейде на следующий мажорный релиз программист будет видеть список обратно несовместимых изменений, которые были сделаны для того чтобы оценить что именно ему нужно сделать для поддержки своего кода в новой версии.
это Agile терминология, которая получила широкое распространение и стала устоявшимся термином, а терминология может использоваться как есть (As is если пожелаете).

Вас же не смущает использования других терминов как deprecated, cohesion и т.д.
Ну не совсем настолько коротко :)
Так как основная идея была все же про рефакторинг. И что даже строгие ограничение BC не должны ему мешать.

Также в устаревших методах стоит писать в какой версии они будут удалены.

А вот здесь немножко не так. Мы не можем знать заранее в какой версии они будут удалены, так как наши планы могут поменяться, мы можем только знать с какой версии они перестали быть актуальны.
Поэтому чтобы проинформировать сторонних разработчиков о наших планах по изменению публичного кода мы добавляем маркер since вместе с аннотацией @deprecated

Как в примере:
/**
 * @deprecated since 2.1.0
 * @see \Magento\Framework\Model\ResourceModel\Db\AbstractDb::save()
 */
public function save()
{
    // ...
}


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

Статья слезно просит дописать её.

Дописал эти два пункта
не воспринимайте слишком буквально, это аллегория. Но если вам интересно.
— первый вариант починки дело рук программистов
— второй дело рук водопроводчика, который пытался починить кран
— третий — водопроводчик поменял кран на новый, так как не смог нормально починить его на второй итерации
да, зависимости на сущности не отмеченные @api собрали в автоматическом режиме.

После чего списки зависимостей были переданы командам, которые отвечают за конкретные модули.
И команды вместе с командным архитектором проходятся по этому списку в мануальном режиме (анализирую код и зависимости) отмечая какие зависимости являются валидными (т.е. Magento не предоставляет альтернативных API) и для них добавляются @api
аннотации, а какие — нет (для которых API аннотация добавлена не будет)
Более того, мы введем концепт публичного/приватного кода с 2.2 и соответсвенно обратно несовместимые изменения сможем вводить со следующим мажорным релизом, т.е. с 2.3

Мы стараемся никого не поломать нашим ближайшим релизом
Теперь по поводу разработки.
Фактически каждая коммерческая версия Magento — 2.0.*, 2.1.*, 2.2.* это отдельный продукт.
Поэтому имеет смысл иметь отдельные ветки и версии вашего экстеншена под каждую из мажорных коммерческих веток Magento.
В текущих версиях Magento 2.0.x и 2.1.x нельзя обойтись без зависимостей на приватный код.
Потому что у нас недостаточное покрытие @api для этого. Некоторые модули не имеют сервис контрактов вообще (например, wishlist). Поэтому у сторонних разработчиков нет другого выхода кроме как не помеченные api аннотацией.

Разработка API — самый сложный процесс в проектировании и разработке программного обеспечения. И так как мы пока не знаем когда именно закончится работа по добавлению сервис контрактов во все модули Magento мы решили в релизе 2.2 отметить @api все сущности, которые необходимы для написания/кастомизации модулей на мадженто сторонними программистами.
Т.е. в 2.2 мы отметим все «честные контракты», т.е. если функциональность, которую предоставляет модуль реализуется хелпером или ресурс моделью. И получить данную функциональность путем вызова API модуля нельзя, то мы пометим данный хелпер как @api.
Для примера, у нас есть ProductInterface, который определяет набор операций над сущностью продукта. И есть продуктовая модель, которая имплементирует этот интерфейс. Так как сейчас продуктовая модель наследуется от абстрактной модели и соответственно имеет контракт абстрактной модели, то мы не можем сказать сейчас, что сторонний разработчик можем полагаться только на ProductInterface, и если кто-то предоставит свою реализацию этого интерфейса, то сможет легко подменить внутреннюю реализацию мадженто (если это не будет наследник продуктовой модели). Т.е. достаточно много кода в Magento использует метод get/setData который пришел из абстрактной модели. Поэтому в 2.2 мы пометим продуктовую модель как @api, не смотря на то, что у нас уже есть API в виде ProductInterface.

До релиза 2.2. мы считаем весь код — публичным, т.е. на все классы распространяется политика обратной совместимости. Не только на классы, помеченные @api. Разделение концепта на публичный и приватный начнется с 2.2 релиза.

Теперь как мы определяем, что используют, а что нет.
Мы анализируем какие зависимости между модулями Magento использует внутри себя. А также мы анализируем какие зависимости используются экстеншенами на Marketplace (non api dependency). И если зависимости валидны, т.е. такой результат нельзя получить используя текущие API модуля — мы помечаем сущность как @api
тогда старанно, у меня он просто пропал, когда я начал писать ответ.

Information

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