- PVSM.RU - https://www.pvsm.ru -
Итак, после постановки требований описанной в части 1 [1] можно перейти к проектированию системы.
Основная наша задача в проектировании, как это понятно из названия статьи, добиться разделения интерфейсов на Query и Command, чтобы впоследствии разделить бизнес сценарии на те, которые будут читать данные (Query интерфейсы) и на те, которые будут изменять данные (Command интерфейсы). А также обеспечить минимальное время ожидание (latency) на обновление данных, доступных через Query, после того как мы изменили данные через Command.
В последнее время разделение методов интерфейса (CQS [2]), а впоследствии и самих интерфейсов на Query/Command стало популярным веянием в разработке архитектуры приложений. Но в Magento CQRS применяют не из-за популярности, а из-за того, что для нас иногда это единственный способ построить гибкое расширяемое (адаптирование к специфическим потребностям) решение с возможностью независимо масштабировать операции Чтения и Записи.
Фактически мы пришли к CQRS в результате осмысления и повсеместного применения SOLID в написании кода. CQRS это реализация принципа единой ответственности (The Single Responsibility Principle) и принципа разделения интерфейсов (The Interface Segregation Principle) и уход от классического CRUD.
Элементы CQRS у нас появились достаточно давно, когда мы задавались вопросом как масштабировать модель данных EAV (entity-attribute-value) [3] для операций чтения и вводили индексные агрегационные таблицы для этого.
Более подробно про CQRS, и что он для нас значит можно послушать в презентации, которую я делал на MageCONF 2016 (видео [4], слайды [5]).
В предметной области Inventory у нас есть шесть основных доменных моделей:
Данная диаграмма представляет собой взаимодействие описанных выше сущностей:
Таким образом мы получаем разделение интерфейсов на Query и Command:
Одна из наших основных задач — это максимально разгрузить процесс размещения заказа. И идеально сделать эту операцию такой, которая не будет изменять состояние системы (State Modification). Это избавит от лишних блокировок и улучшит масштабирование чекаута.
По диаграммам выше видно, что такие операции как рендеринг страницы категории будет выполнен используя только Query API (StockItem), где нам нужно отобразить количество товаров, которые мы можем продать в определенном контексте (SalesChannel).
Операция синхронизации с внешней ERP или PIM системой будет использовать Command API (SourceItem) и обновлять количество товаров на конкретных складах, после чего реиндексация, которая пересоберет StockItem позволит этим обновлениям быть видимыми на фронте.
В случае операции размещения заказа все становится интересней.
Операция размещения заказа разделяется на две операции: непосредственно размещение заказа, в которой принимает участие покупатель и которая заканчивается оплатой и подтверждением принятия заказа на выполнение; и обработка заказа, которая происходит постфактум (с определенной задержкой от нескольких миллисекунд до минут и часов) основная задача которой определить из какого именно склада (или складов) мы должны выполнить доставку нашему покупателю и произвести калибровку количества товаров на этих складах после выполнения заказа.
Собственно, рассмотрим эти операции на примере подробней.
Пусть мы имеем 3 физических склада: Source A, Source B, Source C. На которых хранится товар SKU-1 в таком количестве:
Мы имеет всего один канал продаж (Sales Channel), роль которого выполняет Website.
Для этого канала продаж у нас есть созданная виртуальная агрегация Stock A, с которой связаны все текущие физические склады (Source A, Source B, Source C). Получаем StockItem A для SKU-1 имеет количество 20+25+10 = 55
Соответственно, когда покупатель заходит на Website, система точно определяет Stock, который должен быть применен для определения количества товаров, и использует Stock Item-ы в рамках этого стока для всех продуктов (SKU) в категории, в нашем случае SKU-1.
Пусть покупатель решил купить 30 единиц товара SKU-1.
Состояние системы, которое мы имеем на текущий момент:
Количество товара SKU-1 на складах:
Количество товара SKU-1 для StockItem A — 55 (не изменилось).
Reservation для SKU-1 на Stock A в количестве 30.
Пока мы не успели обработать данный заказ, например из-за большой latency, к нам на сайт приходит другой покупатель, который также хочет приобрести товар SKU-1 в количестве 10 единиц.
Система начинает выполнять те же шаги, что описаны выше.
Проверяем можем ли мы продать 10 единиц SKU-1. Проверка осуществляется таким образом: количество StockItem A для SKU-1 = 55 минус количество всех резерваций для продукта SKU-1 на Stock A, в нашем случае 30, 55 — 30 = 25 > 10 принимаем решение, что продать 10 единиц товара SKU-1 можем.
По факту состояние в Event Sourcing определяется как проекция агрегированных данных (в нашем случае StockItem), с добавлением всех событий, которые были получены за дельту времени с момента формирования данной проекции (в нашем случае это Reservation).
На данном этапе мы должны определить какие именно физические склады будут принимать участие в доставке, и для этих складов уменьшить значение количества отгруженных товаров.
За эту часть ответсвенный алгоритм, который приймет решение о выборе складов (будет описан в следующей статье). Например, алгоритм принял решение, что делевле всего для продавца будет отправить 30 товаров со складов Source С и Source B.
Соответсвенно количество товара SKU-1 на складах должно измениться:
А открытая резервация для SKU-1 на Stock A удалена (либо изменить статус этой резервации на Complete), чтобы она больше не участвовала в вычислениях. После этого должна создастся команда на обновление индекса Stock Item, которая также может быть асинхронной.
Данная статья является второй статьей в цикле «Система управления складом с использованием CQRS и Event Sourcing» в рамках которого будет рассмотрен сбор требований, проектирование и разработка системы управления складом на примере Magento 2.
Открытый проект, где ведется разработка, и куда привлекаются инженеры из сообщества, а также где можно ознакомиться с текущим состоянием проекта и документацией, доступен по ссылке [7].
Автор: maghamed
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/260804
Ссылки в тексте:
[1] части 1: https://habrahabr.ru/post/331654/
[2] CQS: https://en.wikipedia.org/wiki/Command%E2%80%93query_separation
[3] EAV (entity-attribute-value): https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model
[4] видео: https://www.youtube.com/watch?v=V24L4a9FFps
[5] слайды: https://www.slideshare.net/maghamed/ommand-query-responsibility-segregation-cqrs
[6] SKU: https://en.wikipedia.org/wiki/Stock_keeping_unit
[7] по ссылке: https://github.com/magento-engcom/magento2/projects
[8] Источник: https://habrahabr.ru/post/333678/
Нажмите здесь для печати.