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

Микромодуль — это небольшой самостоятельный участок приложения с одной понятной ответственностью, собственными правилами доступа и минимальным числом связей с остальным кодом. Он помогает вынести, например, расчёт доставки или проверку прав из большого компонента, но не требует отдельного контейнера, базы данных и команды эксплуатации. Ниже — практическое сравнение вариантов выделения для приложений, включая проекты на Django 5.x и похожих стеках.
Что считать микромодулем
Термин «микромодуль» не закреплён за одним стандартом. В этой статье под ним понимается логическая граница внутри одного приложения: за ней находится законченная возможность, а наружу выходит небольшой контракт — набор функций, классов или команд, которыми пользуются другие части системы.
Ключевой признак не размер файла. Модуль на 20 строк может быть плохим, если он напрямую меняет таблицы, настройки и глобальное состояние пяти подсистем. И наоборот, компонент на несколько сотен строк может оставаться удачным микромодулем, если он отвечает за один сценарий и скрывает свои детали.
Сноска. Контракт — это договор о том, что компонент принимает на вход, что возвращает и какие ошибки может выдать. В него входят не только типы аргументов, но и побочные эффекты: запись в базу, отправка события, вызов внешнего API.
Хорошая граница уменьшает число причин, по которым вам приходится менять один и тот же код. Если изменение правил скидки заставляет открывать пять несвязанных пакетов, модуль выделен неудачно.
Какие варианты есть у команды
Обычно спорят не о том, нужен ли модуль, а о степени его изоляции. Чем сильнее граница, тем выше цена: больше инфраструктуры, тестовых контуров и способов отказа. Поэтому начинать с микросервиса только из-за названия «микромодуль» опасно.
| Вариант | Граница | Что выигрываете | Цена и риск |
|---|---|---|---|
| Функция или класс | Внутри файла или пакета | Быстро выделяется, легко вызвать и протестировать | Слабая защита от прямого доступа к внутренностям |
| Пакет приложения | Отдельный каталог и публичный интерфейс | Понятная ответственность, удобное ревью, изоляция зависимостей | Нужно следить за импортами и не допускать циклов |
| Внутренняя библиотека | Версионируемый пакет внутри репозитория или организации | Повторное использование и отдельные тесты | Появляются версии, совместимость и процедура публикации |
| Микросервис | Отдельный процесс и сетевой API | Независимый деплой, масштабирование и отказ одного компонента | Сеть, наблюдаемость, ретраи, контракты, безопасность и стоимость эксплуатации |
Функция или класс
Выбирайте этот уровень, если логика используется в одном месте или не имеет самостоятельного жизненного цикла. Например, преобразование денежной суммы перед выводом в интерфейс не нуждается в отдельном пакете. Такая граница полезна для локального упрощения, но не решает проблему, когда один и тот же бизнес-сценарий расползается по нескольким слоям.
Признак удачного выделения — функция получает явные аргументы и не читает данные из глобального состояния. Если для её вызова нужно сначала установить пять переменных в другом модуле, вы просто спрятали связанность.
Пакет приложения
Пакет — основной кандидат на роль микромодуля в модульном монолите. В Django 5.x это может быть приложение с правилами работы с заказами, платежами или уведомлениями. Важно не название каталога, а направление зависимостей: интерфейс вызывает пакет заказов, пакет заказов не импортирует представления и не знает о конкретной HTML-странице.
Публичный интерфейс стоит ограничить несколькими точками входа. Остальные классы и вспомогательные функции считаются внутренними. В Python технически можно импортировать почти всё, поэтому дисциплина команды и ревью здесь важнее самого каталога.
Внутренняя библиотека
Библиотека оправдана, когда одна возможность нужна нескольким приложениям: например, единые правила формирования идентификаторов или клиент внутреннего сервиса. Но не выносите код только ради красивой структуры. Публикация пакета создаёт обязательство поддерживать совместимость, документацию и процесс обновления.
Особенно опасна библиотека с бизнес-логикой и прямым доступом к базе конкретного приложения. Она выглядит общей, но фактически связывает несколько систем с одной схемой данных. Такой компонент лучше оставить в одном приложении или сначала сформулировать настоящий независимый контракт.
Микросервис
Отдельный сервис нужен не для того, чтобы файл стал меньше. Его выбирают, когда компоненту требуется самостоятельный цикл поставки, отдельное масштабирование, изоляция отказов или другой режим владения данными. Вызов через HTTP или очередь добавляет задержку и новые сбои: тайм-аут, повторную доставку, недоступность зависимости и рассинхронизацию состояний.
Если команда пока не умеет отвечать, кто принимает алерты ночью, где лежат метрики и как откатывается контракт, превращать микромодуль в микросервис рано. Сначала выделите компонент внутри монолита и измерьте его реальные границы.
Как выбрать границу на живом проекте
- Назовите одну бизнес-возможность. Формулировка должна звучать как действие или правило: «рассчитать доставку», «создать возврат», «проверить лимит». «Общие утилиты» — плохое название, потому что в нём нет ответственности.
- Опишите входы и выходы. Укажите обязательные данные, результат, ошибки и побочные эффекты. Если компоненту нужны объекты из пяти разных подсистем, проверьте, не смешаны ли в нём несколько сценариев.
- Найдите владельца данных. Один модуль должен быть главным местом изменения своих правил. Остальные части приложения не должны обновлять его таблицы или внутренние структуры напрямую.
- Посчитайте связи. Посмотрите импорты, вызовы и записи в базу. Удобная граница уменьшает число направленных связей; если после выделения появились адаптеры, события и десятки прокси, выигрыш может быть мнимым.
- Выберите минимальную изоляцию. Начните с функции или пакета. Переводите компонент во внутреннюю библиотеку только при повторном использовании, а в отдельный сервис — при подтверждённой потребности в независимом запуске.
- Проверьте сценарий изменения. Возьмите реальную задачу из ближайшего спринта. Если изменение правил доставки проходит внутри одного пакета и не требует правок в платёжном коде, граница работает.
Что обычно ломается
Пакет превращается в общий склад
В него складывают константы, модели, отправку почты и запросы к внешним системам. Через несколько месяцев любой модуль зависит от этого каталога, а изменение одной функции вызывает каскад тестов. Решение — разделять код по бизнес-возможностям, а не по типам файлов вроде utils, helpers и common.
Внутренности становятся публичным API
Другие компоненты начинают импортировать классы из глубоких путей и обращаться к полям напрямую. После этого перестроить пакет уже нельзя без массовых изменений. Зафиксируйте несколько публичных точек входа и проверяйте на ревью, что новые вызовы идут через них.
Граница проведена по таблице, а не по правилу
Отдельный пакет «модели заказов» ещё не означает модуль заказов. Если расчёт цены находится в контроллере, а проверка статуса — в фоновой задаче, логика разнесена по техническим слоям. Границу лучше строить вокруг сценария и его инвариантов*, а не вокруг расширения файла.
Микросервис создаёт распределённый монолит
Компоненты разнесены по процессам, но каждый запрос требует синхронно вызвать четыре сервиса. При сбое одного из них падает весь пользовательский сценарий. До декомпозиции зафиксируйте допустимую задержку, политику повторов, владельца данных и способ деградации.
* Инвариант — правило, которое должно оставаться истинным после любой допустимой операции. Например, возврат не может одновременно иметь статус «завершён» и «ожидает подтверждения».
Когда какой вариант выбирать
Для нового сценария в одном приложении выбирайте пакет с узким интерфейсом. Для маленькой локальной операции — функцию или класс. Внутреннюю библиотеку вводите после появления второго реального потребителя, а не по прогнозу. Микросервис оправдан, когда независимый жизненный цикл важнее цены сетевого взаимодействия.
Есть и обратный сигнал: если пакет постоянно меняется вместе с другим пакетом, его граница подозрительна. Возможно, это один компонент, который разделили ради структуры каталогов. Если же он меняется редко, имеет собственные правила и разные команды выпускают его независимо, можно оценивать переход к библиотеке или сервису.
Что делать дальше
Выберите один часто меняющийся сценарий и нарисуйте его зависимости: кто вызывает код, какие данные он читает, где пишет результат и какие внешние системы трогает. Затем выделите пакет с одним публичным входом, добавьте тесты контракта и неделю наблюдайте за изменениями в репозитории.
Для продакшена измеряйте не количество каталогов, а число затронутых компонентов на задачу, время сборки и доставки, долю межмодульных вызовов и количество инцидентов после изменений. Инвалидация кеша и декомпозиция базы данных — отдельные темы: они не решаются одним переименованием пакета.
Источники: Martin Fowler — MonolithFirst, Microsoft Learn — Microservices architecture style
Частые вопросы
Микромодуль — это то же самое, что микросервис?
Сколько строк должен содержать микромодуль?
Нужно ли сразу выносить модуль в отдельный пакет или репозиторий?
Что делать, если после выделения появились циклические импорты?
Подойдёт ли этот подход для другой версии Django или другого фреймворка?
Какие проблемы проявятся в продакшене?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.