Привет! Меня зовут Евгений Рябышев, я разработчик в одной из команд направления Warehouse Management System (WMS) компании Lamoda. Я занимаюсь тем, что автоматизирую склад. В этой статье расскажу, как мы строим нашу модульную архитектуру.


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



Складские бизнес-процессы: от поступления товара до отправки заказчику


В настоящий момент WMS — это монолитная архитектура, которая поставляется единым .ear-файлом и разворачивается в контейнеры приложения в Wildfly. Она позволяет управлять тремя основными бизнес-процессами на складе.


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



Тут же находится бизнес-процесс CIC (Central Inbound Clearing). Он позволяет сотрудникам склада решать проблемы во время приемки товаров. Например, на товаре нет наклейки, и они перепечатывают ее, или товар не соотносится с картинкой, к которой он привязан.


Outbound — исходящий поток товаров. Когда пользователь делает заказ в Lamoda, все начинается с Order Management. Это подпроцесс, который отвечает за старт сборки заказов.



После того как работники склада собрали товары в заказ, начинается упаковка. Мы используем три способа:


  • Полуавтоматическая упаковка Item Sorter.
  • Ручная упаковка через minibatch. Это рабочее место с компьютером и столом, где находится упаковочный материал.
  • Автоматическая упаковка через АВМ (Auto Bagging Machine или автоматическую упаковочную машину).

Процесс ручной упаковки с помощью minibatch


После этого мы размещаем товары на паллеты. Начинается подпроцесс Palletising: заказы отправляются к месту назначения. Тут же есть подпроцесс СОС (Central Outbound Clearing). Он используется для аналогичных целей, что и Central Inbound Clearing, но только на этапе отгрузки.


Stock — последний основной бизнес-процесс.



В нем есть три подпроцесса.


  • Putaway занимается размещением товаров на складе.
  • Picking отвечает за сборку товаров для заказа и для внутреннего аудита. Например, когда менеджеры хотят проверить срок годности косметики.
  • Inventory (инвентаризация). Он нужен, чтобы проводить частичную или полную инвентаризацию на складе.

Архитектура WMS: из чего она состоит



Первый компонент — Printing service, который отвечает за распечатывание этикеток. Они расклеиваются везде: на паллеты, товары, контейнеры, рабочие станции — в общем, на все сущности, которыми управляет WMS. Следовательно, этикетка позволяет сотруднику склада понять, с чем сейчас он имеет дело.



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


Следующий компонент — это фронтенд-клиент. Его можно разделить на два подтипа: клиент для рабочих станций, где происходит упаковка, и клиент для десктоп, написанный на Angular. Последний нужен, чтобы пользователи общались с нашей WMS.


Еще есть мобильный клиент. Подробнее о нем рассказывали в одной из наших статей. В основном он нужен пользователям, которые не сидят за компьютером, а постоянно находятся в движении.


И последний компонент — ESB (Enterprise Service Bus). Он позволяет общаться с внешними системами, обрабатывать и получать заказы, отправлять сведения о том, что мы обработали заказы.


Почему бы не перейти на микросервисы? А вот почему


Бизнес-процессы нашего склада тесно связаны между собой — это нормальная ситуация для монолитной архитектуры. Плюс под управлением WMS находится более чем 250 сущностей, где постоянно меняется их статус. И это для монолитной архитектуры также абсолютно приемлемо.


Недавно перед нами встала задача: мы должны были заменить подпроцесс Reject на абсолютно новый. Вытащить его из связанной логики было не так просто. Пришлось немного повоевать, но в итоге мы это сделали. И тут пришло понимание, что пора как-то инкапсулировать нашу логику.


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


Допустим, пришла поставка и пользователь сканирует последний товар из нее, а затем делает запрос на наш сервер. Состояние товара сразу же меняется на «принят после сканирования». Меняется состояние заказа, затем поставки и грузовика. После этого должен измениться стейт склада, так как товар стал доступен для заказа на сайте.


У нас есть связанность между бизнес-процессами Stock и Inbound. И если что-то пойдет не так, мы всегда сможем откатиться благодаря механизмам транзакций в базе данных. Так что, микросервисы нам не очень подходят.


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


Также мы рассматривали для модульной архитектуры OSGI-фреймворк, но для себя нашли в нем несколько минусов. Например, проблему с большим количеством зависимостей в разных модулях, которые могут конфликтовать между собой. К тому же, это не самая популярная технология, а связка spring и osgi тоже не очень распространена. Поэтому мы решили отказаться от него.


К какому решению пришли


Итак, у нас есть три основных бизнес-процесса. Значит, мы хотим иметь три сервиса, которые будут отвечать за каждый из них.



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



Идея такова: мы делим бизнес логику на 3 основных модуля, а технические процессы (Printing Service, Conveyor Service, Tote Service и Auth Service) делаем общими, независимыми сервисами. Выглядит достаточно логично, поскольку оборудование на складе может выходить из строя, не работать, модернизироваться, обновляться. Допустим, сейчас у нас один поставщик оборудования, завтра придет другой, и на складе будет совсем другая автоматизация. Нам важно, чтобы это никак не влияло на наши основные бизнес-процессы.



В итоге получается следующая схема: три основных бизнес-процесса и четыре вспомогательных сервиса. Самое прикольное, что до сих пор остается одна база данных. То есть мы не теряем наших преимуществ в транзакции, и у нас нет проблем с распределенными транзакциями.


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


Что происходит сейчас


  • Завершили внедрение сервиса авторизации, который был написан на базе Keycloak. Сейчас мы активно переписываем наш Printing Service с использованием второго SpringBoot и Kotlin на беке.


  • Выпустили новый клиент для мобильных устройств, переписав его с JSP на Android — WMS Mobile. Теперь у нас нативное приложение для мобильных устройств, которое тратит меньше энергии и имеет огромное количество возможностей.


  • Планируем выносить наш Conveyor service и уже настолько разогнались, что сейчас думаем, как имплементировать нашу логику для него.



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


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


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