Geçtiğimiz hafta üretim düzeyinde, multi-tenant bir SaaS projesi açık kaynak olarak yayımlandı. İşte içeride neler olduğunu ve yapılandırma kararlarını inceleyelim.
Schoolytics ile Tanışın
Schoolytics ile Tanışın
Schoolytics, okullar için açık kaynak bir yardım masasıdır. Tek bir platform, bir bölgedeki tüm okulları çalıştırır — her okul, kendi alt alanında izole bir biçimde, markalı bir portal çalıştırır ve kendi kullanıcıları, sorunları ve CSAT verileri ile yönetilir.
Zendesk, Linear ve ebeveyn-öğretmen uygulamasının birleşimi — MIT lisanslı, kendi sunucularında barındırılabilir ve sonsuza dek ücretsizdir.
- 🏫 Multi-tenant — tek bir dağıtım, sınırsız okul, alt alan ile izole
- 🔑 Parolasız ebeveyn portalı — tek kullanımlık erişim kodları, uygulama yok, hesap yok
- 🤖 AI önceliklendirmesi — Python tabanlı duygu analizi mikroservisi, her sorunu teslim sırasında puanlar
- 🧑💼 Üç katmanlı RBAC — yönetici / şube yöneticisi / personel, sorgularda ve politikada zorunlu
- 📧 Tam e-posta yığını — işlem e-postaları, uygulama içi bildirimler, CSAT anketleri
- 🎛 Nova süper yöneticisi — yeni bir okulu (kiracı + alan + yönetici + 10 kategori + demo verisi) tek tıklama ile provision eder
Teknoloji Yığını
Teknoloji Yığını
| Katman | Seçim | Neden |
|---|---|---|
| Framework | Laravel 12 / PHP 8.2 | En hızlı iterasyon, Nova, ilk taraf kuyruk/mail/auth |
| DB | PostgreSQL 16 | Satır bazında çok kiracılık, büyük/küçük harfe duyarsız ilike, kontrol kısıtlamaları |
| Multi-tenancy | stancl/tenancy v3 | Savaş-teste tabi, alt alan tabanlı, satır düzeyinde kapsam |
| RBAC | Spatie Permission (takım modu) | Takım kapsamlı izinler = kiracı kapsamı için ücretsiz |
| Super-admin | Laravel Nova | Yerleşik kaynak CRUD, provision için özel eylemler |
| Frontend | Blade + Alpine + Tailwind + ECharts | SPA yükü yok, hızlı çalışır, sunucuda render eder |
| Kuyruk | Veri tabanı sürücüsü (geliştirme), Redis/SQS (üretim) | Yerel olarak bir parça daha az hareketli parça |
| AI | FastAPI (Python) | Izole, değiştirilebilir, Laravel’i şişirmez |
İlginç Kararlar
İlginç Kararlar
1. İki kimlik doğrulama koruma mekanizması, sıfır örtüşme
1. İki kimlik doğrulama koruma mekanizması, sıfır örtüşme
Birçok multi-tenant uygulama, süper yöneticileri aynı users tablosunda is_superadmin bayrağı ile yerleştirir. Bu, her bir kiracı sorgusunun merkezi bir kullanıcıyı döndürmesine neden olabilecek bir hata oluştuğunda hatadır.
Schoolytics iki tamamen ayrı modele sahiptir:
// Koruma: merkez → giriş: /nova
class CentralUser extends Authenticatable { /* tablo: central_users */ }
// Koruma: web → giriş: /admin/login (kiracı alt alanı)
class User extends Authenticatable {
use BelongsToTenant, HasRoles;
// tablo: users (satır düzeyinde tenant_id)
}
CentralUser Spatie rollerini alamaz (takım modu, kiracı bağlamı gerektirir), bu nedenle IssuePolicy‘de bir before() kancası vardır:
public function before(?Authenticatable $user): ?bool
{
if ($user instanceof CentralUser) return true; // tam erişim
return null; // normal politika yöntemlerine geç
}
İki koruma mekanizması. Sıfır çapraz kontaminasyon. Süper admin, asla bir kiracı rolüne ihtiyaç duymadan her şeyi hata ayıklayabilir.
2. Satır düzeyinde çok kiracılık, iki katmanlı uygulanmıştır
2. Satır düzeyinde çok kiracılık, iki katmanlı uygulanmıştır
Her kiracıya özel model, şu işlemleri gerçekleştiren BelongsToTenant trait’ini kullanır:
- Global bir kapsam ekler,
WHERE tenant_id = tenant('id')filtresi uygular - Yaratma işlemi sırasında otomatik dolum yapar
Ama global kapsamların yalnızca birine güvenmiyorum. Hassas işlemler için ayrıca bir kontrol yapıyorum:
abort_unless($issue->tenant_id === tenant('id'), 404);
Derin savunma. Eğer biri kazara withoutGlobalScopes() çağrısını yaparsa, açık kontrol bunu yakalar.
3. Parolasız ebeveyn akışı
3. Parolasız ebeveyn akışı
Ebeveynler teknik değillerdir. Hesap istemezler. Parolalarını kaybederler. Bu nedenle:
- Admin, tablo listesini (CSV/Excel) içe aktarıyor
- Sistem bir
AccessCodeoluşturur, e-posta/SMS gönderir - Ebeveyn
schoola.domain.comadresini ziyaret eder, kodu girer, sorununu iletir - Sadece bir açık sorunun olması zorunludur (kapalı olmayan sorun kontrolü ile sağlanır)
- Sorun kapandığında,
access_code.used_atsıfırlanır — ebeveyn tekrar aynı kodla iletebilir
UX, “büyülü bağlantı” ile aynı ama senkronize ve denetlenebilir.
4. Rol kapsamına alınmış sorgular tek bir yerde
4. Rol kapsamına alınmış sorgular tek bir yerde
Üç rol, üç farklı dilim görmektedir:
- admin — kiracının her sorunu
- branch_manager — yönettikleri şubelerdeki sorunlar
- staff — yalnızca kendilerine atanan sorunlar
Bir sorgu kapsamı bunların hepsini yönetir:
public function scopeVisibleTo(Builder $q, User $user): Builder
{
if ($user->hasRole('admin')) return $q;
<span class="k">if</span> <span class="p">(</span><span class="nv">$user</span><span class="o">-></span><span class="nf">hasRole</span><span class="p">(</span><span class="s1">'branch_manager'</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nv">$q</span><span class="o">-></span><span class="nf">whereIn</span><span class="p">(</span><span class="s1">'branch_id'</span><span class="p">,</span> <span class="nv">$user</span><span class="o">-></span><span class="n">branches</span><span class="o">-></span><span class="nf">pluck</span><span class="p">(</span><span class="s1">'id'</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$q</span><span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'assigned_user_id'</span><span class="p">,</span> <span class="nv">$user</span><span class="o">-></span><span class="n">id</span><span class="p">);</span>
}
Her Issue::query() her denetleyicide ->visibleTo(auth()->user()) çağırır. Bir kez unutursanız, politika yine de işlemi engeller. İki katmanlı güvenlik.
5. Ayrı bir mikroservis aracılığıyla AI önceliklendirmesi
5. Ayrı bir mikroservis aracılığıyla AI önceliklendirmesi
Bir ebeveyn bir sorun ilettiğinde, kuyruk dinleyicisi, FastAPI servisine POST gönderir:
Laravel ──event──► PerformAiAnalysis (queued) ──HTTP──► Python FastAPI
│
▼
{duygu, kategori, güven}
│
▼
IssueAiAnalysis satırı
Neden ayrı? Çünkü:
- Python’un ML kütüphaneleri Python’da, PHP’de değil
- Modeli değiştirilebilir yapabilirim (sonradan ince ayar yapabilirim) Laravel’i etkilemeden
- HTTP sınırı, beni hata modları (time-out, tekrar denemeler, devre kesiciler) hakkında düşünmeye yönlendirir
- Duygu analizi servisi durumsuzdur — kendi başına yatay olarak ölçeklenebilir
6. Gerçekten zamanı tasarruf eden Nova eylemleri
6. Gerçekten zamanı tasarruf eden Nova eylemleri
5 adımlık manuel kiracı kurulumunun yerine ProvisionTenant her şeyi yapar:
Tenantsatırı oluşturDomainsatırı oluştur (alt alan)- Kiralayıcı bağlamına geçin, kiracı göçlerini çalıştırın
- Varsayılan rolleri (admin, branch_manager, staff) ekleyin
- 10 varsayılan sorun kategorisini ekleyin
School+ varsayılanBrancholuşturun- Rastgele parolaya sahip ilk admin
Useroluşturun - Yöneticiye kimlik bilgilerini e-posta ile gönderin
TenantProvisionedMailüzerinden
Bir tıklama. Otuz saniye. Yeni bir okul canlı.
GenerateDemoData daha havalı — bir kiracıya, 10 varsayılan kategoriyi karşılayan gerçekçi veri tohumlar (örneğin Transport “Otobüs geç kaldı”, Academics “Matematik ödeviyle ilgili endişe”) oluşturarak, şube yöneticileri, atanan personeller, ebeveynler ve öğretmenler ile karşılaşan açık bir sorun oluşturur. Ürünü bir okula 60 saniyede tanıtabilirsiniz.
7. Kuyruk + kiracılık tuzağı
7. Kuyruk + kiracılık tuzağı
Beni yeterince zorladığı için, kendi postmortem yazımına sahip. Kısa: asla bir kiracıya özel Eloquent modelini kuyruk işinin yapıcı kısmına koymayın. Basit değerler geçirin, handle() içinde tenancy()->initialize() çağrısını yapın.
Repo İçeriği
Repo İçeriği
app/
Http/Controllers/ → kiracı + genel portal denetleyicileri
Models/ → User, Issue, RosterContact, AccessCode, ...
Policies/ → IssuePolicy, RosterContactPolicy, ...
Nova/ → Kiracı, Alan, CentralUser kaynakları + eylemler
Jobs/ → AnalyzeIssueSentiment, LogActivityJob
Listeners/ → PerformAiAnalysis
Mail/ → 6 işlem e-postası
database/
migrations/ → merkezi şema
migrations/tenant/ → her kiracı için şema
seeders/ → roller, varsayılan kategoriler, demo verisi
routes/
web.php → /nova + merkezi admin
tenant.php → /admin + kamu portalı (her kiracı için)
~30 göç, ~40 model, ~20 denetleyici, ~15 politika, tam Nova seti.
Gelecek Sefer Ne Farklı Yapardım
Gelecek Sefer Ne Farklı Yapardım
- Önce Redis ile kuyruğa başla — veritabanı sürücüsü, demo günü geldiğinde AI işleri biriktiğinde pek iyi değil
- Kiralama özelliği testini ÖNCE yaz — aslında bir kuyruk çalışanını kiracı bağlamı olmadan başlatan test. Kuyruk tuzağını dakikalar içinde yakalardı, saatler yerine.
- Admin için Livewire yerine Blade+Alpine kullanın — filtre formu dansı çok sıkıcı oluyor; Livewire, kodun yarısını azaltırdı
- Ebeveyn portalı için Inertia kullanın — SPA benzeri bir hissiyatın UX açısından fayda sağlayacağı tek yer burası
Deneyin
Deneyin
git clone https://github.com/sharjeelz/eliflammeem-git.git
cd eliflammeem-git
composer install && npm install
cp .env.example .env && php artisan key:generate
php artisan migrate && php artisan db:seed
composer run dev
Daha sonra http://central.lvh.me:8000/nova adresini açın. Bir kiracı provision edin. Tüm sürecin canlandığını izleyin.
Katkıda Bulunun
Katkıda Bulunun
⭐ eğer bu sizlere bir hafta sonu kazandırdıysa repoyu yıldızlayın. PR’ler hoş karşılanır – yol haritası README’de:
- Arapça + RTL kullanıcı arayüzü
- Erişim kodu teslimatı için WhatsApp İş API’si
- Personel için SSO
- Her kiracı için özel markalaşma
🔗 github.com/sharjeelz/eliflammeem-git
Siz farklı olarak ne yapardınız? Yorum bırakın — gerçekten duymak istiyorum.
Kaynak: Orijinal Makale
- Schoolytics ile Tanışın
- Teknoloji Yığını
- İlginç Kararlar
- 1. İki kimlik doğrulama koruma mekanizması, sıfır örtüşme
- 2. Satır düzeyinde çok kiracılık, iki katmanlı uygulanmıştır
- 3. Parolasız ebeveyn akışı
- 4. Rol kapsamına alınmış sorgular tek bir yerde
- 5. Ayrı bir mikroservis aracılığıyla AI önceliklendirmesi
- 6. Gerçekten zamanı tasarruf eden Nova eylemleri
- 7. Kuyruk + kiracılık tuzağı
- Repo İçeriği
- Gelecek Sefer Ne Farklı Yapardım
- Deneyin
- Katkıda Bulunun


