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

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

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

Практический кейс: ltree и сжатый кэш
Здесь хорошо показывает себя еще один практический пример — 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 Паттерн для отделения бизнес-логики от деталей доступа к данным.
ссылка на оригинал статьи https://habr.com/ru/articles/1044218/