Почему кастомный модуль для Medusa.js ломается при обновлении
Обновление ядра — самый частый момент, когда кастомный модуль для Medusa.js перестаёт работать. Причина не в самом обновлении, а в том, как модуль был встроен: если он правит файлы ядра напрямую или держит собственные миграции в общей цепочке, апдейт затирает изменения или останавливается на конфликте миграций. Разбираем, как это закрывается архитектурно.
Первая категория сбоев — конфликт миграций. Модуль добавляет свои таблицы в ту же последовательность, что и ядро, и при обновлении порядок применения меняется: часть миграций уже прошла, часть ждёт, а схема оказывается в промежуточном состоянии. База поднимается, но часть запросов падает на отсутствующих колонках. Лечится это изоляцией: миграции модуля живут отдельным набором и применяются независимо от ядра.
Вторая — перезапись расширений. Правки в сервисах, подписчиках событий или резолверах, сделанные поверх ядра, при обновлении исчезают: файлы заменяются новыми версиями, и логика модуля молча пропадает. Здесь работает только расширение через штатные точки — подписки на события, собственные сервисы, обёртки над существующими, — тогда обновление ядра не трогает код модуля.
Третья и самая дорогая — потеря связей сущностей. Модуль ссылается на сущности ядра по идентификаторам, а при обновлении меняется структура или каскад удаления: связи рвутся, появляются висячие записи, отчёты и корзины показывают неполные данные. Обнаруживается это не сразу, а через недели, когда расхождение уже накопилось. Защита — явные внешние ключи с продуманной политикой удаления и проверки целостности в миграциях.
Мы закладываем эти три сценария на этапе проектирования модуля, а не после первого апдейта: изолированные миграции, расширение через штатные точки, контроль ссылочной целостности. Дальше — как это выглядит в архитектуре и на реальных примерах.
Что даёт правильно изолированный модуль
Границы модуля и скорость сборки
Изоляция начинается с того, что модуль объявляет свои зависимости явно и не тянет их из общего контекста приложения. Пересборка при правке одной предметной области перестаёт запускать компиляцию всего проекта: собирается только затронутый модуль и то, что от него зависит. На типовом проекте это сокращает цикл сборки с минут до десятков секунд, а горячая перезагрузка при разработке укладывается в пару секунд — правки перестают накапливаться в очередь.
Время восстановления после сбоя
Когда модуль падает, отказ локализован: остальные части приложения продолжают обслуживать запросы, а не уходят в общий отказ. Восстановление сводится к перезапуску одного модуля, а не всего сервиса, — время возврата в рабочее состояние измеряется секундами вместо минут. Отдельно уходит класс отказов, где падение второстепенной функции утягивало за собой оформление заказа или оплату.
Доля ручных операций
Конфигурация, миграции и регистрация модуля описываются декларативно, поэтому подключение новой предметной области не требует правок в ядре и ручной синхронизации окружений. Обновление модуля — это замена версии и прогон миграций, а не набор ручных шагов по переносу данных. В результате доля операций, которые инженер делает руками при выкатке, падает, а вместе с ней — вероятность ошибки по невнимательности.
Что это даёт на длинной дистанции
Модуль можно развивать и тестировать отдельно: свои проверки, свои фикстуры, предсказуемая зона ответственности. Новый инженер входит в задачу быстрее, потому что видит границы, а не разбирает связанный клубок. Именно эта предсказуемость и есть основной эффект изоляции — она окупается на каждой следующей доработке.
Способы расширения: сравнение подходов
В Medusa.js расширение функциональности почти всегда можно сделать тремя путями, и выбор между ними определяет, как система будет вести себя при обновлениях и росте нагрузки. Встроенное расширение живёт внутри ядра и переиспользует его сервисы напрямую, отдельный модуль выносит логику в изолированный пакет со своим жизненным циклом, а внешний сервис выносит её за пределы приложения целиком. Ниже — сравнение по критериям, которые реально влияют на поддержку.
| Вариант | Когда подходит | Ограничения подхода |
|---|---|---|
| Встроенное расширение | Логика тесно связана с заказами, корзиной или каталогом и должна читать их состояние в той же транзакции | Обновление ядра требует ручного переноса правок; растёт связность, тесты замедляются |
| Отдельный модуль | Нужна переиспользуемая доменная логика: интеграции, расчёты, собственные сущности | Требует явного описания зависимостей и миграций; межмодульные вызовы надо проектировать заранее |
| Внешний сервис | Нагрузка пиковая, логика на другом стеке или уже есть готовый сервис | Появляется сетевой вызов: нужны идемпотентность, повторные попытки и обработка частичных отказов |
Разница проявляется на горизонте: встроенное расширение быстрее собрать, но каждое обновление ядра превращается в ревизию правок. Отдельный модуль дороже на старте — зато обновляется независимо и покрывается тестами изолированно. Внешний сервис даёт свободу по стеку, но переносит часть ответственности за согласованность данных на инфраструктуру.
На практике выбор редко бывает «или-или»: ядро заказа остаётся встроенным, расчёты и интеграции уходят в модуль, а тяжёлые операции — во внешний сервис. Опишите задачу — вернёмся с оценкой и планом, какой вариант закроет её без лишней связности.
Этапы проектирования и внедрения модуля
Работа над модулем начинается не с кода, а с разбора того, что уже есть в проекте. На этом этапе важно понять, где именно проходит граница между штатным поведением платформы и вашей логикой — иначе модуль начнёт дублировать встроенные механизмы и потянет за собой лишние зависимости.
- Аудит кодовой базы. Смотрим структуру проекта, версию ядра, набор установленных модулей и то, как в них организованы сервисы и подписки на события. Фиксируем точки расширения, которые уже используются, и те, что остались нетронутыми.
- Проектирование схемы данных. Определяем сущности модуля, их связи с базовыми (товар, заказ, покупатель), правила миграций и стратегию отката. Отдельно проговариваем, какие поля обязательны, а какие заполняются по событию.
- Настройка модуля. Собираем конфигурацию: регистрация в основном приложении, параметры подключения, права доступа, переменные окружения для разных сред. Модуль должен подниматься и в разработке, и в проде без ручных правок.
- Реализация и проверка интеграции. Пишем логику, покрываем ключевые сценарии автотестами и проверяем стыковку с существующими сервисами — оформление заказа, расчёт доставки, обмен с внешними системами.
- Нагрузочная проверка. Прогоняем сценарии под нагрузкой, близкой к боевой, и смотрим, не деградируют ли запросы к каталогу и корзине после подключения модуля.
- Передача и документация. Отдаём описание архитектуры, схему данных, инструкцию по развёртыванию и чек-лист типовых сбоев. Команда заказчика должна уметь обновлять модуль без нас.
Каждый этап заканчивается артефактом, который можно проверить: карта точек расширения, схема данных, конфигурация, отчёт о тестах. Так на любом шаге видно, что уже сделано и что осталось, — и риск скрытых доработок в конце проекта уходит.
Артефакты, которые остаются у команды заказчика
Работа с кастомным модулем для Medusa не заканчивается деплоем — после сдачи у команды заказчика должен остаться комплект, по которому модуль можно развивать, обновлять и передавать новым инженерам без нас. Ниже — что именно фиксируется в передаваемом пакете.
- Конфигурация модуля — рабочие файлы настроек с описанием каждого параметра: какие значения обязательны, какие зависят от окружения, что меняется при подключении нового провайдера оплаты или доставки.
- Схема данных — описание сущностей, связей и миграций: какие таблицы создаёт модуль, как они соотносятся с ядром, что произойдёт при откате миграции.
- Политики доступа — матрица ролей и прав: кто из администраторов видит и редактирует данные модуля, какие операции доступны через API, где стоят ограничения на уровне запросов.
- Документация — инструкция по развёртыванию с нуля, описание точек расширения и примеры вызовов, чтобы новую логику можно было добавить без чтения всего исходного кода.
- Регламент обновления — порядок действий при выходе новой версии ядра: какие проверки прогоняются, как тестируется совместимость модуля, в какой момент обновление откатывается.
- Набор автотестов — покрытие ключевых сценариев модуля: оформление заказа, расчёт стоимости, синхронизация с внешними сервисами. Тесты показывают, что обновление не сломало поведение.
- Схема интеграций — карта внешних сервисов, с которыми связан модуль, с указанием, какие данные куда передаются и что происходит при недоступности сервиса.
Каждый пункт — не формальность ради отчёта, а ответ на конкретный риск: смена подрядчика, обновление ядра, подключение нового канала продаж. Если команда заказчика открывает документацию и по ней воспроизводит окружение с нуля — пакет собран правильно.
Кейс: модуль лояльности для интернет-магазина
Что было до внедрения
Интернет-магазин на Medusa.js с накоплением баллов лояльности прямо в основном приложении: логика начисления, сгорания и списания жила в контроллерах заказов и в фоновых заданиях. Любое изменение правил — правка ядра, полный прогон тестов и пересборка всего приложения. Сборка занимала около 12 минут, а релиз правил лояльности приходилось согласовывать с командой, отвечавшей за оформление заказа, — очередь на выкатку растягивалась на дни.
Параллельно накопились ручные операции: менеджеры вручную пересчитывали баллы при возвратах, отменах и частичных отгрузках. На каждую сотню заказов приходилось 8–10 ручных корректировок, и именно на них возникала основная доля расхождений в балансах клиентов.
Что изменили
Вынесли всю логику лояльности в отдельный кастомный модуль: собственные таблицы для балансов и транзакций, сервис начисления с явными правилами, подписки на события заказа вместо правок контроллеров. Основное приложение перестало знать о деталях программы — оно только публикует события, а модуль решает, сколько баллов начислить, списать или вернуть.
Правила сгорания и начисления вынесли в конфигурацию модуля, а не в код: изменение условий больше не требует пересборки ядра. Отдельно закрыли идемпотентность операций — повторная обработка одного и того же события не задваивает баллы, что сняло целый класс расхождений при сбоях очереди.
Что стало
Время сборки приложения сократилось с 12 до 4 минут — правки лояльности больше не тянут за собой пересборку всего проекта. Ручные корректировки баллов упали с 8–10 на сотню заказов до единичных случаев в месяц. Класс отказов «задвоенное начисление при повторной обработке события» ушёл полностью: операции стали идемпотентными, а расхождения в балансах теперь ловятся на этапе тестов, а не в поддержке.
Модуль изолирован, поэтому его можно развивать отдельным потоком: подключать новые уровни, акции и партнёрские начисления, не трогая оформление заказа.
Чем отличается кастомный модуль от плагина?
Плагин в Medusa — это надстройка над уже существующим контуром: он подписывается на события, добавляет маршруты, расширяет сервисы, но живёт внутри ядра и наследует его жизненный цикл. Кастомный модуль — самостоятельная единица с собственными сущностями, миграциями, сервисами и точками подключения. Разница проявляется не в объёме кода, а в уровне изоляции: плагин делит пространство с ядром, модуль держит свою границу и общается с остальным приложением через явные интерфейсы.
Отсюда следует главное практическое расхождение — поведение при обновлениях. Плагин цепляется за внутренние хуки и структуры, поэтому мажорное обновление платформы регулярно ломает совместимость: приходится переписывать обработчики, проверять порядок загрузки, ловить конфликты зависимостей. Модуль обновляется отдельно от ядра — если контракт на стыке не менялся, апгрейд проходит без правок в бизнес-логике. Это и определяет стоимость поддержки: у плагина она растёт вместе с частотой обновлений платформы, у модуля остаётся предсказуемой и сосредоточена в одном месте.
Типовые сценарии расходятся по той же границе. Плагин уместен, когда нужно добавить интеграцию с внешним сервисом, расширить существующую сущность полем или повесить обработчик на событие — точечное вмешательство без своей модели данных. Кастомный модуль выбирают, когда появляется отдельная предметная область: программа лояльности, складской учёт, биллинг, сложные правила ценообразования. Такая логика получает свои таблицы, свои миграции и свой набор тестов, а не растворяется в ядре.
Есть и промежуточный путь: начать с плагина и вынести код в модуль, когда он обрастает состоянием и связями. Мы оцениваем не «модно ли делать модуль», а где пролегает граница домена — от этого зависит, во сколько обойдётся следующее обновление платформы.
Частые технические сомнения при выборе подрядчика
Кастомный модуль в Medusa — это изолированный пакет, который подключается к ядру через механизм модулей и не трогает его исходники. Поэтому главный вопрос не «сработает ли», а «как это переживёт обновления и смену подрядчика». Ниже — то, что чаще всего проверяют на нашей стороне до старта.
Как модуль поведёт себя при обновлении ядра?
Мы не патчим ядро и не форкаем его — вся логика живёт в отдельном модуле с явно объявленными зависимостями. При переходе на актуальную ветку обновляется ядро, а модуль проходит проверку совместимости отдельно: контракты, точки расширения, миграции. Если версия ядра меняет поведение, это видно на тестах, а не в продакшене.
Что с миграцией уже накопленных данных?
Схему данных модуля описываем миграциями с версионированием: каждая миграция обратима, накат идёт на копии базы до продакшена. Существующие заказы, товары и связи переносятся скриптами с проверкой целостности — сверяем количество записей и ключевые связи до и после. Откат возможен на любом шаге.
Как проверяется, что модуль не ломает магазин?
Покрываем тестами критичные пути: оформление заказа, расчёт доставки, синхронизацию с внешними системами. Перед выкатом модуль гоняем на копии боевых данных — так ловим конфликты с существующими расширениями. Отдельно проверяем поведение под нагрузкой, чтобы новый код не просадил время ответа каталога и корзины.
Что остаётся у нас после сдачи?
Передаём не только код: схему архитектуры, описание точек расширения, инструкцию по развёртыванию и обновлению, набор тестов и журнал миграций. Проводим разбор с вашей командой — чтобы модуль можно было развивать без нас. Если команды нет, берём поддержку на себя.
Эти четыре пункта закрывают основную часть рисков. Опишите задачу — вернёмся с оценкой и планом.
Обсудим вашу задачу и вернёмся с планом
Чтобы оценка была предметной, а не «плюс-минус квартал», предварительный разбор идёт по вашей текущей кодовой базе и данным магазина. Дальше фиксируем план работ и сроки по этапам — так видно, что делается первым и где точки проверки.
- Короткий разговор по задаче: какие процессы модуль закрывает, что уже работает и что мешает — со слов вашей команды и владельца продукта.
- Доступ к репозиторию и конфигурации Medusa: смотрим версию ядра, структуру модулей, кастомизации ядра и подписки на события.
- Разбор схемы данных: сущности, связи, миграции, объёмы — от этого зависит, будет модуль отдельным пакетом или расширением существующего.
- Проверка интеграций: платёжные провайдеры, склад, доставка, учётные системы, очереди — что модуль должен вызывать и что вызывать его.
- Согласование границ: что входит в модуль, что остаётся в ядре, какие интерфейсы фиксируем как публичные.
- Оценка сроков по этапам: проектирование, реализация, миграции, тесты, выкатка — с отдельным запасом на несовместимости.
- План выката и отката: как модуль включается на стенде, как проверяется на боевых данных и как отключается без потери заказов.
Что нужно от вас для старта: доступ к репозиторию и тестовому окружению, описание процессов, которые модуль закрывает, и контакт инженера, который ведёт проект со вашей стороны. Если части данных нет — разбор всё равно возможен, просто часть оценок пойдёт с допущениями, и мы это отметим прямо в плане.
Опишите задачу — вернёмся с оценкой сроков и планом работ по этапам, с явно отмеченными рисками и точками проверки.







