Перейти к основному содержимому

База данных

API ChurchApps использует архитектуру одна база данных на модуль. Каждый из шести модулей данных имеет свою собственную базу данных MySQL с независимым пулом подключений, обеспечивая чёткие границы данных, сохраняя при этом всё в одном развёртывании.

Перед началом

Обзор архитектуры

Api
├── membership_db ← Люди, группы, разрешения
├── attendance_db ← Служения, сессии, записи
├── content_db ← Страницы, разделы, элементы
├── giving_db ← Пожертвования, фонды, платежи
├── messaging_db ← Разговоры, уведомления
└── doing_db ← Задачи, планы, назначения

Ключевые решения проектирования

  • Одна база данных на модуль — каждый модуль поддерживает собственную базу данных MySQL с выделенным пулом подключений (управляется KyselyPool). Это держит модули развязанными и позволяет независимой эволюции схемы.
  • Исключительное владение — таблицы модуля читаются и записываются только в коде этого модуля. Когда другому модулю нужны данные, он вызывает шлюз владеющего модуля, а не запрашивает таблицы напрямую — см. Кросс-модульная коммуникация.
  • Шаблон репозитория без ORM — весь доступ к данным происходит через классы репозитория, которые создают типизированный SQL с построителем запросов Kysely для схемы модуля. Это обеспечивает полный контроль над производительностью и поведением запросов.
  • Мультитенантность по дизайну — каждый запрос ограничен churchId. Все таблицы включают столбец churchId, и слой репозитория автоматически обеспечивает изоляцию тенантов.

Строки подключения

Подключение базы данных каждого модуля настраивается в .env с использованием стандартного формата строки подключения MySQL:

mysql://user:password@host:port/database

Например, локальная установка для разработки может выглядеть следующим образом:

Каждый модуль читает своё подключение из переменной окружения с названием <MODULE>_CONNECTION_STRING:

MEMBERSHIP_CONNECTION_STRING=mysql://root:password@localhost:3306/churchapps_membership
ATTENDANCE_CONNECTION_STRING=mysql://root:password@localhost:3306/churchapps_attendance
CONTENT_CONNECTION_STRING=mysql://root:password@localhost:3306/churchapps_content
GIVING_CONNECTION_STRING=mysql://root:password@localhost:3306/churchapps_giving
MESSAGING_CONNECTION_STRING=mysql://root:password@localhost:3306/churchapps_messaging
DOING_CONNECTION_STRING=mysql://root:password@localhost:3306/churchapps_doing
Информация

В продакшене строки подключения хранятся в AWS SSM Parameter Store и читаются классом Environment при запуске.

Скрипты схемы

Схемы таблиц определены как миграции Kysely в каталоге tools/migrations/, организованные по модулям:

tools/migrations/
├── membership/
├── attendance/
├── content/
├── giving/
├── messaging/
└── doing/

Миграции определяют создание таблиц, индексы и изменения схемы. Каталог tools/dbScripts/ содержит данные демонстрации и семена, которые могут быть загружены поверх схемы.

Инициализация базы данных

Инициализировать все базы данных

npm run initdb

Это создаёт все шесть баз данных и запускает миграции для каждой.

Инициализировать один модуль

npm run initdb -- --module=membership
Совет

При работе с конкретным модулем вы можете переинициализировать только базу данных этого модуля, не затрагивая другие.

Шаблон доступа к данным

Репозитории создают запросы с помощью построителя запросов Kysely для типизированной схемы базы данных модуля, полученной через функцию getDb() модуля. Типичный метод репозитория выглядит следующим образом:

public async loadAll(churchId: string) {
return getDb().selectFrom("people").selectAll()
.where("churchId", "=", churchId)
.execute();
}

Репозитории получаются через RepoManager:

const repos = await RepoManager.getRepos<Repos>("membership");
const people = await repos.person.loadAll(churchId);
Предупреждение

Всегда включайте churchId в ваши запросы, чтобы поддерживать изоляцию мультитенантов. Никогда не запрашивайте данные у нескольких тенантов, если у вас нет конкретной, авторизованной причины для этого.

Кросс-модульные ссылки

Поскольку данные каждого модуля находятся в отдельной базе данных, нет иностранных ключей или SQL-присоединений через границы модулей. Запись, которая связана с данными другого модуля, хранит id этой записи — например, пожертвование в базе данных пожертвований содержит personId человека в базе данных членства — и любая кросс-модульная композиция происходит в коде приложения.

Это ограничение — то, что делает границы модулей реальными: каждая схема может развиваться независимо, база данных модуля может быть перемещена на собственный сервер, и модуль может быть даже извлечён в автономный сервис без распутывания общих таблиц или кросс-базовых запросов.

Связанные статьи