Part 2: Integration with the library¶
Please make sure that the library has been installed. If not, head over to the installation section and then come back here.
Pre-requisites¶
This tutorial uses Docker to install and run Redis. We use Redis as the backend for storing tenant-specific metadata.
Install Docker from its official website - there are official runtimes for Mac OS, Windows and Linux that make it easy to use across all platforms.
Note
Although you could manually install and run Redis specific to your platform, we recommend using Docker as it is easier to run/manage such components without having any platform dependencies.
Step 1: Setup¶
Add the application to the list of INSTALLED_APPS
as follows:
# mt_site/settings.py
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'tenant_router.apps.TenantRouterConfig', # <-- added here
'mt_site.apps.MtAppConfig'
]
...
Warning
Make sure to add this at the top of any user defined app. If not, the entire application could potentially malfunction.
The library also ships with a middleware that is responsible for setting the correct tenant context
based on the request headers' for a particular request/response cycle. It must be added to the list
of MIDDLEWARES
as follows:
# mt_site/settings.py
...
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'tenant_router.middleware.TenantContextMiddleware', # <-- added here
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
...
Important
Make sure to add this at the top of any middleware which depends on the Django ORM. If not, the tenant context would be unavailable and any corresponding queries would fail.
Step 2: Configure¶
Add the following configuration options to the settings.py
file as follows:
# mt_site/settings.py
...
DATABASE_ROUTERS = [
'tenant_router.orm_backends.django_orm.router.DjangoOrmRouter'
]
TENANT_ROUTER_ORM_SETTINGS = {
'django_orm': {
'SETTINGS_KEY': 'DATABASES',
}
}
TENANT_ROUTER_SERVICE_NAME = 'mt_site'
TENANT_ROUTER_PUBSUB_SETTINGS = {
'BACKEND': 'tenant_router.pubsub.backends.redis.RedisPubSub',
'LOCATION': {
'HOST': '0.0.0.0',
'PORT': '6379'
}
}
TENANT_ROUTER_PUBSUB_ENABLED = True
...
Note
To know more about the list of configuration options available and what each of them mean,
refer to the
settings page.
Next, we'll have to setup the config store as part of the CACHES
dictionary. Since Redis will be used, we'll need to install a Django compatible redis cache
backend.
Although any Django compatible redis backend can be used, for the purposes of this tutorial, we'll be using django-redis.
$ pip install -U django-redis
Now, add an entry to the CACHES
dictionary in settings.py
as follows:
# mt_site/settings.py
...
CACHES = {
"tenant_router_config_store": {
"BACKEND": "django_redis.cache.RedisCache",
"TIMEOUT": None,
"LOCATION": "redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}".format(
REDIS_HOST='0.0.0.0',
REDIS_PORT='6379',
REDIS_DB='5'
),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"SERIALIZER": "django_redis.serializers.json.JSONSerializer"
}
}
}
...
Also, the DATABASES
dictionary which was configured in
Part 1 of this tutorial will have to be refactored as
follows:
# mt_site/settings.py
...
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql"
}
}
...
Also, the HospitalView
has to be refactored as follows:
# mt_demo/views.py
import json
from django.http import HttpResponse
from django.views import View
from tenant_router.threadlocal import tls_tenant_manager
from tests.dummy_app.models import Hospital
class HospitalView(View):
def get(self, request):
data = list(Hospital.objects.all().values())
return HttpResponse(
content=json.dumps(
{
"data": data,
"tenant_id": tls_tenant_manager.current_tenant_context.id
}
),
status=200
)
Step 3: Creating the tenant_config.json
file¶
Create a file called tenant_config.json
in the directory path ~/mt_site/
. Now the directory
layout would look like this:
mt_site/
manage.py
tenant_config.json <-- created here
mt_site/
__init__.py
asgi.py
settings.py
urls.py
wsgi.py
Add the following content within this newly created file:
{
"mapping_metadata": {
"POSTGRES_URL": "orm_config_django_orm_default",
},
"tenant_config": {
"tenant-1.test.com": {
"mt_site": {
"orm_config": {
"django_orm": {
"default": {
"HOST": "127.0.0.1",
"PORT": "5432",
"USER": "your_db_username",
"PASSWORD": "your_db_password",
"NAME": "tenant_1"
}
}
}
}
}
}
}
Note
For more info about the schema of this file, refer to this link.
To start Redis, run the follwoing command:
$ docker run -p 6379:6379 -d redis:latest
In order to load contents of the file into Redis, run the following command:
Important
Before running the command below, make sure that settings.DEBUG
is True
.
$ python manage.py load_tenant_config
Step 4: Running the server¶
Having completed the integration, the next step is ensure that the server starts properly without any errors and its behaviour is the same as before.
Start the server using
$ python manage.py runserver
Now make a HTTP GET
call to the endpoint http://127.0.0.1:8000/hospital/
as follows:
$ python manage.py shell
>>> import requests
>>> r = requests.get(
... 'http://127.0.0.1:8000/hospital/',
... headers={"x-tenant-id": "tenant-1.test.com"}
... )
>>> print(r.json())
The expected response is as follows:
{
"data": [{
"id": 1,
"name": "Test hospital",
"address": "1st street"
}],
"tenant_id": "tenant-1.test.com"
}
As you can see, not much has changed from before except that a new header named
x-tenant-id
with value as tenant-1.test.com
is sent as part of the request.
Behind the scenes, the library parses this value from the header to route ORM queries made from within the view to the appropriate DB.
As a result, the app has suddenly become capable of routing DB queries to the appropriate databases by providing the tenant id as part of the HTTP request.
Info
In order to verify the above statement, make the same HTTP request as above with a different
value for the x-tenant-id
header. The server will return a 500: Internal server error
with a
traceback containing the TenantContextNotFound
exception.
However, we see that there is currently only one DB configuration pertaining to
tenant-1.test.com
. So the golden question is:
What's the procedure to add a new tenant and how to verify it ?
This and a lot more would be answered in the next part of the tutorial.