home / blog / Laravel'de Çoklu Kiracılık: Kapsamlı Bir...
Laravel Jan 15, 2026

Laravel'de Çoklu Kiracılık: Kapsamlı Bir Mimari Rehberi

Laravel'de kiracı başına veritabanı ve tek veritabanı stratejileri kullanarak ölçeklenebilir çoklu kiracılı uygulamalar oluşturmayı pratik kod örnekleriyle öğrenin.

Laravel'de Çoklu Kiracılık: Kapsamlı Bir Mimari Rehberi

Neden Multi-Tenancy?

Multi-Tenancy, tek bir uygulama örneğinin birden fazla müşteriye (tenant) hizmet verdiği ve her birinin kendi izole verisine sahip olduğu bir mimari desendir. Bunu bir apartman binası gibi düşünün -- tek bina, birçok daire, her birinin kendi kilidi ve anahtarı var.

Bir SaaS ürünü geliştiriyorsanız, kaçınılmaz olarak şu kararla yüzleşeceksiniz: tenant verilerini nasıl izole edebilirim? Bu kararın güvenlik, performans, maliyet ve karmaşıklık açısından büyük etkileri vardır.

Üç Strateji

1. Tek Veritabanı, Paylaşımlı Şema

Her tabloda bir tenant_id sütunu bulunur. Bu en basit yaklaşımdır ancak disiplin gerektirir -- tek bir where koşulunu unutursanız veri sızıntısı yaşarsınız.

// A global scope handles tenant filtering automatically
class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        if (auth()->check()) {
            $builder->where($model->getTable() . '.tenant_id', auth()->user()->tenant_id);
        }
    }
}

// Apply it via a trait
trait BelongsToTenant
{
    protected static function bootBelongsToTenant(): void
    {
        static::addGlobalScope(new TenantScope);

        static::creating(function (Model $model) {
            if (auth()->check()) {
                $model->tenant_id = auth()->user()->tenant_id;
            }
        });
    }
}

// Usage is invisible to the developer
class Invoice extends Model
{
    use BelongsToTenant;
}

// This query is automatically scoped
Invoice::all(); // SELECT * FROM invoices WHERE tenant_id = 1

Artıları: Basit dağıtım, kolay yedekleme, düşük maliyet.
Eksileri: Veri sızıntısı riski, bireysel tenant'ları ölçeklendirmek daha zor, gürültülü komşu problemi.

2. Tek Veritabanı, Ayrı Şemalar

Her tenant kendi veritabanı şemasına (veya ön ekine) sahip olur. Bu, birden fazla veritabanı yükü olmadan daha iyi izolasyon sağlar.

// Dynamically set the table prefix per tenant
class TenantDatabaseManager
{
    public function connect(Tenant $tenant): void
    {
        config(['database.connections.tenant.prefix' => "tenant_{$tenant->id}_"]);
        DB::purge('tenant');
        DB::reconnect('tenant');
    }
}

3. Tenant Başına Veritabanı

Her tenant kendi veritabanına sahip olur. Maksimum izolasyon, ancak en yüksek operasyonel karmaşıklık.

// config/database.php — the tenant connection is dynamic
'tenant' => [
    'driver' => 'mysql',
    'database' => '', // Set at runtime
    'host' => env('DB_HOST', '127.0.0.1'),
    // ...
],

// Tenant middleware resolves and connects
class IdentifyTenant
{
    public function handle(Request $request, Closure $next)
    {
        $tenant = Tenant::where('domain', $request->getHost())->firstOrFail();

        config([
            'database.connections.tenant.database' => "tenant_{$tenant->id}",
        ]);

        DB::purge('tenant');
        app()->instance(Tenant::class, $tenant);

        return $next($request);
    }
}

Tenant Tanımlama Stratejileri

Bir isteğin hangi tenant'a ait olduğunu nasıl anlarsınız? Yaygın yaklaşımlar:

  • Subdomain: acme.uygulamaniz.com -- temiz, profesyonel, ayrıştırması kolay
  • Yol ön eki: uygulamaniz.com/acme/dashboard -- daha basit DNS yapılandırması
  • Özel alan adı: app.acme.com -- wildcard SSL veya alan adı başına sertifika gerektirir
  • İstek başlığı: X-Tenant-ID: acme -- API'ler için yaygın
// Subdomain resolver
class SubdomainTenantResolver implements TenantResolver
{
    public function resolve(Request $request): ?Tenant
    {
        $subdomain = explode('.', $request->getHost())[0];

        return Tenant::where('slug', $subdomain)->first();
    }
}

Migration Yönetimi

Tenant başına veritabanı yaklaşımında, migration'ları tüm tenant veritabanlarında çalıştırmanız gerekir:

// artisan command: php artisan tenants:migrate
class TenantMigrate extends Command
{
    protected $signature = 'tenants:migrate {--tenant=}';

    public function handle(): void
    {
        $tenants = $this->option('tenant')
            ? Tenant::where('id', $this->option('tenant'))->get()
            : Tenant::all();

        $tenants->each(function (Tenant $tenant) {
            $this->info("Migrating tenant: {$tenant->name}");

            config(['database.connections.tenant.database' => "tenant_{$tenant->id}"]);
            DB::purge('tenant');

            Artisan::call('migrate', [
                '--database' => 'tenant',
                '--path' => 'database/migrations/tenant',
                '--force' => true,
            ]);

            $this->info(Artisan::output());
        });
    }
}

Hangi Stratejiyi Seçmelisiniz?

Faktör Paylaşımlı Şema Ayrı Şema Ayrı Veritabanı
İzolasyon Düşük Orta Yüksek
Karmaşıklık Düşük Orta Yüksek
Maliyet Düşük Orta Yüksek
Tenant Sayısı Binlerce Yüzlerce Onlardan Yüzlere
Uyumluluk (GDPR) Daha Zor Daha Kolay En Kolay

Paylaşımlı şema yaklaşımıyla başlayın. Yalnızca yasal düzenlemeler veya belirli bir yüksek değerli tenant gerektirdiğinde ayrı veritabanlarına geçin. Erken izolasyon, aşırı mühendisliğin bir biçimidir.

Temel Çıkarımlar

  • Tenant izolasyonunu otomatik olarak uygulamak için global scope kullanın
  • Her zaman tenant'lar arası veri sızıntısı olmadığını doğrulayan testler yazın
  • Tenant başına veritabanı kurulumları için stancl/tenancy gibi paketleri kullanmayı düşünün
  • Tenant tanımlama işlemini önbelleğe alın -- her istekte gerçekleşir
  • 100 tenant'ınız olmadan önce migration stratejinizi planlayın
tum yazilara don