refactor: Integrate Flyway for database migrations and simplify Docker Compose and tenant configurations.
All checks were successful
Build and Push Docker Images / build-and-push-backend (push) Successful in 3m44s
Build and Push Docker Images / build-and-push-frontend (push) Successful in 11s
Build and Push Docker Images / deploy-to-k8s (push) Successful in 1m19s

This commit is contained in:
Zuev
2026-03-13 04:35:50 +03:00
parent 10c06e726a
commit 9f124c52a5
10 changed files with 114 additions and 278 deletions

77
backend/README.md Normal file
View File

@@ -0,0 +1,77 @@
# Руководство 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`) поднимите инфраструктуру:
```bash
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 проекте. Главное, помните: у каждого тенанта — своё изолированное хранилище!