Files
magistr/backend
Zuev a8144acb8b
All checks were successful
Build and Push Docker Images / build-and-push-backend (push) Successful in 4m27s
Build and Push Docker Images / build-and-push-frontend (push) Successful in 10s
Build and Push Docker Images / deploy-to-k8s (push) Successful in 1m20s
config: Update application properties.
2026-03-18 02:00:49 +03:00
..

Руководство Backend-разработчика Magistr

Добро пожаловать в проект Magistr! Этот бэкенд построен на Spring Boot и имеет сложную мультитенантную архитектуру, где одно приложение обслуживает множество независимых университетов, каждый со своей базой данных. В проекте также есть интеграция с Kubernetes для "горячего" управления этими тенантами.

Здесь описано, как тут всё устроено, чтобы вы ничего не сломали.


1. Архитектура мультитенантности

Мы используем подход Separate Database per Tenant (Отдельная БД для каждого клиента).

  • Как приложение понимает, к какой базе обращаться? Все запросы с фронтенда приходят с заголовком Host (например, swsu.zuev.company). В классе TenantInterceptor (находится в config/tenant/TenantInterceptor.java) мы перехватываем этот запрос ДО того, как он дойдёт до контроллеров, вытаскиваем поддомен (swsu) и сохраняем его в ThreadLocal переменную через класс TenantContext.

  • Как переключаются базы данных? Класс TenantRoutingDataSource наследуется от спринговского AbstractRoutingDataSource. Перед каждым запросом в базу (любой findById или save из репозитория) Spring спрашивает этот класс: "Какой сейчас ключ тенанта?". Класс берёт имя из TenantContext и переключает коннект на нужную БД на лету.

Важно: Вся логика переключения абсолютно прозрачна для бизнес-кода. В контроллерах и сервисах вы пишете обычный код (userRepository.findAll()), и он сам выполнится в нужной базе.


2. Динамическое управление тенантами (Kubernetes / ConfigMap)

Бэкенд спроектирован для работы в Kubernetes с несколькими репликами (replicas: 2+).

Список тенантов не зашит в код:

  • В K8s он лежит в специальном ConfigMap, который монтируется внутрь пода как файл tenants.json.
  • В классе DatabaseController находится API для добавления нового тенанта из админки.
  • Чтобы изменения применились ко всем подам без перезагрузки, DatabaseController вызывает ConfigMapUpdater. Этот класс обращается напрямую к Kubernetes API (используя ServiceAccount токен пода) и патчит ConfigMap.
  • В фоне работает планировщик TenantConfigWatcher (каждые 30 секунд). Он следит за изменениями tenants.json и, если видит нового тенанта, на лету поднимает для него новый HikariCP пул соединений и добавляет в маршрутизатор баз данных.

3. Базы данных и Миграции (Flyway)

Мы НЕ используем автоматическую генерацию таблиц через Hibernate (spring.jpa.hibernate.ddl-auto=none). Структурой баз данных правит Flyway.

Поскольку баз данных много (они создаются динамически), стандартный Spring Boot Flyway отключён. Вместо этого TenantConfigWatcher вызывает Flyway программно в момент первого подключения нового тенанта.

🛑 ПРАВИЛА ИЗМЕНЕНИЯ СТРУКТУРЫ БД:

Если вам нужно добавить новую таблицу, колонку или изменить тип поля:

  1. Запрещено трогать старые файлы миграций! Запомните: файл V1__init.sql (и любые другие V-файлы, которые уже попали в коммит) — СВЯЩЕНЕН. Если вы его измените, бэкенд не запустится на сервере с ошибкой Migration checksum mismatch.

  2. Как правильно добавить таблицу?

    • Зайдите в папку src/main/resources/db/migration/.
    • Создайте новый файл. Название строго по формату: V<Номер>__<Описание>.sql. Например: V2__add_student_rating_table.sql.
    • Напишите в нём ваш SQL (CREATE TABLE ..., ALTER TABLE ...).
    • Сохраните и запустите проект. Flyway сам пройдёт по всем базам данных тенантов и накатит этот скрипт.
  3. Что если локально я накосячил в V2? Пока файл V2_... не залит в Git и крутится только у вас на локалке, вы можете его переписывать. Но для этого вам нужно зайти в вашу локальную БД (через DBeaver/pgAdmin), вручную откатить свои кривые изменения (удалить таблицу) и удалить запись из истории Flyway: DELETE FROM flyway_schema_history WHERE version = '2'; Либо, что проще: удалите контейнер с локальной БД (docker compose down -v) и поднимите заново пустую.


4. Как запускать проект локально

В корневой папке репозитория (где лежит docker-compose.yaml) поднимите инфраструктуру:

docker compose up -d

Соберется и запустится:

  • Фронтенд
  • Бэкенд
  • Ваша локальная тестовая PostgreSQL-база данных (на порту 5432, имя базы app_db, юзер myuser, логин/пароль см. в compose файле).

Файл backend/tenants.json нужен для локальной разработки. Если вы запускаете бэкенд в Docker Compose, вы можете указать URL jdbc:postgresql://db:5432/app_db (где db — имя контейнера в compose сети). Либо, если вы тестируете взаимодействие бэкенда с вашим текущим IP-адресом (например, 192.168.1.87), вы можете использовать этот IP. Оба варианта рабочие! Проект сразу подхватит настройки и накатит таблицы через Flyway.

Контроллеры и бизнес-логику пишите как в обычном Spring Boot проекте. Главное, помните: у каждого тенанта — своё изолированное хранилище!