Multi Tenants
¿Qué es y como implementarlo?
¿Qué es?
Multi-tenant es una arquitectura que permite a una instancia única de software proveer servicio a varios clientes.
Cada cliente es considerado un Tenant.
Cada tenant puede personalizar ciertas partes de la aplicación (colores de la interfaz por ejemplo) pero no puede personalizar el código.
¿Pros?
- Economía en el desarrollo y mantenimiento (los costos son distribuidos entre todos los clientes "tenants")
- Facilidad en las actualizaciones (solo es necesario actualizar una instancia del software).
- Seguridad de la información de cada cliente
- Mejor aprovechamiento de los recursos de los servidores
¿Contras?
- Dificultad para desarrollar características especificas para un cliente.
- Único punto de falla (la aplicación falla para todos los clientes)
Arquitecturas
Compartida (Shared)
Separada (Isolated)
Híbrida (Semi-Isolated)
Pero primero
¿Qué es un Schema?
Actúa como nombre de espacio (Postgres).
Añade una capa adicional de organización/separación a la base de datos.
(Database -> Schema -> Tabla)
*Cuando no se especifica un schema en la conexión or consulta, solo se accede al schema publico*
Compartida (Shared)
Una Base de datos - Un schema
Pros
- Capa de datos fácil de construir
- Todos los usuarios usan el mismo dominio
Contras
- Costoso (Tamaño de la base de datos y cantidad de peticiones a la db por cada tenant)
Separada (Isolated)
Cada tenant tiene su propia base de datos
Pros
- Mayor seguridad
- Facilidad de recuperación en caso de perdida de datos
- No consume demasiados recursos de procesamiento
Contras
- Dificultad para escalar
- Costoso en recursos de almacenamiento
- Dificultad para compartir información entre tenants
Híbrida (Semi-Isolated)
Una base de datos - Multiples schemas
Pros
- Escalable
- Económico
- Sencillo
- Seguro
- Compartir data entre tenants
Contras
- Dificultad para recuperación en caso de perdida de datos.
- Menos seguro que bases de datos dedicadas (Isolated)
Implementación de multi-tenancy
con django-tenant-schemas
¿Cómo funciona?
Cada tenant (cliente) es identificado por su hostname por ejemplo (dbguiance.platzi.com).
La información del cliente y hostname es almacenada en una tabla en el schema public.
Cuando una petición es realizada, el hostname es usado para buscar el tenant en la base de datos.
Si no se encuentra un tenant con el hostname es lanzado un error 404
Aplicaciones especificas
La mayoría de aplicaciones dentro de un proyecto pueden ser especificas, esto significa que la información en la base de datos no se comparte con otros tenants.
Por ejemplo si hablamos de un software de tiendas on-line podemos tener Clientes, Proveedores, Facturas, etc...
Aplicaciones compartidas
A diferencia de las anteriores, estas son aplicaciones que pueden compartir información entre todos los tenants.
Seguimos hablando de la tienda on-line puede existir por ejemplo una tabla con los porcentajes de los impuestos, o tarifas de servicios de envíos, etc...
Instalación
pip install django-tenant-schemas
Configuración (básica)
En el archivo 'settings.py'
DATABASES = {
'default': {
'ENGINE': 'tenant_schemas.postgresql_backend',
# ..
}
}
DATABASE_ROUTERS = (
'tenant_schemas.routers.TenantSyncRouter',
)
MIDDLEWARE_CLASSES = (
'tenant_schemas.middleware.TenantMiddleware',
# 'tenant_schemas.middleware.SuspiciousTenantMiddleware',
#...
)
Si se desea lanzar un error 400 en lugar de un 404 cuando no se encuentra un tenant valido se usa `tenant_schemas.middleware.SuspiciousTenantMiddleware`
Es necesario asegurarse que `django.core.context_processors.request` se encuentre dentro de `TEMPLATE_CONTEXT_PROCESSORS`
El modelo Tenant
Es el modelo donde se guarda la relación entre el dominio (subdominio) y el nombre del schema, puede tener más campos pero tiene que heredar de `TenantMixin`
from django.db import models
from tenant_schemas.models import TenantMixin
class Client(TenantMixin):
name = models.CharField(max_length=100)
paid_until = models.DateField()
on_trial = models.BooleanField()
created_on = models.DateField(auto_now_add=True)
# default true, schema will be automatically created
# and synced when it is saved
auto_create_schema = True
Una vez creado el modelo se crea el archivo de migración.
python manage.py makemigrations <nombre modelo>
Aplicaciones
Se crean dos tuplas una `SHARED_APPS` y la otra `TENANT_APPS`
En la primera se ponen las aplicaciones que se van a estar en el schema public y visibles por todas los tenants.
En la segunda se ponen las aplicaciones que tienen información separada por tenant
SHARED_APPS = (
'tenant_schemas', # mandatory
'customers', # you must list the app where your tenant model resides in
'django.contrib.contenttypes',
# everything below here is optional
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.admin',
)
TENANT_APPS = (
# The following Django contrib apps must be in TENANT_APPS
'django.contrib.contenttypes',
# your tenant-specific apps
'myapp.hotels',
'myapp.houses',
)
INSTALLED_APPS = list(SHARED_APPS
) + [app for app in TENANT_APPS if app not in SHARED_APPS]
Por último
Se configura el modelo tenant
Ejecutar
TENANT_MODEL = 'customer.Client"
Cuidado
Nunca usar ```./manage.py migrate```
./manage.py migrate_schemas
Configuraciones opcionales
PUBLIC_SCHEMA_NAME
Default: public
Nombre del schema que sera tratado como público
TENANT_CREATION_FAKES_MIGRATIONS
Default: True
Define si los models son migrados a directamente a la última versión y las migraciones posteriores son falseadas
PUBLIC_SCHEMA_URLCONF
Default: None
Configura las urls usadas cuando se visita el sitio principal.
Si consideramos que tenemos cliente.example.com y example.com la url de /login por ejemplo seria la misma en las dos urls. Definiendo este parámetro se consigue que las urls publicas sean diferentes a las compartidas por el tenant.
En este caso se usa las url definidas en PUBLIC_SCHEMA_URLCONF en lugar de las urls de ROOT_URLCONF
Comandos
Todos los comandos excepto `tenant_commant` corren para todos los tenants.
Para correr un comando para tenants especificos es necesario heredar de `BaseTenantCommand`
# foo/management/commands/do_foo.py
from django.core.management.base import BaseCommand
class Command(BaseCommand):
def handle(self, *args, **options):
do_foo()
# foo/management/commands/tenant_do_foo.py
from tenant_schemas.management.commands import BaseTenantCommand
class Command(BaseTenantCommand):
COMMAND_NAME = 'do_foo'
Para correr el comando para un tenant en particular se usa el paramentro
--schema=<nombre schema>
tenant_command
Corre cualquier comando en un schema individual.
Si no se especifica schema, el prompt solicitara el nombre del schema, o se puede especificar así:
createsuperuser
Ya tiene configurado por defecto el parametro o flag schema
./manage.py tenant_command loaddata
./manage.py tenant_command loaddata --schema=<nombre schema>
./manage.py createsuperuser --useradmin='admin' --schema=<nombre schema>
list_tenants
Listado de schemas: domain_url de cada tenant creado
./manage.py list_tenants
Utils
schema_context
Context manager para un schema especifico
tenant_context
Context manager para un tenant especifico
schema_exists
Devuelve True si un schema ya existe en la base de datos
get_tenant_model
Devuelve el nombre de modelo tenant
get_public_schema_name
Devuelve el nombre del schema publico
Aplicaciones de terceros
Celery
Soporta Celery usando tenant-schemas-celery
django-debug-toolbar
Es necesario agregar los routes a urls.py manualmente.
from django.conf import settings
from django.conf.urls import include
if settings.DEBUG:
import debug_toolbar
urlpatterns += patterns(
'',
url(r'^__debug__/', include(debug_toolbar.urls)),
)
Tests
Django no crea los tenants durante los test, para esto existe TenantTestCase.
from tenant_schemas.test.cases import TenantTestCase
from tenant_schemas.test.client import TenantClient
class BaseSetup(TenantTestCase):
def setUp(self):
self.c = TenantClient(self.tenant)
def test_user_profile_view(self):
response = self.c.get(reverse('user_profile'))
self.assertEqual(response.status_code, 200)
Tambien existe `TenantRequestFactory` y `TenantClient` los cuales toman el dominio del tenant automagicamente.
Multi Tenants
By gollum23
Multi Tenants
- 501