В поисках баланса в backend-архитектуре
- суббота, 6 июня 2026 г. в 00:00:13
Эта статья для всех, кому интересна архитектура backend‑систем, но я понимаю, что для джунов она может быть немного перегружена терминами. Я не жду, что всем понравится такой взгляд, и как раз открыт к обсуждению.
Я попытался представить не академическую доктрину, а её переход в реальность: туда, где красивые архитектурные шаблоны сталкиваются с производительностью, сроками, legacy, ограничениями команды и конкретными production‑задачами. Главная мысль статьи — попробовать описать пространство где‑то посередине между архитектурной чистотой и производительностью, не превращая ни одну из сторон в абсолют.
За годы работы бэкенд‑разработчиком мне довелось проектировать и поддерживать самые разные системы. Как и многие, в поисках оптимальных решений я часто бросался из одной крайности в другую.
Проблема в том, что большинство доступных материалов в нашей индустрии написаны в менторском стиле: «Вот вам единственно верное решение, нужно писать строго так и никак иначе». С другой стороны — куча статей, где вообще нет системного подхода, а показаны лишь разрозненные куски кода для решения сиюминутных задач.
Мне долго не хватало комплексного, но при этом гибкого разбора — без перегибов и попыток объявить какую‑то одну методологию «священной коровой». Поэтому я решил структурировать собственный опыт и собрать этот материал в одну цельную статью. Это не истина в последней инстанции, а попытка посмотреть на архитектуру бэкенда как на живой, пластичный организм.
В этой статье мы пройдём путь от философии до практического каркаса: сначала поговорим о крайностях в архитектуре, затем разберём production‑компромиссы, после этого перейдём к идее Баланса и посмотрим, как эта идея может выражаться на уровне framework‑а.
Давайте начнём с компромиссов, из которых и состоит наша работа.
Если посмотреть на современные подходы к разработке, мы увидим две противоположные стороны весов.
Здесь всё красиво, стерильно и правильно. Код разложен по слоям, домены изолированы, зависимости строго контролируются.

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

Простой пример: вместо того чтобы тащить данные через слои, репозитории и маппинг, можно написать один сложный raw SQL‑запрос. База сама сделает всю тяжёлую работу — соберёт, отфильтрует и склеит данные, и вернёт уже готовый ответ, который можно почти без обработки отдать на фронт. Производительность заметно выше: меньше запросов, меньше нагрузки на приложение, программисту не нужно дополнительно собирать результат в коде.
Но у этого есть цена. Во‑первых, нужно хорошо знать SQL — такие запросы писать сложнее. Во‑вторых, архитектурно становится неудобно: бизнес‑логика расползается по запросам, системой труднее управлять, а изменения становятся рискованными. Выигрываешь в скорости — проигрываешь в управляемости.
Свою лепту вносит и программирование с ИИ: код выглядит красиво и даже работает, пусть и с багами. Но когда объём растёт, следить за всем становится трудно.
Плюсы: Максимальное быстродействие на старте, минимальный оверхед на маппинг и абстракции. А с ИИ всё это ещё быстрее — будто по волшебству: программист думает, а код пишется сам.
Минусы: Со временем пропадает предсказуемость, удобство поддержки и масштабируемость. Код стремительно превращается в спагетти, даже если в начале казалось, что «мы всё контролируем». Это похоже на бездумное использование goto — быстро сейчас, ментальный ад потом.
AI‑assisted development усиливает эту проблему. Раньше кодовая база превращалась в кашу из‑за спешки, слабой архитектуры или отсутствия дисциплины — медленно. С AI это ускоряется: код появляется быстрее, чем разработчик успевает проверить архитектурные последствия каждого изменения.
Учебники часто описывают сферический бэкенд в вакууме — некую усреднённую абстрактную систему. Но реальных систем «по учебнику» не существует. Каждая система уникальна. В ней всегда есть участки, которые невозможно красиво упаковать в каноническую архитектуру без заметной просадки по производительности.
Яркий пример из практики — дилемма получения данных.
При слишком буквальном следовании layered domain‑oriented подходу мы можем пытаться получать связанные сущности через отдельные репозитории и затем склеивать результат в коде на уровне приложения. Формально это выглядит красиво: каждый слой делает свою работу, зависимости контролируются, архитектурная симметрия сохраняется.
Но в реальности такой подход может быстро стать дорогим. Приложению приходится делать много обращений к базе, дополнительно собирать результат в памяти и тратить CPU на работу, которую база данных могла бы выполнить эффективнее. В худшем случае это превращается в классическую проблему N+1 запросов, где количество обращений к БД растёт вместе с количеством связанных сущностей.
Если мы хотим решить задачу эффективно, мы можем написать один специализированный SQL‑запрос с JOIN‑ами, CTE или другой projection‑логикой, который сразу собирает нужную read‑модель.
С точки зрения строгой архитектурной чистоты здесь возникает асимметрия: для записи у нас остаются доменные сущности, сервисы, репозитории и транзакции, а для чтения появляется отдельный optimized read‑path. Это уже не выглядит так «красиво», как каноническая layered‑схема, но в production такая асимметрия часто оказывается оправданной.
Главное — понимать цену этого решения. Мы выигрываем в производительности и гибкости read‑модели, но платим частью модульности, усложнением SQL и более высокой ценой поддержки. Это не ошибка архитектуры, если решение принято осознанно. Это нормальный production‑компромисс.

Здесь хорошо показывает себя еще один практический пример — endpoint, который отдает дерево категорий вместе с его item‑ами.
Даже с использованием расширения ltree в PostgreSQL это всё равно остается дорогостоящей операцией. Если хранить дерево через обычный parent_id, нужно искать детей выбранной категории рекурсией. С ltree немного легче, потому что в хранится путь то есть все ID родителей до этого элемента, и искать ветку дерева можно эффективнее. Но дальше всё равно нужно подключать данные item‑ов, собрать дерево и подготовить финальный ответ для клиента.
На этом этапе логично добавить кэш. Но кэшировать просто сырые данные не всегда продуктивно. Если кэшировать отдельно категории, отдельно item‑ы и отдельно связи, приложение всё равно каждый раз должно заново собирать окончательную структуру ответа в памяти.
Поэтому я пришел к идее кэшировать именно скомпилированные данные — то есть уже готовый результат, который должен вернуть endpoint.
Схема получается простой:
Endpoint сначала смотрит, есть ли готовый кэш.
Если кэш есть — сразу отдает готовые данные.
Если кэша нет — собирает данные из базы.
Формирует окончательную структуру.
Сжимает результат.
Сохраняет уже сжатые готовые данные в кэш.
Отдает ответ клиенту.
Получается, мы кэшируем не просто данные из базы, а результат сборки read‑модели. Это уменьшает не только количество запросов к базе, но и нагрузку на само приложение (CPU), потому что ему не нужно каждый раз заново проходить весь путь сборки дерева.

С точки зрения строгой архитектурной чистоты это может выглядеть не идеально. Мы как будто создаем отдельный read‑path, который не совсем вписывается в красивую каноническую схему. Но с точки зрения production‑системы это нормальный компромисс: для записи мы сохраняем более чистую модель, а для чтения используем оптимизированный путь, потому что чтение и запись решают принципиально разные задачи.
Главное — чтобы это было осознанным решением, а не хаотичным костылем.
Моя идея проста: не нужно следовать догматическим дисциплинам как аксиомам, но нельзя и писать плохой, неподдерживаемый код.
Как говорилось в одной известной игре: «Ничто не истина, всё дозволено».
Любая архитектурная дисциплина — это лишь высокоуровневое описание лучших практик, ориентир, но никак не свод законов для вашего конкретного приложения. Вы имеете полное право не следовать им в лоб. Но есть важное условие: за любой свой архитектурный выбор отвечать будете именно вы, а не авторы умных книг.
Суть Философии Баланса — найти точку равновесия. Взять сильные стороны из каждой методологии и осознанно применять их там, где они уместны, не стесняясь отходить от жестких догм паттерна ради производительности и здравого смысла.
У меня формула простая: нужно брать те элементы из разных архитектурных подходов, которые решают вашу конкретную проблему здесь и сейчас, не пытаясь слепо натянуть одну методологию на весь проект.
На старте мы структурируем систему, опираясь на требования DDD (Domain‑Driven Design): делим пакеты и модули по бизнес‑смыслу из реального мира. При этом мы собираем зависимости приложения в несколько изолированных графов: база кода у нас остается общей, но конкретные реализации зависимостей для разных модулей будут отличаться.
Внутри папки каждого домена или модуля мы используем классическую многослойную структуру. Каждый модуль автономен и инкапсулирует всё необходимое внутри себя:
Entities (доменные сущности)
Repositories (интерфейсы и адаптеры данных)
Services (бизнес‑логика)
DTO (объекты переноса данных)
Собственные внутренние зависимости.
Такой подход дает нам изоляцию: мы можем в любой момент заменить или полностью переписать один домен, не теряя связи с остальной системой и не переделывая другие куски кода. Мы делаем модули настолько самостоятельными, насколько это вообще возможно.
Когда базовый каркас готов, мы начинаем смотреть на реализацию критических узлов и ради производительности осознанно идем на компромиссы. Именно здесь включаются принципы CQRS:
Путь записи (Write‑Path): Оставляем академически чистым. Репозитории работают с объектами атомарно, бережно сохраняя консистентность данных.Даже можно например связанные данные в БД предоставлять для записи в разных репозиториях и при надобности прокладывать транзакции для сохранения консистентности.
Путь чтения (Read‑Path): Деформируем в угоду скорости. Если профайлер показывает нагрузку, мы пишем сложные, «грязные» SQL‑запросы, которые одним махом вытаскивают и склеивают готовые данные (например, все дочерние категории одного выбранного родителя и все продукты этих категорий, склеенные вместе).
Если присмотреться к получившейся картине, в ней можно одновременно разглядеть и DDD, и многослойную архитектуру, и CQRS. Это не эклектичный хаос, а осознанное соединение доктрин. Мы берем от них структуру и порядок, которые облегчают жизнь команде, но вовремя останавливаемся, чтобы не навредить производительности продакшна.
Держать в голове этот баланс и каждый раз вручную собирать такие графы зависимостей — задача тривиальная только на словах. На практике рутина, человеческий фактор, рост системы и production‑задачи быстро затягивают проект либо в спагетти‑код, либо в overengineering.
В какой‑то момент становится понятно: одной философии недостаточно. Нужен каркас, который помогает разработчику оставаться в зоне равновесия.
Дальше я расскажу про концепт собственного архитектурного фреймворка, который был спроектирован именно для автоматизации этих решений. Мы подробно разберем его внутреннюю кухню и посмотрим, как на уровне каркаса приложения получить сбалансированное архитектурное решение, где можно достаточно гибко «тянуть» ползунки в нужную сторону: углубляться в DDD для сложной логики и соответствие нейминга требованиям бизнеса для лучшего понимания всей команды или уходить в CQRS ради экстремальной оптимизации базы данных в ущерб красивой архитектуре.
Когда я начал писать свои сервисы на Go, я сначала посмотрел, что делают и рекомендуют другие. В основном рекомендации сводились к чему‑то близкому к Clean Architecture: структурированные папки, services, repositories, entities, DTO и другие слои внутри internal. Я начал писать примерно так, но довольно быстро почувствовал неудобство.
Проблема была не в самой идее слоёв, а в том, как это выглядело в ежедневной работе. Для добавления одного endpoint‑а приходилось переходить по разным частям internal, искать связанные папки и держать в голове слишком много разнесённых фрагментов. При этом мне нужно было писать похожие сервисы, и постепенно стало видно, что часть базового кода и архитектурных решений можно вынести в основу.
Сначала это была просто попытка переиспользовать готовый каркас. Потом я решил развить это в более модульный framework с немного другой структурой. Мне хотелось получить гибкую и удобную систему, где всё, что относится к одному объекту или домену, лежит рядом: entity, repository, service, DTO, provider и связанные внутренние механизмы. Это похоже на DDD‑разделение по бизнес‑смыслу, но без попытки насильно загнать всю систему в академическую форму.
Кроме доменных модулей, в framework появились общие механизмы: JWT‑auth, tenant package, kernel для подключения базы данных, Redis и других зависимостей, transaction manager, graceful shutdown, context propagation и другие системные части, о которых не хочется каждый раз думать заново при старте нового сервиса.
При этом я специально не хотел делать framework слишком жёстким. Его задача — дать основу и правила игры, но не забрать у разработчика возможность управлять архитектурой под конкретный продукт. Где‑то реализация может быть ближе к DDD, где‑то легче и прагматичнее, а где‑то read‑path может уйти в CQRS, raw SQL и кэширование готовых ответов.
По сути, текущая версия framework‑а — примерно пятая итерация. Столько раз я внедрял, менял и перестраивал подход, пока он не начал соответствовать тому, что я изначально хотел получить архитектурно. Сейчас это уже похоже на alpha‑версию: до полноценного framework‑а ещё много работы, но основные архитектурные идеи уже заложены.
Его задача — не заменить Gin, Fiber, Echo или другие Go‑библиотеки, а дать production‑oriented каркас для сервисов, где важны модульность, явные зависимости, multi‑tenancy и управляемые компромиссы между DDD‑inspired структурой и pragmatic CQRS.
Фреймворк написан на языке Go и сперва обсудим какие пакеты и почему я выбрал. Такие пакеты подходили именно под мою архитектуру и представление. Это не совсем легковесный фреймворк для микросервисов

Роутер — Я старался минимизировать runtime magic и сохранить предсказуемый middleware flow, поэтому выбрал lightweight router с explicit composition. В моем случае для этого хорошо подошел Chi.
Внедрение зависимостей — для DI я выбрал Google Wire. Он собирает dependency graph на этапе компиляции, без runtime service locator и скрытой магии. Благодаря этому сразу видно, какие зависимости подключены, где граф не собирается и какой entrypoint использует какие компоненты.
Консоль — для консольных команд я выбрал Cobra: хороший готовый пакет из коробки.
Документация — godoc собирает документацию из комментариев к Handler‑ам, не нужно вести её вручную, достаточно запустить команду. Похожий подход, кажется, используется и в Symfony.
Энвайрмент — выбрал Cleanenv — с его помощью легко собрать конфигурацию, причём поддерживаются не только.env, но и yaml‑файлы.
Логирование — slog написал небольшой враппер. В нём можно настроить уровень логов (error, warning, info), формат вывода (текст или JSON) и назначение (файл или stdout/stderr).
Валидатор — go‑playground/validator удобно валидировать данные через DTO. Для request/response я как раз предлагаю использовать отдельные DTO.
ORM — для write‑path я выбрал GORM. Не потому что ORM должен выражать любые сложные запросы, а потому что на стороне записи он удобен как safety boundary: entity mapping, транзакции, connection handling, parameter binding и единообразная работа с ошибками.
Для простых CRUD‑сценариев entity может использоваться и как модель базы, и как базовая структура для операций чтения/записи. Но если read‑path требует другой формы данных, сложных JOIN‑ов или projection под конкретный API‑response, лучше использовать отдельный DTO/read‑model и специализированный SQL‑запрос.
Датабейз — сейчас подключены только Postgres и Redis — под мои задачи нужны были именно они. Но при необходимости можно добавить и другие.
Тенант пакет — написанный мной tenant resolver. Сейчас он из коробки работает со схемами — то есть разделение идёт по принципу schema per tenant.
skyrix-delivery/ ├── cmd/ │ ├── console/ // Точка входа для CLI-команд, кронов и джоб │ └── wire.go // DI-контейнер (Google Wire) - здесь собирается граф │ └── http/ // Точка входа для веб-приложения (HTTP-сервер) │ └── wire.go // DI-контейнер (Google Wire) - здесь собирается граф │ ├── internal/ │ ├── kernel/ // ФУНДАМЕНТ: Низкоуровневый системный каркас │ │ ├── db/ // Настройка сырых подключений (Postgres, Redis) │ │ └── kernel.go // Инициализация базовых системных провайдеров │ │ │ ├── engine/ // ПРАВИЛА ИГРЫ: Сквозные архитектурные модули │ │ ├── auth/ // Изолированный движок авторизации (JWT, Middleware) │ │ ├── tenantPackage // МУЛЬТИТЕНАНТНОСТЬ: Динамический Schema Resolver │ │ │ // (тот самый баланс изоляции без лишних затрат) │ │ └── transaction.go// МЕНЕДЖЕР ТРАНЗАКЦИЙ: Держит сервисы чистыми от SQL-кода │ │ │ ├── domain/ // БИЗНЕС-ЛОГИКА (DDD): Автономные модули │ │ ├── order/ // Каждый домен содержит внутри себя всё необходимое: │ │ │ ├── entity/ // Чистые сущности (Write-Path) │ │ │ ├── repository // Тонкие репозитории для атомарного CRUD │ │ │ ├── service/ // Умные сервисы с бизнес-логикой │ │ │ └── provider.go // Локальный провайдер зависимостей для сборки домена │ │ │ ├── handlers/ // СЛОЙ АДАПТЕРОВ: HTTP-хендлеры │ │ ├── orderHandler.go // Принимает запрос, дергает decode, валидирует DTO, │ │ └── baseHandler.go // отдает управление сервису нужного домена │ │ │ └── providers/ // ГЛОБАЛЬНАЯ СКЛЕЙКА: Сюда импортируются локальные │ ├── domains.go // провайдеры из папок domain/ и engine/, чтобы │ └── platform.go // Google Wire мог одной командой собрать всё приложение
В начале структуры находится папка cmd, где разделены разные entrypoint‑ы приложения: http и console.
Так как framework использует Google Wire, для каждого entrypoint‑а можно собрать отдельный dependency graph.
Для http‑приложения в граф попадают router, middleware, handlers, domain‑сервисы и остальные зависимости, необходимые для обработки API‑запросов.
Для console‑приложения собирается другой граф: CLI‑команды, background jobs, cron‑задачи и только те домены/сервисы, которые реально нужны этим задачам.
В результате один и тот же кодовый базис может порождать несколько разных runtime‑приложений, не подтягивая лишние зависимости туда, где они не нужны.
В папке internal находится основная часть приложения.
kernel — это фундаментальный слой: базовые интерфейсы, контракты и низкоуровневые зависимости, на которые опирается framework.
engine — это слой сквозных технических механизмов: database adapters, auth, tenant resolving, transaction manager, middleware и другие инфраструктурные модули.
Я специально отделяю engine от domain, потому что авторизация, multitenancy, транзакции и подключение к базе данных не являются бизнес‑логикой. Если смешивать эти вещи с доменами, бизнес‑код постепенно начинает зависеть от инфраструктуры, становится сложнее тестировать, переносить и переиспользовать.
Такое разделение позволяет держать domain packages ближе к бизнес‑смыслу, а infrastructure concerns — в отдельной зоне framework‑а.
domain предназначен для бизнес‑логики. Каждый домен содержит свои entity, repository, service, dto и provider.go. Например, order, courier, dispatch, payment или store могут развиваться независимо, но при этом подключаться к приложению через общий assembly layer.
handlers — это HTTP‑адаптеры приложения. Они принимают запрос, декодируют payload, валидируют DTO и передают управление нужному доменному сервису. Их задача — быть внешним фасадом, а не местом для бизнес‑логики.
providers — это слой сборки приложения. Здесь подключаются домены, engine‑модули, middleware, jobs, handlers и другие зависимости, чтобы Wire мог собрать нужный runtime graph.

Иконечно, есть несколько правил, которые определяют, как работает фреймворк. Чтобы соответствовать всему, мною сказанному, — это не догма, а набор рекомендации для сохранения баланса.
Репозитории должны быть тонкими.
Они не должны содержать бизнес‑логику. Их задача — читать, сохранять и обновлять данные.
Сервисы не должны знать детали базы данных.
Сервис работает с репозиториями и бизнес‑правилами, но не управляет SQL, schema switching или низкоуровневым database‑кодом напрямую.
Транзакции должны проходить через transaction manager.
Сервис может инициировать транзакционный бизнес‑сценарий, но не должен напрямую управлять низкоуровневыми деталями базы данных.
Для этого используется transaction manager: он открывает транзакцию, передаёт её в нужные репозитории и завершает через commit или rollback.
Репозитории при этом должны иметь методы, которые могут работать как с обычным database connection, так и с transaction context.
Так сервис сохраняет контроль над бизнес‑сценарием, но не превращается в слой, завязанный на конкретную реализацию базы данных.
У каждого домена должен быть главный фасадный сервис.
Внутри домена могут быть дополнительные сервисы, фабрики, стратегии и валидаторы, но наружу домен должен отдавать понятную точку входа.
Домены не должны напрямую лезть во внутренности друг друга.
Если одному домену нужны данные другого, связь должна идти через явный сервис, DTO, query/read‑model или событие, если связь становится сложной.
Write‑path должен сохранять консистентность.
Операции записи идут через доменные сервисы, репозитории и транзакции.
Read‑path может быть оптимизирован отдельно.
Для сложных чтений допустимы DTO, read‑model, raw SQL, JOIN‑ы и кэширование готового ответа.
Infrastructure concerns должны жить в engine/kernel.
Авторизация, tenant resolving, transaction manager, config, db и middleware не должны протекать в бизнес‑логику.
DI‑граф должен быть явным.
Зависимости собираются через Wire/provider‑ы, а не через runtime service locator или глобальную магию.
Event‑driven внутри сервиса не используется по умолчанию.
Сначала предпочтительна явная orchestration‑логика. Events добавляются тогда, когда связь между доменами становится сложной и события реально уменьшают связанность.
Правила — это не догма.
Их можно нарушать, но только осознанно: если понятно, какую проблему это решает и какую цену создаёт.
На правилах заканчивается зона framework‑а и начинается зона конкретной реализации.
Framework задаёт каркас, но не решает за разработчика, насколько строго нужно следовать DDD, насколько лёгкой должна быть реализация и где оправдано использовать CQRS‑подход. Это сделано намеренно: разные продукты, разные домены и разные bottleneck‑и требуют разной степени строгости.
Где‑то важнее усилить архитектурную дисциплину и сделать доменную модель более выразительной. Где‑то важнее оставить лёгкий CRUD без лишних абстракций. А где‑то bottleneck находится на чтении данных, и тогда read‑path можно сознательно увести в optimized SQL, projection‑ы, read‑model‑и и кэширование готового ответа.
Именно здесь проявляется идея Баланса: framework не должен быть клеткой. Он должен давать понятные границы, но оставлять свободу двигать архитектуру в сторону, которая нужна конкретной production‑задаче.
Эти правила дают не просто красивую структуру папок, а более предсказуемый способ развития backend‑сервиса.
Добавление нового endpoint‑а становится понятным процессом:
описать DTO/request‑response модели;
добавить handler;
реализовать domain service;
подключить repository или read‑model;
зарегистрировать зависимости через provider;
пересобрать Wire graph;
обновить документацию.
Такой подход помогает локализовать изменения. Если меняется бизнес‑логика — мы идём в domain.
Если меняется способ сборки приложения, подключение модулей или конфигурация инфраструктуры — разработчик в основном работает через папку providers, где собираются runtime graph‑ы приложения и подключаются нужные engine‑модули.
kernel и большая часть engine при этом рассматриваются как внутренний infrastructure/runtime layer framework‑а. В обычной разработке бизнес‑логики разработчик не должен постоянно взаимодействовать с этими слоями напрямую.
В перспективе такие пакеты вообще могут быть вынесены в отдельные Go‑модули и подключаться через go get, оставляя application‑layer максимально чистым и сфокусированным на доменах и runtime composition.
В результате архитектура остаётся достаточно гибкой: её можно приблизить к DDD, если доменная логика становится сложнее, или усилить CQRS/read‑model подход, если узким местом становится чтение данных.
Ещё одно важное последствие — тестируемость. Так как зависимости собраны явно, пакеты проще подменять mock‑реализациями и проверять как unit‑тестами, так и интеграционными тестами.
Именно здесь проявляется главная идея Философии Баланса: структура не должна душить разработку, но должна удерживать систему от хаоса.
Код фреймворка открыт под свободной лицензией:
Основной - GitLab
Зеркало - GitHub
Сейчас Framework находится на ранней стадии. Это ещё не зрелая библиотека с зафиксированным публичным API, а template‑first подход: сначала каркас проверяется в реальных сервисах, и только после этого стабильные части можно будет выносить в отдельные Go‑пакеты.
В будущем хочется добавить:
конфигуратор, который позволит выбирать нужные engine‑модули;
возможность подключать или отключать tenant package;
альтернативные варианты авторизации;
генераторы доменов и базовой структуры;
поддержку миграций;
поддержку других баз данных, например MySQL, MongoDB или отдельных PostgreSQL‑адаптеров;
более зрелую документацию и cookbook с практическими сценариями.
Главный принцип остается тем же: не замораживать архитектуру раньше времени, а давать ей развиваться через реальные production‑задачи.
Важно уточнить: я не пытаюсь представить всё это как полностью новую архитектурную методологию. К части этих выводов я пришёл сам через практику, ошибки, production‑задачи и поддержку реальных backend‑сервисов. Часть идей я позже находил в уже существующих подходах: DDD, Clean Architecture, Hexagonal Architecture, CQRS и других архитектурных дисциплинах.
Но именно в этом и есть смысл статьи. Не в том, чтобы заново изобрести известные паттерны, а в том, чтобы собрать их в рабочую ментальную модель, которая помогает принимать архитектурные решения без фанатизма и без хаоса.
Это не истина. Это мой путь.
Если у вас был другой опыт, если вы видите слабое место в этой логике или считаете, что где‑то баланс проведён неправильно — именно этот разговор я и хочу начать. Архитектура развивается не через догму, а через столкновение опыта, ограничений и реальных production‑задач.
Если какие-то архитектурные термины из статьи вам незнакомы, ниже оставлю несколько справочных ссылок, с которых можно быстро начать:
Domain-Driven Design Общая идея DDD: проектирование системы вокруг бизнес-домена и его модели.
Bounded Context Кратко о границах модели и разделении системы на осмысленные контексты.
Hexagonal Architecture / Ports and Adapters Подход, где бизнес-ядро отделяется от внешнего мира через порты и адаптеры.
Clean Architecture Краткое описание идеи Clean Architecture: зависимости направлены внутрь, к бизнес-логике.
Onion Architecture Похожий подход с бизнес-логикой в центре и инфраструктурой во внешних слоях.
Command Query Responsibility Segregation (CQRS) Разделение операций чтения и записи, чтобы read-path и write-path могли развиваться по-разному.
Command–Query Separation (CQS) Базовый принцип, из которого вырос CQRS.
Dependency Injection Способ передавать зависимости явно, вместо того чтобы создавать их внутри объектов.
Inversion of Control Общий принцип, на котором основаны многие DI-подходы и framework-и.
Repository Pattern Паттерн для отделения бизнес-логики от деталей доступа к данным.