paint-brush
Django SaaS Mimarisi: Tek Kiracılı mı Çok Kiracılı mı - Hangisi Size Uygun?ile@pbityukov
2,998 okumalar
2,998 okumalar

Django SaaS Mimarisi: Tek Kiracılı mı Çok Kiracılı mı - Hangisi Size Uygun?

ile Pavel Bityukov9m2023/11/01
Read on Terminal Reader

Çok uzun; Okumak

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.
featured image - Django SaaS Mimarisi: Tek Kiracılı mı Çok Kiracılı mı - Hangisi Size Uygun?
Pavel Bityukov HackerNoon profile picture

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.

Tek Kiracılı Mimari

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.

Özel uygulama ve veritabanı örneklerine sahip tek kiracılı mimari


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 .

Çok Kiracılı Mimari

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.

Paylaşılan uygulama ve veritabanı örneğine sahip çok kiracılı mimari

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:

  1. 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.


  2. 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.


  3. 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.

Paylaşılan Şema ile Paylaşılan Veritabanı

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 .

Ayrı Şemalarla Paylaşılan Veritabanı

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.

İlk migrate_schemas komutunun çalıştırılmasından sonraki veritabanı yapısı

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:

Kiracıları oluşturduktan sonraki veritabanı şeması

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.

Ayrı Veritabanları

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ılar için ayrı veritabanları


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.

Karşılaştırmak


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

Çözüm

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.