Orendix: Cтарт дан
Как я выбирал структуру монорепы для AI-стартапа (и почему не взял Bazel)
Наконец-то вечер. Офисный ноут закрыт, таски в джире отложены до завтра - можно налить нормальный кофе и заняться своим проектом.
Разработку Orendix я решил начать не с написания агентов, а с фундамента - структуры репозитория. Так как я строю систему один по вечерам (да, тот самый "один в поле воин"), мне критически важно:
- Максимально всё автоматизировать.
- С первого дня заложить жесткие правила, чтобы через месяц не утонуть в собственном легаси-коде.
Проект уже сейчас видится сложным: несколько клиентов на фронте (JS/TS), бэкенд с ML-мозгами на Питоне, а для тяжелых шлюзов и ускорения - Go или Rust. Держать всё это в разных репозиториях - значит сойти с ума при релизах. Поэтому только монорепа.
Но монорепы бывают разными. Прежде чем создать первую папку, я перебрал 4 классических варианта:
1. Подход "Apps & Libs" (Мой выбор)
Самый здравый паттерн для backend-ориентированных систем на Go, Python или Rust.
- Структура: Две главные директории. `apps/` (или `projects/`) для запускаемых сервисов и `libs/` (или `packages/`) для общих библиотек.
- Плюсы: Максимально простая логика и жесткий однонаправленный граф импортов (`projects` -> `packages`). Структура сама подсказывает, где деплой, а где переиспользование.
- Минусы: Папка с пакетами быстро превращается в помойку (`packages/utils`), за этим нужно жестко следить на ревью.
- Нюанс: Сама по себе эта структура не дает магической инкрементальной сборки ("собираем только то, что изменилось"). Эту фичу придется дописывать руками через `git diff` и эвристики в CI, чтобы не тащить в проект тяжелые тулзы ради одной функции.
2. Подход "Component-Based" (Стиль Lerna / Workspaces)
Исторически пришел из экосистемы JavaScript/TypeScript.
- Структура: Всё в репозитории - это "пакет". Нет жесткого разделения на сервисы и библиотеки. В одной плоской папке packages/ вперемешку лежат `frontend-app` и `ui-kit`.
- Плюсы: Идеально для чистого фронтенда и Node.js. Сверху накидывается Turborepo или Nx, и вы получаете автоматический граф зависимостей с кэшированием.
- Минусы: В мультиязычных репозиториях (Python + Go) магия исчезает. Инструменты не понимают импорты не-JS языков из коробки. Разворачивать тот же Nx только как скрипт-раннер - откровенный оверинжиниринг.
3. Подход "Domain-Driven" (По бизнес-доменам)
Код организуется не вокруг технических слоев, а вокруг бизнеса.
- Структура: Папки бьются по доменам: `domain/auth/`, `domain/billing/`, `domain/agents/`. Внутри каждой - свой API, своя логика и свои схемы БД.
- Плюсы: Идеально для микросервисной распилки в будущем. Команды сидят строго в своих доменах.
- Минусы: Шарить общий код - настоящая боль. Разработчики часто просто копипастят куски кода, лишь бы не создавать связей между доменами и не нарушать DDD-пуризм.
4. Подход "Hierarchical / Target-Based" (Стиль Google / Bazel)
Хардкор для гигантских репозиториев (Google, Uber, Twitter).
- Структура: Глубоко вложенное дерево папок, но логика строится на сборочных файлах (BUILD.bazel в каждой директории), которые объявляют таргеты.
- Плюсы: Масштабируется практически бесконечно. Идеальное кэширование, так как граф зависимостей строится на уровне конкретных таргетов, а не целых папок.
- Минусы: Серьёзный налог на инфраструктуру. Да, новые фичи Bazel (вроде Bzlmod) снизили порог входа, но писать и содержать BUILD-файлы всё равно дорого. Для соло-фаундера это гарантированная смерть на взлете.
Что в итоге?
Для Orendix я выбрал "Apps & Libs" с небольшими модификациями под Nix-окружение. Это дает баланс: у меня есть порядок, но я не трачу выходные на написание BUILD-файлов. А инкрементальную сборку я добиваю лёгкими bash-эвристиками в CI.
👉 Мой дневник разработки: Telegram: @orendix_ai
(P.S. Буду рад любой обратной связи в комментах).