Django, şirketiniz için bir uygulama geliştirmek amacıyla seçebileceğiniz popüler bir çerçevedir. Peki ya birden fazla istemcinin kullanacağı bir SaaS uygulaması oluşturmak istiyorsanız? Hangi mimariyi seçmelisiniz? Bu göreve nasıl yaklaşılabileceğini görelim.
En basit yaklaşım, sahip olduğunuz her müşteri için ayrı bir örnek oluşturmaktır. Diyelim ki bir Django uygulamamız ve bir veritabanımız var. Daha sonra her istemci için kendi veritabanını ve uygulama örneğini çalıştırmamız gerekir. Bu, her uygulama örneğinin yalnızca bir kiracıya sahip olduğu anlamına gelir.
Bu yaklaşımın uygulanması basittir: sahip olduğunuz her hizmetin yeni bir örneğini başlatmanız yeterlidir. Ancak aynı zamanda bir soruna da neden olabilir: Her müşteri altyapı maliyetini önemli ölçüde artıracaktır . Yalnızca birkaç müşteriye sahip olmayı planlıyorsanız veya her bir örnek küçükse bu çok da önemli olmayabilir.
Ancak 100.000 kuruluşa kurumsal mesajlaşma hizmeti sağlayan büyük bir şirket kurduğumuzu varsayalım. Her yeni müşteri için tüm altyapıyı kopyalamanın ne kadar pahalı olabileceğini hayal edin! Ayrıca, uygulama sürümünü güncellememiz gerektiğinde, bunu her istemci için dağıtmamız gerekir, böylece dağıtım da yavaşlar .
Uygulama için çok sayıda istemcimizin olduğu bir senaryoda yardımcı olabilecek başka bir yaklaşım daha var: çok kiracılı mimari. Bu, kiracı olarak adlandırdığımız birden fazla istemcimiz olduğu, ancak hepsinin uygulamanın yalnızca bir örneğini kullandığı anlamına gelir.
Bu mimari, her istemci için özel örneklerin yüksek maliyeti sorununu çözerken, yeni bir sorunu da beraberinde getiriyor: Müşteri verilerinin diğer istemcilerden güvenli bir şekilde izole edildiğinden nasıl emin olabiliriz?
Aşağıdaki yaklaşımları tartışacağız:
Paylaşılan veritabanı ve paylaşılan veritabanı şeması kullanma: Her veritabanı tablosuna eklememiz gereken yabancı anahtarla verilere hangi kiracının sahip olduğunu tespit edebiliriz.
Paylaşılan bir veritabanı ancak ayrı veritabanı şemaları kullanma: Bu şekilde, birden fazla veritabanı örneğini korumamıza gerek kalmayacak, ancak iyi düzeyde kiracı veri yalıtımı elde edeceğiz.
Ayrı veritabanları kullanma: tek kiracılı örneğe benzer görünüyor ancak aynı olmayacak çünkü yine de paylaşılan bir uygulama örneği kullanacağız ve kiracıyı kontrol ederek hangi veritabanının kullanılacağını seçeceğiz.
Bu fikirlerin derinliklerine inelim ve bunları Django uygulamasıyla nasıl entegre edeceğimizi görelim.
Bu seçenek akla ilk gelen olabilir: tablolara bir Yabancı Anahtar eklemek ve bunu her kiracı için uygun verileri seçmek amacıyla kullanmak. Ancak çok büyük bir dezavantajı var: kiracıların verileri hiçbir şekilde izole edilmiyor, bu nedenle küçük bir programlama hatası, kiracının verilerinin yanlış istemciye sızması için yeterli olabilir.
Django belgelerinden veritabanı yapısına bir örnek alalım:
from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)
Hangi kayıtların hangi kiracıya ait olduğunu belirlememiz gerekecek. Bu nedenle, mevcut her tabloya bir Tenant
tablosu ve bir yabancı anahtar eklememiz gerekiyor:
class Tenant(models.Model): name = models.CharField(max_length=200) class Question(models.Model): tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") class Choice(models.Model): tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)
Kodu biraz basitleştirmek için oluşturduğumuz her modelde tekrar kullanılacak soyut bir temel model oluşturabiliriz.
class Tenant(models.Model): name = models.CharField(max_length=200) class BaseModel(models.Model): tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) class Meta: abstract = True class Question(BaseModel): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") class Choice(BaseModel): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)
Gördüğünüz gibi burada en az iki büyük risk var: Bir geliştirici yeni modele kiracı alanı eklemeyi unutabilir veya geliştirici verileri filtrelerken bu alanı kullanmayı unutabilir.
Bu örneğin kaynak kodu GitHub'da bulunabilir: https://github.com/bp72/django-multitenancy-examples/tree/main/01_shared_database_shared_schema .
Paylaşılan şemanın risklerini akılda tutarak başka bir seçeneği değerlendirelim: veritabanı hâlâ paylaşılacak ancak her kiracı için özel bir şema oluşturacağız. Uygulama için popüler bir kütüphane olan Django-tenants'a ( belgeler ) bakabiliriz.
Küçük projemize django-tenants
ekleyelim (resmi kurulum adımları burada bulunabilir).
İlk adım pip
aracılığıyla kütüphane kurulumudur:
pip install django-tenants
Modelleri değiştirin: Tenant
modeli artık ayrı bir uygulamada olacak Question
ve Choice
modellerinin artık kiracıyla bağlantısı olmayacak. Farklı kiracıların verileri ayrı şemalarda olacağından, artık bireysel kayıtları kiracı satırlarına bağlamamız gerekmeyecek.
Tenants/models.py dosyası
from django.db import models from django_tenants.models import TenantMixin, DomainMixin class Tenant(TenantMixin): name = models.CharField(max_length=200) # default true, schema will be automatically created and synced when it is saved auto_create_schema = True class Domain(DomainMixin): # a required table for django-tenants too ...
anketler/models.py dosyası
from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)
Soru ve Seçimin artık Kiracı için yabancı bir anahtara sahip olmadığına dikkat edin!
Değişen diğer şey ise Kiracının artık ayrı bir uygulamada olmasıdır: Bu yalnızca etki alanlarını ayırmak için değil, aynı zamanda tenants
tablosunu paylaşılan şemada saklamamız gerekeceğinden ve her kiracı için polls
tabloları oluşturulacağından da önemlidir. şema.
Birden fazla şema ve kiracıyı desteklemek için settings.py
dosyasında değişiklikler yapın:
DATABASES = { 'default': { 'ENGINE': 'django_tenants.postgresql_backend', # .. } } DATABASE_ROUTERS = ( 'django_tenants.routers.TenantSyncRouter', ) MIDDLEWARE = ( 'django_tenants.middleware.main.TenantMainMiddleware', #... ) TEMPLATES = [ { #... 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.request', #... ], }, }, ] SHARED_APPS = ( 'django_tenants', # mandatory 'tenants', # 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 = ( # your tenant-specific apps 'polls', ) INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS] TENANT_MODEL = "tenants.Tenant" TENANT_DOMAIN_MODEL = "tenants.Domain"
Şimdi geçişleri oluşturup uygulayalım:
python manage.py makemigrations
python manage.py migrate_schemas --shared
Sonuç olarak public şemanın oluşturulacağını ve sadece paylaşılan tabloları içereceğini göreceğiz.
public
şema için varsayılan bir kiracı oluşturmamız gerekecek:
python manage.py create_tenant --domain-domain=default.com --schema_name=public --name=default_tenant
Sorulursa is_primary
değerini True
olarak ayarlayın.
Daha sonra hizmetin gerçek kiracılarını oluşturmaya başlayabiliriz:
python manage.py create_tenant --domain-domain=tenant1.com --schema_name=tenant1 --name=tenant_1 python manage.py create_tenant --domain-domain=tenant2.com --schema_name=tenant2 --name=tenant_2
Artık veritabanında polls
tablolarını içeren 2 şema daha olduğuna dikkat edin:
Artık kiracılar için kurduğunuz etki alanlarındaki API'leri çağırdığınızda farklı şemalardan Sorular ve Seçenekler alacaksınız - hepsi bu kadar!
Mevcut uygulamayı taşırsanız kurulum daha karmaşık ve belki daha da zor görünse de, yaklaşımın kendisi hala verilerin güvenliği gibi birçok avantaja sahiptir.
Örneğin kodunu burada bulabilirsiniz.
Bugün tartışacağımız son yaklaşım daha da ileri giderek kiracılar için ayrı veri tabanlarına sahip olmaktır.
Bu sefer birkaç veri tabanımız olacak:
Kiracının veritabanlarının adlarıyla eşleşmesi gibi paylaşılan verileri default_db
saklayacağız ve her kiracı için ayrı bir veritabanı oluşturacağız.
Daha sonra settings.py dosyasında veritabanlarının yapılandırmasını ayarlamamız gerekecek:
DATABASES = { 'default': { 'NAME': 'default_db', ... }, 'tenant_1': { 'NAME': 'tenant_1', ... }, 'tenant_2': { 'NAME': 'tenant_2', ... }, }
Artık her kiracının verilerini QuerySet yöntemini using
çağırarak alabileceğiz:
Questions.objects.using('tenant_1')…
Yöntemin dezavantajı, tüm geçişleri her bir veritabanına aşağıdakileri kullanarak uygulamanız gerekmesidir:
python manage.py migrate --database=tenant_1
Ayrıca, her kiracı için yeni bir veritabanı oluşturmak, django-tenants
kullanımına veya paylaşılan şema yaklaşımında olduğu gibi yalnızca yabancı anahtar kullanımına kıyasla daha az uygun olabilir.
Öte yandan, kiracının verilerinin izolasyonu gerçekten iyidir: veritabanları fiziksel olarak ayrılabilir. Diğer bir avantajımız ise django-tenants
gerektirdiği gibi sadece Postgresql kullanmakla sınırlı kalmayacağız, ihtiyaçlarımıza uygun herhangi bir motoru seçebiliyoruz.
Çoklu veritabanı konusu hakkında daha fazla bilgiyi Django belgelerinde bulabilirsiniz.
| Tek kiracılı | Paylaşılan şemayla MT | Ayrı şemaya sahip MT | Ayrı veritabanlarına sahip MT |
---|---|---|---|---|
Veri izolasyonu | ✅Yüksek | ❌En düşük | ✅Yüksek | ✅Yüksek |
Yanlışlıkla veri sızıntısı riski | ✅Düşük | ❌Yüksek | ✅Düşük | ✅Düşük |
Altyapı maliyeti | ❌Her kiracıyla daha yüksek | ✅Alt | ✅Alt | ✅❌ Tek kiracılıya göre daha düşük |
Dağıtım hızı | ❌Her kiracıyla daha düşük | ✅ | ✅❌ Her şema için yürütülmesi gerektiğinden taşıma işlemleri daha yavaş olacaktır | ✅❌ Her veritabanı için yürütülmesi gerektiğinden taşıma işlemleri daha yavaş olacaktır |
Uygulaması kolay | ✅ | ❌ Hizmet zaten tek kiracılı bir uygulama olarak uygulanmışsa çok sayıda değişiklik gerektirir | ✅ | ✅ |
Yukarıdakilerin tümünü özetlemek gerekirse, Görünüşe göre sorun için sihirli bir değnek yok, her yaklaşımın artıları ve eksileri var, bu nedenle hangi ödünleşimi yapabileceklerine karar vermek geliştiricilere kalmış.
Ayrı veritabanları, kiracının verileri için en iyi yalıtımı sağlar ve uygulanması kolaydır, ancak bakım için size daha yüksek bir maliyet getirir: n veritabanının güncellenmesi, veritabanı bağlantı sayıları daha yüksektir.
Uygulanması gereken ayrı bir şema biti kompleksine sahip paylaşılan bir veritabanıdır ve geçişle ilgili bazı sorunlar yaşayabilir.
Tek kiracı, uygulanması en basit olanıdır ancak kiracı başına hizmetinizin tam bir kopyasına sahip olduğunuz için kaynak aşırı tüketimi nedeniyle size maliyet getirir.