От монолита к модулям: как отстроены бизнес-процессы склада Lamoda
Привет! Меня зовут Евгений Рябышев, я разработчик в одной из команд направления 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 или автоматическую упаковочную машину).
После этого мы размещаем товары на паллеты. Начинается подпроцесс 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 или сервис авторизации, могут меняться. Допустим, завтра мы решим использовать другую авторизацию или другие конвейерные линии. В этом случае легко сможем отказаться от нынешних, напишем новые сервисы, и эти изменения никак не затронут бизнес-логику.
Вся наша схема не имеет проблем с определенными транзакциями. Сейчас мы движемся в этом направлении и планируем строить наши сервисы и модульную архитектуру.