Мультитенантная архитектура
Коротко: Программная архитектура, в которой одно приложение обслуживает одновременно нескольких независимых клиентов («арендаторов») — у каждого свои данные, часто разделённые собственными поддоменами.
Подробнее: Типичный шаблон: customer1.my-platform.com, customer2.my-platform.com и т. д. работают на одной кодовой базе, но с раздельными данными по арендаторам. Обычно для этого нужна wildcard-запись DNS и платёжное решение, способное распределять деньги между несколькими получателями (например, Stripe Connect).
Наш контекст: Долгосрочная цель Emzett — платформу магазина позже предполагается «сдавать в аренду» другим, каждому клиенту на своём поддомене. По дорожной карте это последний и самый большой шаг расширения.
Подробности
Три степени изоляции
В мультитенантных системах центральное проектное решение — КАК разделять данные разных арендаторов; от «мягкого» к «строгому» существует примерно три распространённых подхода:
- Общие таблицы со столбцом
tenant_id— все арендаторы живут в одних таблицах, каждая строка несёт идентификатор арендатора, и каждый запрос дополнительно фильтрует по нему. Проще всего в эксплуатации (одна база данных, одна схема, один пул соединений), но забытый фильтрWHERE tenant_id = ...может смешать данные клиентов — серьёзный риск безопасности, от которого на практике часто защищаются политиками безопасности на уровне строк в базе данных (Postgres поддерживает это нативно: политика гарантирует, что даже забытое WHERE в коде приложения не вернёт чужих строк, потому что база данных фильтрует сама). - Одна схема на арендатора — та же база данных, но каждый арендатор получает собственную схему PostgreSQL (пространство имён, например
tenant_customer1.ordersвместоpublic.orders). Изоляция лучше, чем в подходе 1 (плохо написанный запрос не может случайно обратиться к другой схеме без явного указания), но миграции приходится выполнять для каждой схемы отдельно — заметная эксплуатационная нагрузка при сотнях арендаторов. - Одна база данных на арендатора — максимальная изоляция (ошибка базы данных или всплеск производительности у арендатора A совсем не влияет на арендатора B), но больше всего усилий в эксплуатации, особенно при многих мелких арендаторах: у каждой базы данных свои резервные копии, свой мониторинг, свои миграции, а число открытых соединений растёт линейно с числом арендаторов.
Многие крупные SaaS-платформы (например, Slack в ранней архитектуре) начинают с подхода 1 и переходят к подходу 2 или 3 по мере роста клиентов и ужесточения требований соответствия (некоторые корпоративные клиенты по договору требуют физического разделения данных), часто лишь для крупнейших клиентов, тогда как мелкие продолжают делить подход 1 («гибридная» модель).
Маршрутизация по поддоменам
Определение поддомена обычно происходит на уровне middleware/proxy: заголовок Host запроса разбирается, из него выводится арендатор, и этот контекст передаётся всем последующим запросам к базе данных (например, через контекст запроса или переменную, заданную middleware, которая затем «пропускается» через всю обработку запроса, например с помощью AsyncLocalStorage в Node.js, без необходимости явно передавать её каждой функции).
// Упрощённый шаблон на уровне middleware/proxy
const host = request.headers.get("host"); // например "customer1.my-platform.com"
const tenant = host?.split(".")[0];Для wildcard-поддоменов зоне DNS нужна соответствующая запись (*.my-platform.com → тот же IP сервера), а сертификат TLS также должен быть wildcard-сертификатом (или автоматической выдачей сертификатов на каждый поддомен, как предлагает Vercel для собственных доменов), иначе посетители получают предупреждение о сертификате.
Распределение платежей
Для платформы, которая сама принимает деньги от конечных клиентов и должна передавать их соответствующим арендаторам (модель маркетплейса), обычной интеграции Stripe недостаточно — для этого и существует Stripe Connect: каждый арендатор получает собственный (связанный) аккаунт Stripe, а платежи можно направлять напрямую или через платформу с разделением комиссии («application fee»), и платформе самой не нужно выступать юридическим получателем всей выручки арендаторов.
См. также: Записи DNS