Cross-Tenant Sorunu
Bir çok kiracılı (multi-tenant) B2B SaaS platformu geliştirdiğinizde, binlerce farklı şirket tam olarak aynı veritabanını paylaşır. Örneğin, invoices tablonuz, Apple, Microsoft ve Acme Corp gibi şirketlerin mali kayıtlarını barındırırken tek ayrım, tenant_id sütunu ile sağlanır.
Geliştiricilerin standart yaklaşımı, her bir Eloquent sorgusuna manuel olarak where('tenant_id', $user->tenant_id) eklemektir. Bu, mimari bir zaman bombası gibidir. Bir junior geliştirici bir API uç noktası yazarken bu spesifik where ifadesini eklemeyi unutursa, Invoice::all() sorgusu Acme Corp’un çalışanına Apple’ın faturalarını döndürebilir. Böylece felaket düzeyinde bir veri ihlali gerçekleşmiş olur. Sıfır güven (zero-trust) mimarilerini oluşturmak için insan hafızasına güvenmemelisiniz. Sınırları matematiksel olarak zorunlu hale getirmek için Global Scopes kullanmalısınız.
Çözüm: Mimari Güvenlik Şeritleri
Laravel’in Global Scopes’ları, belirli bir Eloquent modeli tarafından gerçekleştirilen her bir sorguya otomatik olarak bir kısıtlama tanımlamanıza olanak tanır. Geliştiricinin bunu eklemeyi hatırlamasına gerek yoktur; framework bunu sorgu oluşturucunun en temel seviyesinde zahmetsizce ekler.
Adım 1: Kapsamın Mimari Oluşumu
Öncelikle, geçerli kullanıcının Tenant ID’sini oturumdan veya API token’dan çıkartan ve sorguya ekleyen özel bir Scope sınıfı oluşturmalıyız.
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;
class TenantScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
// Eğer kullanıcı kimliği doğrulanmışsa, sorgunun sadece kendi kiracısı içinde arama yapmasını zorunlu kılarız
if (Auth::check()) {
$builder->where($model->getTable() . '.tenant_id', Auth::user()->tenant_id);
}
}
}
Adım 2: Kendini Uygulayan Trait
Bu kapsayı 50 farklı modelde manuel olarak kaydetmek yerine, yeniden kullanılabilir bir Trait oluşturuyoruz. Bu Trait, kapsayı otomatik olarak uygular ve yeni kayıtlar oluştururken tenant_id‘yi zahmetsizce ekler.
namespace App\Models\Traits;
use App\Models\Scopes\TenantScope;
use Illuminate\Support\Facades\Auth;
trait BelongsToTenant
{
protected static function bootBelongsToTenant()
{
// 1. Global olarak okuma sınırını uygula
static::addGlobalScope(new TenantScope);
// 2. Oluşturulurken tenant_id'yi otomatik olarak enjekte et (yazma sınırı)
static::creating(function ($model) {
if (Auth::check() && ! $model->tenant_id) {
$model->tenant_id = Auth::user()->tenant_id;
}
});
}
}
Adım 3: Kapsamlı Modeller
Artık geliştiricileriniz sadece model dosyasına bir satır ekleyecek. Eğer bir junior geliştirici Invoice::all() yazarsa, Laravel sorguyu yakalar, bunu SELECT * FROM invoices WHERE tenant_id = 5 şeklinde çevirir ve güvenli bir şekilde çalıştırır.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Traits\BelongsToTenant;
class Invoice extends Model
{
// Bu tek satır veri ihlallerini matematiksel olarak imkansız hale getirir.
use BelongsToTenant;
}
Mühendislik ROI’si
Global Scopes’ları zorunlu kılarak, veri güvenliğini insan sorumluluğundan framework garantisine kaydırmış olursunuz. API’lerinizdeki kiracı verisinin sızması riskini tamamen ortadan kaldırır, gereksiz where() ifadelerini temizleyerek kontrolcü mantığını basitleştirir ve kurumsal çok kiracılı bir yapının güvenli bir şekilde ölçeklenmesi için sağlam bir temel oluşturursunuz.
Kaynak: Orijinal Makale


