IHOR.DEVБАЗА_ЗНАНЬ
МЕНЮ

~/blog / Архітектура / multi-tenant-saas-architecture-for-web-products.md

// посібник · 3 хв читання

Архітектура SaaS із кількома орендарями для вебпродуктів

Задайте membership і tenant scope для SaaS-запитів, записів, кешів, файлів, фонової роботи й політик бази до вибору топології ізоляції.

Коротка відповідь

Продукт перевіряє membership вибраного tenant і передає scope через усі операції. Tenant ID URL — вибір, не дозвіл. Спочатку scoped-запити й записи, потім shared tables, schemas чи databases за ізоляцією й підтримкою. Топологія не авторизує користувача.

Практичні кроки реалізації

1. Отримайте перевірений tenant context

Прочитайте сесію, tenant, активне membership і role та сформуйте внутрішній context. Client-header чи role не авторитетні. У користувача можуть бути різні дозволи кількох tenant. Перемикання явне, чутлива операція повторно перевіряє membership.

2. Обмежте весь граф даних

Tenant входить у читання, записи, унікальність і зв’язки. Глобальний ID не є доступом. Foreign key лише з ID може допускати міжtenant-зв’язок. Public-проєкція після scoped-read. Експорт, пошук, download і admin мають ту саму політику.

3. Передайте scope у фонову роботу й кеш

Job містить tenant і операцію, потім відновлює дозволений context. Cache-key включає tenant, теги лише групують інвалідацію. Storage-path потребує авторизації. Session-level tenant у pool небезпечний: потрібні transaction-local context і cleanup.

4. Додайте політики бази

PostgreSQL RLS обмежує рядки, але owner зазвичай обходить її, як superuser і BYPASSRLS. FORCE ROW LEVEL SECURITY підпорядковує owner політиці. Використовуйте відповідну app-role, перевірте read, write і missing context. Тест restricted-role не доводить deployed-owner.

Приклад реалізації

Builder показує tenant scope і параметри. Context створює серверна membership-перевірка, не сама назва типу. Приклад не виконує базу. Адаптер зв’язує values окремо й додає scope до мутацій, joins і зв’язків.

type VerifiedTenantContext = { tenantId: string; userId: string };

export function reportQuery(
  context: VerifiedTenantContext, reportId: string,
) {
  return {
    text: "SELECT id, title FROM report WHERE tenant_id = $1 AND id = $2",
    values: [context.tenantId, reportId],
  };
}

Компроміси та альтернативи

Shared tables зменшують інфраструктуру, але підсилюють важливість scope. Окремі schemas/databases допомагають ізоляції й restore та додають міграції й provisioning. Вони не усувають дозволи чи помилковий вибір бази. Визначте tenant-restore, deletion й операційні межі.

Поширені помилки

Не авторизуйте selector, не покладайтеся на ID, не кешуйте без tenant, не забувайте exports. RLS не захищає bypass-role. Optional tenant у багатьох helper небезпечний: один unscoped-виклик може розкрити чужі записи.

Перевірка перед випуском

  • Context походить із перевіреного membership.
  • Читання, записи, joins і правила мають scope.
  • Кеш, файли, exports і jobs містять tenant.
  • Перевірки використовують app-role.
  • Restore й deletion tenant описані.

Поширені запитання

Чи потрібна окрема база?

Ні. Вибирайте за ізоляцією й підтримкою. Авторизація й scope потрібні в обох.

Чи tenant-tag запобігає витоку?

Ні. Tag групує інвалідацію, ключ і дозволений reader визначають правильні дані.

Наступний крок

Простежте read, write, export і кеш для двох tenant із різними membership. Усуньте відсутній scope, перевірте реальну database-role й tenant-restore.

Первинні джерела

Пов’язані матеріали та приклади

Ігор Харчишин

Незалежний full-stack інженер продуктів, який пише посібники з реалізації розширень браузера та сучасних вебпродуктів на основі першоджерел.

Потрібно реалізувати це у вашому продукті?

Перегляньте відповідні приклади продуктів або поділіться контекстом розробки вебзастосунку, розширення Chrome, API чи SaaS.