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

Микрофреймворки — это лёгкие веб‑фреймворки, которые концентрируются на базовом: принять запрос, направить его к нужному обработчику и вернуть ответ. В отличие от полнофункциональных фреймворков, они обычно не включают всё подряд (админку, ORM/слой БД, генераторы форм, фоновые задачи, потоки аутентификации). Вместо этого они дают небольшой, стабильный «ядро» и позволяют добавить только то, что действительно нужно вашему продукту.
Полнофункциональный фреймворк — как покупка уже меблированного дома: удобно и последовательно, но сложнее перестраивать. Микрофреймворк ближе к пустому, но структурно прочному пространству: вы решаете, какие комнаты, мебель и коммуникации будут.
Эта свобода — то, что мы называем кастомной архитектурой: система проектируется под нужды команды, домен и операционные ограничения. Проще говоря: вы выбираете компоненты (логирование, доступ к БД, валидацию, аутентификацию, фоновые процессы) и сами решаете, как они связаны, вместо того чтобы принимать предопределённый «единственно верный» путь.
Команды часто выбирают микрофреймворки, когда им нужно:
Мы сосредоточимся на том, как микрофреймворки поддерживают модульный дизайн: компоновка блоков, использование middleware и внедрение зависимостей без превращения проекта в научный эксперимент.
Мы не будем сравнивать фреймворки строка‑в‑строку и не будем утверждать, что микрофреймворки всегда лучше. Цель — помочь вам осознанно выбирать структуру и безопасно её эволюционировать по мере изменения требований.
Микрофреймворки работают лучше всего, когда вы относитесь к приложению как к набору деталей, а не к готовому дому. Вместо принятия навязанного стек‑мнения, начните с малого ядра и добавляйте функциональность только тогда, когда она окупается.
Практичное «ядро» обычно включает:
Этого достаточно, чтобы выпустить рабочий API‑эндпойнт или страницу. Всё остальное — опционально до появления конкретной причины.
Когда нужны аутентификация, валидация или логирование — добавляйте их как отдельные компоненты, лучше за чёткими интерфейсами. Это сохраняет архитектуру понятной: каждый новый элемент должен отвечать на вопросы «какую проблему он решает?» и «куда он подключается?».
Примеры модулей по принципу «добавляй только по необходимости»:
На ранних стадиях выбирайте решения, которые не загоняют вас в угол. Предпочитайте тонкие обёртки и конфигурацию вместо глубокой магии фреймворка. Если вы можете заменить модуль без переписывания бизнес‑логики — вы на верном пути.
Простое определение «готово» для архитектурных выборов: команда объясняет назначение каждого модуля, может заменить его за день‑два и протестировать отдельно.
Микрофреймворки остаются маленькими по дизайну, поэтому вы сами выбираете «органы» приложения, а не наследуете целое тело. Это делает кастомную архитектуру практичной: начните с малого и добавляйте части только по реальной необходимости.
Большинство приложений на микрофреймворках начинаются с роутера, который сопоставляет URL с контроллерами (или простыми обработчиками). Контроллеры можно организовать по фичам (billing, accounts) или по интерфейсу (веб vs API), в зависимости от предпочтений по поддержке кода.
Middleware обычно оборачивает поток запрос/ответ и является лучшим местом для сквозных задач:
Поскольку middleware компонуется, его можно применять глобально (всё логируется) или только для отдельных маршрутов (для /admin нужны строгие проверки).
Микрофреймворки редко навязывают слой данных, поэтому выбирайте тот уровень, который подходит вашей команде и нагрузке:
Хорошая практика — держать доступ к данным за репозиторием или сервисным слоем, чтобы смена инструмента не разнесла изменения по всем обработчикам.
Не всем продуктам нужны асинхронные процессы с самого начала. Когда нужны — добавьте job runner и очередь (отправка писем, обработка видео, вебхуки). Рассматривайте фоновые задания как отдельную «точку входа» в доменную логику, использующую те же сервисы, что и HTTP‑слой, а не дублирующую правила.
Middleware — это место, где микрофреймворки дают наибольшую отдачу: оно позволяет обрабатывать сквозные требования без раздутия каждого обработчика маршрута. Цель простая: держать обработчики сфокусированными на бизнес‑логике, а «проводку» — в middleware.
Вместо дублирования проверок и заголовков в каждом эндпойнте, добавьте middleware раз и навсегда. Чистый обработчик выглядит так: распарсить вход, вызвать сервис, вернуть ответ. Всё остальное — auth, логирование, дефолтная валидация, формирование ответа — может выполняться до или после.
Порядок — это поведение. Распространённая, читаемая последовательность:
Если сжатие работает слишком рано, оно может пропустить ошибки; если обработка ошибок идёт слишком поздно, есть риск утечки стек‑трейсов или возврата несовместимых форматов.
X-Request-Id и включайте его в логи.{ error, message, requestId }).Группируйте middleware по назначению (observability, безопасность, парсинг, формирование ответа) и применяйте в нужной области: глобально для действительно универсальных правил и групп‑маршрутов для областей типа /admin. Даёте каждому middleware понятное имя и документируйте ожидаемый порядок коротким комментарием возле настройки, чтобы будущие изменения не ломали поведение.
Микрофреймворк даёт тонкое «вход → выход» ядро. Всё остальное — доступ к БД, кеш, электронная почта, сторонние API — должно быть заменяемым. Здесь помогают IoC и DI, но не надо превращать кодовую базу в научный эксперимент.
Если фича нуждается в базе данных, легко создать её прямо внутри фичи (new database client here). Минус: место, где создаётся клиент, теперь жёстко связано с этой конкретной реализацией.
IoC переворачивает это: фича просит то, что ей нужно, а сборка приложения даёт это. Фича становится проще в повторном использовании и замене.
Внедрение зависимостей — это просто передача зависимостей внутрь, а не создание их внутри. В микрофреймворке это обычно делается на старте:
Большому DI‑контейнеру для этого не нужно. Начните с простого правила: создавайте зависимости в одном месте и пробрасывайте их вниз.
Чтобы компоненты были взаимозаменяемы, опишите «что вам нужно» как небольшой интерфейс, затем напишите адаптеры для конкретных инструментов.
Пример паттерна:
UserRepository (интерфейс): findById, create, listPostgresUserRepository (адаптер): реализует методы через PostgresInMemoryUserRepository (адаптер): реализует те же методы для тестовБизнес‑логика знает только про UserRepository, а не про Postgres. Смена хранилища — это вопрос конфигурации, не переписывания.
То же работает для внешних API:
PaymentsGateway (интерфейс)StripePaymentsGateway (адаптер)FakePaymentsGateway для локальной разработкиМикрофреймворки легко приводят к разбросу конфигурации по модулям. Этому нужно сопротивляться.
Поддерживаемый шаблон:
Это даёт цель: менять компоненты без переписывания приложения. Замена БД, API‑клиента или добавление очереди — небольшое изменение в слое проводки, а остальной код остаётся стабильным.
Микрофреймворки не навязывают «единственно верного» способа структуры. Вместо этого они дают маршрутизацию, обработку запрос/ответ и точки расширения — так что вы можете выбирать паттерны, подходящие вашей команде и продукту.
Знакомая «чистая и простая» схема: контроллеры решают HTTP‑вопросы, сервисы содержат бизнес‑правила, репозитории общаются с БД.
Хорошо подходит при простом домене, небольшой‑средней команде и желании иметь предсказуемые места для кода. Микрофреймворки естественно это поддерживают: маршруты мапятся в контроллеры, контроллеры вызывают сервисы, а репозитории подключаются через ручную композицию.
Гексагональная архитектура полезна, когда вы ожидаете, что система переживёт сегодняшние технические выборы — БД, message bus, UI. Микрофреймворки хорошо подходят, потому что «адаптерный» слой часто — это HTTP‑обработчики плюс тонкая трансляция в доменные команды. Порты — это интерфейсы в домене, адаптеры реализуют их (SQL, REST‑клиенты, очереди). Фреймворк остаётся на краю, а не в центре.
Если вы хотите ясности, как в микросервисах, но без операционных расходов, модульный монолит — хороший выбор. Вы держите одно развертываемое приложение, но разделяете его на фичевые модули (Billing, Accounts, Notifications) с явными публичными API.
Микрофреймворки упрощают это, потому что они не автоподключают всё: каждый модуль регистрирует свои маршруты, зависимости и доступ к данным, делая границы видимыми и труднее случайно пересечь их.
Во всех трёх подходах польза одна: вы сами устанавливаете правила — структуру папок, направление зависимостей и границы модулей — а микрофреймворк даёт маленькую, стабильную поверхность для подключения.
Микрофреймворки позволяют начать с малого и оставаться гибкими, но они не отвечают на главный вопрос: какую «форму» принять? Правильный выбор зависит не от технологий, а от размера команды, ритма релизов и того, насколько болезненна координация.
Монолит — один деплой, одна сборка, одно место для логов и дебага. Часто самый быстрый путь к рабочему продукту.
Модульный монолит — всё ещё один деплой, но с внутренним разделением на модули (пакеты, bounded contexts, feature‑папки). Часто лучший «следующий шаг» при росте кода — особенно с микрофреймворками, где модули очевидны.
Микросервисы дробят деплой на несколько сервисов. Это снижает связность команд, но умножает операционные затраты.
Разделяйте, когда граница уже реальна в работе:
Не разделяйте, если причина — только удобство («папка большая») или когда сервисы будут использовать одну и ту же БД. Это признак нестабильной границы.
API‑шлюз упрощает клиентов (единая точка входа, централизованная аутентификация/лимитирование). Минус — он может стать бутылочным горлышком и единой точкой отказа, если станет слишком «умным».
Общие библиотеки ускоряют разработку (валидация, логирование, SDK), но создают скрытую связность. Если несколько сервисов вынуждены апгрейдить одновременно — вы вернулись к распределённому монолиту.
Микросервисы добавляют постоянные расходы: больше пайплайнов деплоя, версионирование, discovery сервисов, мониторинг, трассировка, инцидент‑реакция и дежурства. Если команда не готова управлять этим, модульный монолит на базе микрофреймворков часто безопаснее.
Микрофреймворк даёт свободу, но поддерживаемость нужно проектировать. Цель — сделать кастомные части лёгкими для поиска, замены и сложения неправильного использования.
Выберите структуру, которую можно объяснить за минуту и проверять в кодревью. Практичный разрез:
app/ (composition root: свяжет модули)modules/ (бизнес‑возможности)transport/ (HTTP‑маршруты, маппинг запрос/ответ)shared/ (кросс‑функциональные утилиты: config, логирование, типы ошибок)tests/Поддерживайте консистентные имена: папки модулей — существительные (billing, users), точки входа предсказуемы (index, routes, service).
Обращайтесь к каждому модулю как к маленькому продукту с явными границами:
modules/users/public.ts)modules/users/internal/*)Избегайте «reach‑through» импортов вроде modules/orders/internal/db.ts в другом модуле. Если другой модуль нуждается в этом, поднимите это в публичный API.
Даже крошечным сервисам нужна базовая видимость:
Поместите это в shared/observability, чтобы все обработчики использовали одинаковые конвенции.
Сделайте ошибки предсказуемыми для клиентов и удобными для людей. Определите единый формат ошибки (например, code, message, details, requestId) и одну стратегию валидации (схема для каждого эндпойнта). Централизуйте сопоставление внутренних исключений в HTTP‑ответы, чтобы обработчики оставались сфокусированы.
Если цель — двигаться быстро, сохраняя стиль микрофреймворка, Koder.ai может помочь как инструмент скелетирования и итерации, а не как замена хорошего дизайна. Вы можете описать границы модулей, стек middleware и формат ошибок в чате, сгенерировать рабочую базу (например, React‑фронтенд с бэком на Go + PostgreSQL) и затем осознанно доработать проводку.
Две особенности особенно полезны для кастомной архитектуры:
Поскольку Koder.ai поддерживает экспорт исходников, вы сохраняете владение архитектурой и эволюционируете репозиторий так же, как с ручной настройкой.
Системы на микрофреймворках могут выглядеть «собранными вручную», поэтому тестирование — это не про единый фреймворк, а про защиту швов между кусками. Цель — уверенность без необходимости запускать полный end‑to‑end при каждом изменении.
Начните с unit‑тестов для бизнес‑правил (валидация, ценообразование, логика прав), они быстрые и локализуют ошибки.
Затем добавьте небольшое число ценных интеграционных тестов, покрывающих проводку: routing → middleware → handler → граница персистентности. Они ловят тонкие баги при объединении компонентов.
Middleware — место, где спрятано много сквозной логики (auth, logging, rate limiting). Тестируйте его как pipeline:
Для обработчиков тестируйте публичную HTTP‑форму (коды статусов, заголовки, тело ответа), а не внутренние вызовы. Это делает тесты стабильными при рефакторинге.
Меняйте реальные зависимости на фейки:
Когда несколько команд зависят от API, добавляйте контрактные тесты, фиксирующие ожидания request/response. Провайдер‑тесты помогут не ломать потребителей даже при эволюции внутренней архитектуры.
Микрофреймворки дают свободу, но свобода не равна ясности. Основные риски проявляются позже — при росте команды и кода, когда «временные» решения становятся постоянными.
При отсутствии встроенных конвенций две команды могут реализовать одну и ту же фичу по‑разному (маршрутизация, обработка ошибок, форматы ответов, логирование). Это замедляет ревью и усложняет онбординг.
Простые защитные меры: короткий «service template» (структура проекта, именование, формат ошибок, поля логов) и стартер‑репозиторий с линтами.
Проекты часто стартуют чисто, затем накапливают utils/, который тихо становится вторым фреймворком. Когда модули делят хелперы, константы и глобальное состояние, границы размываются и изменения ломают неожиданно.
Предпочитайте явные общие пакеты с версионированием или держите шаринг минимальным: типы, интерфейсы и проверенные примитивы. Если хелпер зависит от доменных правил — он, вероятно, должен жить в доменном модуле, а не в utils.
При самостоятельной проводке аутентификации, авторизации, валидации и лимитирования легко забыть маршрут или валидировать только «хорошие» входы.
Централизуйте безопасные настройки: безопасные заголовки, единые проверки auth и валидация на границе. Добавьте тесты, которые гарантируют защиту важных эндпойнтов.
Непланируемое наслоение middleware добавляет накладные расходы — особенно если несколько middleware парсят тела, обращаются к хранилищу или сериализуют логи.
Держите middleware маленьким и измеряемым. Документируйте стандартный порядок и проверяйте новые middleware на стоимость. При подозрении на перегруз — профилируйте запросы и убирайте избыточность.
Микрофреймворки дают опции — но опции требуют процесса принятия решения. Цель — не найти «лучшую» архитектуру, а выбрать форму, которую команда сможет строить, эксплуатировать и менять без драмы.
Перед решением «монолит» или «микросервисы», ответьте:
Если сомневаетесь, дефолт — модульный монолит на микрофреймворке. Он сохраняет границы и остаётся простым в доставке.
Микрофреймворки не навязывают консистентность, поэтому определите её сразу:
Одностраничный «service contract» в /docs часто достаточен.
Начните с кросс‑функциональных частей, нужных везде:
Рассматривайте их как общие модули, а не копипасту.
Архитектуры меняются вместе с требованиями. Каждый квартал проверяйте, где развёртывание замедляется, какие части по‑разному масштабируются и что чаще всего ломается. Если одна зона становится бутылочным горлышком — её кандидат на выделение в отдельный сервис, а не весь проект.
Микрофреймворк‑проект редко стартует «полностью спроектированным». Обычно он начинается с одного API, одной команды и жёсткого дедлайна. Ценность проявляется по мере роста: фичи добавляются, людей становится больше, и архитектура должна тянуть, не ломаясь.
Старт с минимального сервиса: маршрутизация, парсинг запросов и один адаптер БД. Логика живёт близко к эндпойнтам, потому что так быстрее выпустить.
С появлением auth, payments, notifications и reporting их выносят в модули (папки или пакеты) с чёткими публичными интерфейсами. Каждый модуль владеет моделями, бизнес‑правилами и доступом к данным, открывая только то, что нужно другим.
Логирование, проверки auth, лимитирование и валидация мигрируют в middleware, чтобы все эндпойнты вели себя одинаково. Поскольку порядок важен — его следует документировать.
Документируйте:
Рефакторьте, когда модули начинают делиться слишком многими внутренностями, время сборки заметно растёт или «малые изменения» требуют правки по нескольким модулям.
Делите на сервисы, когда команды блокируют друг друга из‑за общего деплоя, части требуют разного масштаба или граница интеграции уже ведёт себя как отдельный продукт.
Микрофреймворки хороши, когда хочется формировать приложение вокруг домена, а не вокруг предписанного стека. Они особенно полезны для команд, которые ценят ясность больше удобства: вы готовы выбирать (и поддерживать) ключевые строительные блоки в обмен на поддерживаемую кодовую базу по мере роста требований.
Гибкость окупается, если её защищать простыми привычками:
Начните с двух лёгких артефактов:
И наконец, фиксируйте решения по мере их принятия — даже короткие заметки помогают. Держите страницу «Architecture Decisions» в репозитории и периодически её пересматривайте, чтобы вчерашние быстрые решения не превратились в сегодняшние ограничения.
Микрофреймворк фокусируется на самом необходимом: маршрутизации, обработке запроса/ответа и базовых точках расширения.
Полнофункциональный фреймворк обычно включает много «батареек в комплекте» (ORM, аутентификацию, админку, формы, фоновые задачи). Микрофреймворки меняют удобство на контроль — вы добавляете только то, что действительно нужно, и сами решаете, как компоненты связаны.
Микрофреймворки подходят, когда вы хотите:
«Минимально полезное ядро» обычно включает:
Начните с этого, выпустите один эндпойнт, а модули добавляйте только когда они явно окупаются (аутентификация, валидация, наблюдаемость, очереди).
Middleware хорош для сквозных задач, применимых шире всего, например:
Держите обработчики маршрутов сфокусированными на бизнес-логике: parse → вызвать сервис → вернуть ответ.
Порядок меняет поведение. Частая надёжная последовательность:
Задокументируйте порядок рядом с кодом настройки, чтобы будущие изменения не ломали ответы или безопасность.
IoC (инверсия управления) означает, что ваш бизнес‑код не создаёт собственные зависимости «на месте» (не «идёт за покупками»). Вместо этого сборка приложения предоставляет ему то, что нужно.
На практике: создайте клиент БД, логгер и HTTP‑клиенты при старте и передайте их в сервисы/обработчики. Это снижает жёсткую связность и облегчает тестирование и замену реализаций.
Нет. Большинство преимуществ DI можно получить простой композицией:
Добавляйте контейнер DI только если граф зависимостей становится неудобным для ручного управления — не начинайте сразу с лишней сложности.
Помещайте хранилище и внешние API за небольшими интерфейсами (портами), затем реализуйте адаптеры:
UserRepository (интерфейс) с методами findById, create, listPostgresUserRepository для продакшнаПрактичная структура, сохраняющая видимость границ:
app/ — composition root (сборка модулей)modules/ — функциональные модули (доменные возможности)transport/ — HTTP-маршрутизация и маппинг запрос/ответПриоритезируйте быстрые модульные (unit) тесты для бизнес‑правил, затем добавьте меньше, но ценных интеграционных тестов, которые прогоняют весь конвейер (маршрутизация → middleware → обработчик → граница персистентности).
Используйте DI/фейки, чтобы изолировать внешние сервисы, тестируйте middleware как pipeline (проверять заголовки, побочные эффекты и блокировки). Если над API работают несколько команд, добавьте contract‑тесты, чтобы не ломать клиентов.
InMemoryUserRepository для тестовОбработчики/сервисы зависят от интерфейса, а не от конкретной реализации. Смена БД или провайдера платежей становится конфигурационной/проводочной задачей, а не переписыванием логики.
shared/ — конфиг, логирование, типы ошибок, наблюдаемостьtests/Обязывайтесь к публичным API модулей (например, modules/users/public.ts) и избегайте «проходных» импортов во внутренние части других модулей.