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

Мультитенантность

Руководство по мультитенантной архитектуре платформы.

Что такое мультитенантность

Мультитенантность позволяет одной инсталляции платформы обслуживать множество независимых маркетплейсов (арендаторов/tenants).

Модель данных

Tenant (Арендатор)

Каждый маркетплейс представлен записью в таблице tenants:

CREATE TABLE tenants (
id UUID PRIMARY KEY,
name TEXT NOT NULL,
domain TEXT UNIQUE,
status TEXT DEFAULT 'active',
owner_id UUID REFERENCES auth.users(id),
created_at TIMESTAMP DEFAULT NOW()
);

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

Все пользовательские данные привязаны к конкретному tenant:

  • Продукты: tenant_id в таблице products
  • Заказы: tenant_id в таблице orders
  • Пользователи маркетплейса: tenant_id в таблице marketplace_users

Идентификация tenant

По домену

Основной способ идентификации - через домен:

// Получение tenant по домену
const getTenantByDomain = async (domain: string) => {
const { data } = await supabase
.from('tenants')
.select('id')
.eq('domain', domain)
.single();
return data?.id;
};

По поддомену

Поддержка поддоменов на servicium.ru:

// example: shop1.servicium.ru -> tenant = "shop1"
const subdomain = domain.split('.')[0];

RLS политики

Row Level Security обеспечивает изоляцию данных между tenants:

-- Пример политики для продуктов
CREATE POLICY "Users can only see products from their tenant"
ON products FOR SELECT
USING (tenant_id = get_current_tenant_id());

Конфигурация по tenant

Каждый tenant может иметь уникальные настройки:

Брендинг

  • Логотип
  • Цветовая схема
  • Кастомные стили
  • Favicon

Функциональность

  • Включенные модули
  • Тарифные планы
  • Лимиты и квоты
  • Интеграции

Локализация

  • Язык интерфейса
  • Валюта
  • Часовой пояс
  • Формат дат

Маршрутизация

Определение контекста

При каждом запросе определяется текущий tenant:

const MarketplaceRouter = () => {
const { tenantId, loading } = useTenantInfo();

if (loading) return <LoadingSpinner />;
if (!tenantId) return <TenantNotFound />;

return <Routes>...</Routes>;
};

Защищенные маршруты

Доступ к данным ограничен контекстом tenant:

const useProducts = () => {
const { tenantId } = useTenantInfo();

return useQuery({
queryKey: ['products', tenantId],
queryFn: () => getProductsByTenant(tenantId)
});
};

Развертывание

Single Instance

Один экземпляр приложения обслуживает все tenants:

  • Общий код и инфраструктура
  • Изоляция на уровне данных
  • Экономия ресурсов

Общие ресурсы

  • База данных
  • Файловое хранилище
  • Кэш и сессии
  • Мониторинг

Безопасность

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

  • RLS политики на всех таблицах
  • Валидация tenant_id во всех запросах
  • Аудит доступа к данным

Предотвращение утечек

  • Строгая типизация tenant_id
  • Автоматические тесты изоляции
  • Мониторинг подозрительной активности

Ограничения и квоты

Каждый tenant может иметь лимиты:

  • Количество продуктов
  • Объем файлового хранилища
  • Количество пользователей
  • API запросы в минуту