
Привет, Хабр!
Мульти-тенант (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/
Добавить комментарий