Multi-tenant Django приложения
Многопользовательская архитектура позволяет одному приложению обслуживать множество клиентов (тенантов), где каждый клиент имеет изолированные данные и настройки.
Типы multi-tenant архитектуры
Существует три основных подхода:
- Schema-based isolation - каждый тенант в отдельной схеме БД
- Database-per-tenant - каждый тенант в отдельной БД
- Shared database, shared schema - все тенанты в одной БД с фильтрацией
Schema-based isolation с django-tenant-schemas
Этот подход обеспечивает полную изоляцию данных каждого тенанта:
1. Установка и настройка
2. Настройка settings.py
1INSTALLED_APPS = [
2 'tenant_schemas', # должно быть первым
3 'customers', # модель тенанта должна быть перед shared apps
4 # shared apps
5 'django.contrib.auth',
6 'django.contrib.contenttypes',
7 'django.contrib.sessions',
8 'django.contrib.messages',
9 'django.contrib.staticfiles',
10 # tenant apps
11 'myapp',
12]
13
14TENANT_APPS = [
15 'myapp',
16]
17
18SHARED_APPS = [
19 'tenant_schemas',
20 'customers',
21 'django.contrib.auth',
22 'django.contrib.contenttypes',
23 'django.contrib.sessions',
24 'django.contrib.messages',
25 'django.contrib.staticfiles',
26]
27
28TENANT_MODEL = 'customers.Client'
29
30DATABASE_ROUTERS = (
31 'tenant_schemas.routers.TenantSyncRouter',
32)
3. Создание модели тенанта
1# customers/models.py
2from django.db import models
3from tenant_schemas.models import TenantMixin
4
5class Client(TenantMixin):
6 name = models.CharField(max_length=100)
7 paid_until = models.DateField()
8 on_trial = models.BooleanField()
9 created_on = models.DateField(auto_now_add=True)
10
11 # Обязательное поле для схемы
12 schema_name = models.CharField(max_length=63, unique=True)
13
14 def __str__(self):
15 return self.name
4. Создание tenant-aware моделей
1# myapp/models.py
2from django.db import models
3
4class Product(models.Model):
5 name = models.CharField(max_length=100)
6 price = models.DecimalField(max_digits=10, decimal_places=2)
7 description = models.TextField()
8 created_at = models.DateTimeField(auto_now_add=True)
9
10 class Meta:
11 verbose_name = 'Product'
12 verbose_name_plural = 'Products'
5. Middleware для определения тенанта
1# middleware.py
2from django.http import HttpResponse
3from django_tenants.middleware import TenantMainMiddleware
4from django_tenants.utils import get_tenant
5
6class CustomTenantMiddleware(TenantMainMiddleware):
7 def process_request(self, request):
8 # Определяем тенанта по домену
9 hostname = request.get_host().split(':')[0]
10
11 try:
12 tenant = get_tenant(domain=hostname)
13 request.tenant = tenant
14 except:
15 # Fallback для публичных страницы
16 pass
17
18 return super().process_request(request)
6. Создание тенанта через management command
1# management/commands/create_tenant.py
2from django.core.management.base import BaseCommand
3from customers.models import Client
4from django_tenants.utils import tenant_context
5
6class Command(BaseCommand):
7 help = 'Создает нового тенанта'
8
9 def add_arguments(self, parser):
10 parser.add_argument('schema_name', type=str)
11 parser.add_argument('name', type=str)
12
13 def handle(self, *args, **options):
14 schema_name = options['schema_name']
15 name = options['name']
16
17 # Создаем тенанта
18 tenant = Client(
19 schema_name=schema_name,
20 name=name,
21 paid_until='2025-12-31',
22 on_trial=True
23 )
24 tenant.save()
25
26 # Создаем схему БД для тенанта
27 tenant.create_schema()
28
29 self.stdout.write(
30 self.style.SUCCESS(f'Тенант {name} создан успешно')
31 )
Database-per-tenant подход
Альтернативный подход с отдельными базами данных:
1# settings.py
2 DATABASES = {
3 'default': {
4 'ENGINE': 'django.db.backends.postgresql',
5 'NAME': 'main_db',
6 'USER': 'user',
7 'PASSWORD': 'password',
8 'HOST': 'localhost',
9 'PORT': '5432',
10 }
11 }
12
13 # Динамическое создание БД для тенантов
14 def get_tenant_db(tenant_id):
15 return {
16 'ENGINE': 'django.db.backends.postgresql',
17 'NAME': f'tenant_{tenant_id}_db',
18 'USER': 'user',
19 'PASSWORD': 'password',
20 'HOST': 'localhost',
21 'PORT': '5432',
22 }
Shared database с фильтрацией
Простой подход для небольших приложений:
1# models.py
2 from django.db import models
3
4 class TenantAwareModel(models.Model):
5 tenant_id = models.IntegerField()
6
7 class Meta:
8 abstract = True
9
10 class Product(TenantAwareModel):
11 name = models.CharField(max_length=100)
12 price = models.DecimalField(max_digits=10, decimal_places=2)
13
14 class Meta:
15 unique_together = ['tenant_id', 'name']
16
17 # views.py
18 def get_tenant_products(request, tenant_id):
19 products = Product.objects.filter(tenant_id=tenant_id)
20 return render(request, 'products.html', {'products': products})
Практические примеры использования
Создание тенанта через API
1# views.py
2from rest_framework import status
3from rest_framework.decorators import api_view, permission_classes
4from rest_framework.permissions import IsAdminUser
5from rest_framework.response import Response
6from customers.models import Client
7
8@api_view(['POST'])
9@permission_classes([IsAdminUser])
10def create_tenant(request):
11 schema_name = request.data.get('schema_name')
12 name = request.data.get('name')
13
14 if not schema_name or not name:
15 return Response(
16 {'error': 'schema_name и name обязательны'},
17 status=status.HTTP_400_BAD_REQUEST
18 )
19
20 try:
21 tenant = Client.objects.create(
22 schema_name=schema_name,
23 name=name,
24 paid_until='2025-12-31',
25 on_trial=True
26 )
27 tenant.create_schema()
28
29 return Response({
30 'id': tenant.id,
31 'schema_name': tenant.schema_name,
32 'name': tenant.name
33 }, status=status.HTTP_201_CREATED)
34
35 except Exception as e:
36 return Response(
37 {'error': str(e)},
38 status=status.HTTP_500_INTERNAL_SERVER_ERROR
39 )
Миграции для тенантов
Лучшие практики
- Всегда используй middleware для автоматического определения тенанта
- Кэшируй данные тенанта для улучшения производительности
- Используй select_related и prefetch_related для оптимизации запросов
- Создавай отдельные настройки для каждого окружения (dev, staging, prod)
- Тестируй на изолированных данных каждого тенанта
FAQ
Q: Когда использовать multi-tenant архитектуру?
A: Для SaaS приложений, где каждый клиент должен иметь изолированные данные и настройки. Идеально подходит для B2B сервисов, платформ электронной коммерции, корпоративных приложений.
Q: Какой подход выбрать?
A: Schema-based isolation - для максимальной изоляции и безопасности. Database-per-tenant - для критически важных данных. Shared database - для простых приложений с небольшим количеством тенантов.
Q: Как обрабатывать публичные страницы?
A: Создай отдельные URL-паттерны для публичных страниц, которые не требуют определения тенанта, или используй fallback в middleware.
Q: Как масштабировать multi-tenant приложение?
A: Используй горизонтальное масштабирование, кэширование (Redis), CDN для статических файлов, и рассмотри возможность использования микросервисов для критически важных компонентов.
Q: Как обеспечить безопасность данных между тенантами?
A: Всегда проверяй tenant_id в запросах, используй middleware для автоматической фильтрации, и регулярно аудируй доступ к данным.
Q: Как тестировать multi-tenant функциональность?
A: Создавай отдельные тестовые тенанты для каждого теста, используй фикстуры с разными схемами, и тестируй изоляцию данных между тенантами.