Мульти-тенант в Django

от автора

Привет, Хабр!

Мульти-тенант (multi-tenancy) — это подход, который позволяет одному экземпляру приложения обслуживать множество клиентов или арендаторов (тенатов). Каждый арендатор изолирован от других, имея возможность кастомизации под свои нужды, при этом основной кодовой базой и инфраструктурой делится между всеми.

Когда применять эту замечательную концепцию? Если говорить простыми словами, то мульти-тенант подход наиболее ценен для SaaS-продуктов, когда одно и то же приложение предоставляется разным клиентам, и каждый клиент работает со своим набором данных. Все это серьезно экономит ресурсы на обслуживание инфраструктуры, тк все изменения вносятся централизованно и мгновенно становятся доступны всем клиентам.

В Django мульти-тенант реализовывается довольно часто и для этого есть библиотека django-multitenant.

Установим и настроим

Установим django-multitenant через пип:

pip install django-multitenant

После установки пора добавить django_multitenant в INSTALLED_APPS проекта Django в settings.py.

Необходимо обновить настройки БД, используя 'ENGINE': 'django_tenants.postgresql_backend', чтобы включить поддержку, к примеру схем PostgreSQL:

DATABASES = {     'default': {         'ENGINE': 'django_tenants.postgresql_backend',         'NAME': 'your_db_name',         'USER': 'your_db_user',         'PASSWORD': 'your_db_password',         'HOST': 'your_db_host',         'PORT': 'your_db_port',     } }

Нужно также добавить django_tenants в список установленных приложений:

INSTALLED_APPS = [     ...     'django_tenants',     ... ]

Работа с тенантами

Для создания тенанта и связанных с ним доменов необходимо определить модели Tenant и Domain, как было описано в базовых настройках:

# models.py from django_tenants.models import TenantMixin, DomainMixin  class Client(TenantMixin):     name = models.CharField(max_length=100)  class Domain(DomainMixin):     pass

После создания моделей, можно программно добавлять новых тенантов и домены:

# добавление нового тенанта from your_app.models import Client, Domain  tenant = Client(schema_name='new_tenant', name='New Tenant') tenant.save()  # сначала сохраняем тенанта  # добавление домена для тенанта domain = Domain(domain='newtenant.example.com', tenant=tenant, is_primary=True) domain.save()

Для применения миграций к схеме конкретного тенанта есть команда migrate_schemas:

python manage.py migrate_schemas --schema=new_tenant

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

def create_tenant(user):     new_schema_name = generate_schema_name(user)     new_tenant = Client(schema_name=new_schema_name, name=user.company_name)     new_tenant.save()      domain = Domain(domain=f"{new_schema_name}.yourdomain.com", tenant=new_tenant, is_primary=True)     domain.save()

Можно использовать яmiddleware, которые помогают определить, к какому тенанту относится текущий запрос:

MIDDLEWARE = [     'django_tenants.middleware.main.TenantMainMiddleware',     # другие middleware... ]

В django-tenants есть изоляция static и media файлов между тенантами. Настройка путей для этих файлов производится в settings.py:

STATIC_URL = '/static/' MEDIA_URL = '/media/'  # django-tenants для управления файлами MULTITENANT_RELATIVE_MEDIA_ROOT = '/tenant_media/'

Тесты

django-tenants предоставляет класс TenantTestCase, который является подклассом django.test.TestCase. TenantTestCase автоматически создает публичного тенанта перед выполнением тестов и удаляет его после:

from django_tenants.test.cases import TenantTestCase  class YourTenantTest(TenantTestCase):     def test_something(self):         # тестовый код

Внутри TenantTestCase можно создавать дополнительные тенанты для тестирования сценариев, требующих взаимодействия между разными тенантами:

from django_tenants.utils import tenant_context  class MultiTenantTest(TenantTestCase):     def test_multi_tenant_interaction(self):         # создание нового тенанта для теста         new_tenant = self.create_tenant(domain_url='newtenant.test.com', schema_name='newtenant')         # использование контекста тенанта для тестирования взаимодействия         with tenant_context(new_tenant):             # тестовый код

Можно управлять миграциями в тестовых сценариях:

from django_tenants.test.cases import FastTenantTestCase  class YourMigrationTest(FastTenantTestCase):     def test_migration(self):         # тест

Также есть FastTenantTestCase который более быстрей TenantTestCase за счет минимизации операций с базой данных.

Можно работать с DNS

В settings.py проекта можно определить, какие приложения являются общими для всех тенантов SHARED_APPS и какие приложения уникальны для каждого тенанта TENANT_APPS:

# settings.py  SHARED_APPS = [     'django_tenants',  # обязательно     'your_app',  # здесь указывается имя приложения     # другие общие приложения ]  TENANT_APPS = [     'django.contrib.contenttypes',     # приложения специфичные для тенантов ]  INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS]

Для обработки запросов к разным тенантам нужно настроить URL-конфигурацию соответствующим образом, используя django-tenants URL router:

# urls.py from django.conf.urls import url from django_tenants.utils import tenant_urlpatterns  urlpatterns = [     # URL-конфигурации ]  urlpatterns += tenant_urlpatterns([     # URL-конфигурации специфичные для тенантов ])

На стороне DNS для поддоменов, представляющих разные тенанты, необходимо создать соответствующие записи A или CNAME, указывающие на IP-адрес сервера, где размещен Django-проект.

Например, если основной домен example.com, для тенанта tenant1 будет такая запись:

  • Type: CNAME

  • Name: tenant1.example.com

  • Value: example.com.

При создании нового тенанта можно автоматически добавлять доменные записи для него, используя сигналы Django или переопределяя метод save модели тенанта:

# models.py from django.db.models.signals import post_save from django.dispatch import receiver  @receiver(post_save, sender=Client) def create_domain_for_new_tenant(sender, instance, created, **kwargs):     if created:         Domain.objects.create(domain='{}.example.com'.format(instance.name.lower()), tenant=instance, is_primary=True)

Более подробно с документацией можно ознакомиться здесь.

Подробнее про архитектуру приложений вы можете узнать в рамках онлайн-курсов от практикующих экспертов отрасли. Подробности в каталоге.


ссылка на оригинал статьи https://habr.com/ru/articles/797003/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *