· Admin

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_id prefix 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_id ekleyin: 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_id bazlı 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.