App-Kit — кейс
Мультитенантная SaaS-платформа: Telegram-боты, мини-аппы и сайты из подключаемых модулей.
01 Задача
Один тенант владеет проектами, сайтами и модулями; каждому бизнесу (магазин, бронирование, CMS, отзывы) нужны свои данные, роли и интеграции — без форка кодовой базы. Платформе требовалась строгая изоляция на уровне сайта и возможность заменить внешнюю интеграцию на свой сервис, не трогая код модулей.
02 Что сделал
Backend на Django + DRF с моделью тенантности Project → Site → Modules. Фичи поставляются как подключаемые модули (магазин/заказы, бронирование, CMS, FAQ, отзывы, прайсинг, лояльность, рассылки), а каждая внешняя зависимость спрятана за адаптером ABC + фабрика (платежи, telegram, хранилище, изображения, уведомления, авторизация) и выбирается по конфигу сайта. Per-site RBAC даёт владельцу заводить свои роли и матрицу прав granted/denied; авторизация — JWT для админов и Telegram HMAC для клиентов мини-аппа. Три Next.js-фронтенда — client-admin, platform-admin и публичный storefront — работают с одним API.
- Мультитенантная модель Project → Site → Modules со строгой изоляцией данных по сайту
- Фичи как подключаемые модули; внешние сервисы за адаптерами ABC + фабрика, выбор по сайту
- Per-site RBAC: свои роли владельца + матрица прав granted/denied, авто-сид из модулей
- Двойная авторизация — JWT для админов, Telegram HMAC для клиентов; три Next.js-фронтенда на одном API
03 Тенантность и изоляция
Каждый запрос привязывается к сайту ещё до того, как попадёт во view: middleware сопоставляет хост с Site.domain и кладёт сайт в запрос, а каждый view фильтрует данные по этому сайту или по id сайта из URL. Все тенанты живут в одной базе PostgreSQL, и ADR говорит об этом прямо: физического разделения нет, поэтому изоляция обеспечивается кодом на каждом запросе.
То же правило действует за пределами базы. Файлы лежат под префиксом проекта и сайта, у каждого сайта своя квота хранилища, а ключи в общем Redis получают префикс, чтобы тенанты и окружения не пересекались.
04 Модули и адаптеры
Фичи — это плагины с реестром и автообнаружением. Сейчас модулей 15: магазин, заказы, бронирование, расписание, CMS, FAQ, отзывы, комментарии, прайсинг, лояльность, рассылки, доска объявлений, города, портфолио и поддержка. Владелец включает их для каждого сайта отдельно, а навигация админки и права следуют за тем, что включено.
Внешние сервисы спрятаны за девятью семействами адаптеров: платежи, бронирование, авторизация, уведомления, хранилище, кеш, гео, обработка изображений и Telegram. Только у платежей пять провайдеров (внутренний, Stripe, ЮKassa, Lava и Telegram Payments), а бронирование работает на встроенном движке или через YCLIENTS. Фабрика берёт провайдера из конфига модуля сайта, поэтому два сайта на одном коде могут принимать оплату через разных провайдеров. Тяжёлая работа вроде конвертации изображений вынесена в отдельный сервис за адаптером.
Шаблоны сайтов — это отдельные Next.js-приложения со списком доступа, поэтому приватный шаблон вроде того, на котором собран этот сайт, видят только владельцы, для которых он сделан. Владелец может зарегистрироваться сам, пройти мастер настройки и попасть в кабинет, а поддержка платформы умеет входить от имени владельца, чтобы воспроизвести проблему ровно так, как её видит он.
05 Роли, авторизация и безопасность
RBAC переписан после реального бага: одно грубое право покрывало все действия с отзывами, и анонимный посетитель витрины получал 401 на простое чтение. В новой модели есть роли платформы и роли сайта (системные, предустановленные и созданные владельцем), а каждый модуль объявляет свои права, которые засеиваются при его включении.
Админы входят по JWT, а клиентов мини-аппа проверяет HMAC-SHA256 подпись initData от Telegram. Токены переехали из localStorage в httpOnly-cookie, а refresh-cookie ограничена путём авторизации.
06 Инфраструктура и релизы
Разработка идёт в Docker Compose, прод — в k3s. Тег версии запускает Forgejo Actions: образы собираются и уходят в приватный registry, а снапшот-тест OpenAPI роняет CI, если схема API изменилась незаметно.
Раньше каждый новый домен клиента требовал ручной правки nginx. Теперь шлюз из Lua-хука OpenResty спрашивает у бэкенда список разрешённых SNI с коротким кешем, и новый домен начинает работать, как только он появился у сайта. API-клиент и общий код витрин публикуются в приватный npm-реестр с версиями, привязанными к релизу бэкенда.
07 Результат
Новый бизнес поднимается на общей инфраструктуре со своими изолированными данными, ролями и модулями; смена интеграции — правка конфига, а не переписывание. С марта 2026 года в репозитории почти 900 коммитов и 102 релизных тега, в бэкенде около 3000 тестовых функций, а решения по тенантности, адаптерам, авторизации и деплою записаны в 31 ADR. На платформе работают витрины клиентов, а этот сайт собран на её пакетах для витрин.