Multi-tenant Django приложения

Многопользовательская архитектура позволяет одному приложению обслуживать множество клиентов (тенантов), где каждый клиент имеет изолированные данные и настройки.

Типы multi-tenant архитектуры

Существует три основных подхода:

  • Schema-based isolation - каждый тенант в отдельной схеме БД
  • Database-per-tenant - каждый тенант в отдельной БД
  • Shared database, shared schema - все тенанты в одной БД с фильтрацией

Schema-based isolation с django-tenant-schemas

Этот подход обеспечивает полную изоляцию данных каждого тенанта:

1. Установка и настройка

1pip install django-tenant-schemas
2pip install psycopg2-binary

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        )

Миграции для тенантов

1# Создание миграций для shared apps
2python manage.py makemigrations_schemas --shared
3
4# Создание миграций для tenant apps
5python manage.py makemigrations_schemas
6
7# Применение миграций
8python manage.py migrate_schemas --shared
9python manage.py migrate_schemas --tenant

Лучшие практики

  • Всегда используй 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: Создавай отдельные тестовые тенанты для каждого теста, используй фикстуры с разными схемами, и тестируй изоляцию данных между тенантами.