Laravel ile Multi-Tenancy SaaS Uygulama Geliştirme
Modern SaaS uygulamalarında tek bir kod tabanıyla birden fazla müşteriyi (tenant) yönetmek kritik bir ihtiyaç. Laravel bu konuda hem framework esnekliği hem de paket desteğiyle güçlü çözümler sunuyor.
Multi-Tenancy Nedir?
Multi-tenancy, tek bir uygulama üzerinden birden fazla müşteriye hizmet verme mimarisidir. Her müşteri (tenant) kendi verilerine sahiptir ama aynı kod tabanını paylaşır. SaaS modeli kuran herkes er geç bu mimariyle tanışır.
Basit bir örnekle açıklayalım: Bir proje yönetim aracı düşünün. Firma A ve Firma B aynı uygulamayı kullanıyor ama birbirlerinin verilerini görmüyor. İşte bu multi-tenancy.
Mimari Yaklaşımlar
1. Tek Veritabanı, Paylaşımlı Tablo (tenant_id)
En yaygın ve kolay yaklaşım. Her tabloya tenant_id sütunu eklenir:
Schema::create('projects', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
});
Global scope ile her sorgu otomatik filtrelenir:
class TenantScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
if (auth()->check()) {
$builder->where('tenant_id', auth()->user()->tenant_id);
}
}
}
Model'e ekleme:
class Project extends Model
{
protected static function booted(): void
{
static::addGlobalScope(new TenantScope);
static::creating(function ($model) {
$model->tenant_id = auth()->user()->tenant_id;
});
}
}
Avantajları: Basit kurulum, tek veritabanı yönetimi, kolay migration. Dezavantajları: Veri izolasyonu yazılım seviyesinde — bir bug tüm tenant verilerini açabilir.
2. Veritabanı Bazlı İzolasyon
Her tenant için ayrı veritabanı. En güçlü izolasyon:
// config/database.php
'connections' => [
'tenant' => [
'driver' => 'mysql',
'database' => '', // Runtime'da set edilir
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
],
],
Middleware ile tenant veritabanını set etme:
class SetTenantDatabase
{
public function handle(Request $request, Closure $next)
{
$tenant = $this->resolveTenant($request);
config(['database.connections.tenant.database' => $tenant->database]);
DB::purge('tenant');
DB::reconnect('tenant');
return $next($request);
}
}
Avantajları: Tam veri izolasyonu, bağımsız backup/restore, performans izolasyonu. Dezavantajları: Veritabanı sayısı arttıkça yönetim zorlaşır, migration her tenant için ayrı çalıştırılmalı.
3. Schema Bazlı İzolasyon (PostgreSQL)
PostgreSQL kullanıyorsanız her tenant için ayrı schema oluşturabilirsiniz:
DB::statement("CREATE SCHEMA tenant_{$tenant->id}");
DB::statement("SET search_path TO tenant_{$tenant->id}");
Tek veritabanı ama ayrı schema'lar — veritabanı bazlı ile paylaşımlı arasında bir orta yol.
Tenancy for Laravel Paketi
Sıfırdan yazmak yerine Tenancy for Laravel paketini kullanabilirsiniz:
composer require stancl/tenancy
php artisan tenancy:install
php artisan migrate
Tenant oluşturma:
$tenant = Tenant::create(['id' => 'firma-a']);
$tenant->domains()->create(['domain' => 'firma-a.projeman.net']);
Tenant context'inde çalıştırma:
$tenant->run(function () {
// Bu closure içinde tüm sorgular
// otomatik olarak tenant veritabanında çalışır
User::create([
'name' => 'Admin',
'email' => '[email protected]',
'password' => bcrypt('password'),
]);
});
Subdomain vs Path Bazlı Tenant Ayırımı
Subdomain Yaklaşımı
firma-a.projeman.net
firma-b.projeman.net
Route tanımı:
Route::domain('{tenant}.projeman.net')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
Path Yaklaşımı
projeman.net/firma-a/dashboard
projeman.net/firma-b/dashboard
Route::prefix('{tenant}')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
Subdomain daha temiz ve SEO dostu. Ama wildcard DNS ve SSL sertifikası (wildcard) gerektirir.
Dikkat Edilmesi Gerekenler
- Cache izolasyonu: Cache key'lerine
tenant_idprefix ekleyin. Aksi halde bir tenant'ın cache'i diğerine gider. - Queue izolasyonu: Job dispatch ederken tenant context'ini kaydedin ve job çalışırken restore edin.
- Dosya depolama: Storage path'e
tenant_idekleyin:storage/app/tenants/{tenant_id}/ - Migration yönetimi: Veritabanı bazlı izolasyonda her yeni migration tüm tenant'lara uygulanmalı.
Performans İpuçları
- Yüksek veri hacminde read replica kullanın
- Verileri
tenant_idbazlı index'leyin - Redis cache'i tenant bazlı namespace'leyin
- Queue worker'ları tenant bazlı ölçeklendirin
Ne Zaman Multi-Tenancy Kullanmalı?
Uygun senaryolar:
- SaaS iş modeli kuruyorsanız
- Birden fazla müşteriye tek uygulama ile hizmet verecekseniz
- Maliyet optimizasyonu önemliyse (her müşteriye ayrı sunucu pahalı)
Uygun olmayan senaryolar:
- Tek müşterili uygulamalar
- Müşteriler arası özelleştirme çok fazlaysa
- Yasal düzenlemeler tam fiziksel izolasyon gerektiriyorsa
Sonuç
Multi-tenancy SaaS dünyasında olmazsa olmaz bir mimari. Laravel hem sıfırdan implementasyon hem de Tenancy for Laravel gibi paketlerle güçlü destek sunuyor. Önemli olan: veri izolasyonu ve güvenliği doğru planlamak. tenant_id'yi unuttuğunuz tek bir sorgu, tüm veri güvenliğini riske atar.