Kararlı kalan B2B organizasyon ve takım veritabanı şeması
B2B organizasyonlar ve takımlar için veritabanı şeması: davetler, üyelik durumları, rol mirası ve denetime uygun değişiklikler için pratik bir ilişkisel desen.

Bu şema deseni hangi sorunu çözer
Çoğu B2B uygulaması aslında sadece “kullanıcı hesapları” uygulaması değildir. İnsanların bir organizasyona ait olduğu, takımlara ayrıldığı ve işlerine göre farklı izinlere sahip olduğu paylaşılan çalışma alanlarıdır. Satış, destek, finans ve yöneticiler farklı erişimlere ihtiyaç duyar; ve bu erişimler zamanla değişir.
Aşırı basit bir model hızla bozulur. Eğer tek bir users tablosu ve tek bir role sütunu tutarsanız, “aynı kişi bir orgda Admin, diğerinde Viewer” gibi durumları ifade edemezsiniz. Ayrıca yalnızca bir takımı görmesi gereken yükleniciler veya bir projeden ayrılan ama hâlâ şirkete bağlı bir çalışan gibi yaygın senaryoları da yönetemezsiniz.
Davetler de sık hata kaynağıdır. Bir davet sadece bir e-posta satırıysa, o kişinin orgda "içeride" olup olmadığı, hangi takıma katılacağı ve farklı bir e-postayla kaydolursa ne olacağı belirsizleşir. Küçük tutarsızlıklar burada güvenlik sorunlarına dönüşebilir.
Bu desen dört hedefe odaklanır:
- Güvenlik: izinler varsayımlardan değil, açık üyelikten gelir.
- Berraklık: orglar, takımlar ve rollerin her birinin tek bir gerçek kaynağı olur.
- Tutarlılık: davetler ve üyelikler öngörülebilir bir yaşam döngüsünü takip eder.
- Geçmiş: kimin erişim verdiğini, rolleri değiştirdiğini veya birini kaldırdığını açıklayabilirsiniz.
Vaat edilen şey, özellikler büyüdükçe anlaşılabilir kalan tek bir ilişkisel modeldir: bir kullanıcı için birden fazla org, her org için birden fazla takım, öngörülebilir rol mirası ve denetime uygun değişiklikler. Bugün uygulayabileceğiniz ve daha sonra yeniden yazmaya gerek kalmadan genişletebileceğiniz bir yapı.
Ana terimler: orglar, takımlar, kullanıcılar ve üyelikler
Altı ay sonra hâlâ okunur bir şema istiyorsanız, birkaç terimde uzlaşmayla başlayın. Çoğu karışıklık “birinin kim olduğu” ile “ne yapabildiği”nin karışmasından gelir.
Bir Organization (org) en üst kiracı sınırıdır. Müşteriyi veya verinin sahibi olan iş hesabını temsil eder. İki kullanıcı farklı orglardaysa, varsayılan olarak birbirlerinin verisini görmemelidir. Bu tek kural, kazara çapraz-tenant erişimin çoğunu önler.
Bir Team org içindeki daha küçük bir gruptur. Takımlar gerçek çalışma birimlerini modelller: Sales, Support, Finance veya “Proje A”. Takımlar org sınırının yerini almaz; onun altında yaşarlar.
Bir User bir kimliktir. Giriş ve profildir: e-posta, isim, şifre veya SSO ID ve belki MFA ayarları. Bir kullanıcı herhangi bir erişim olmadan da var olabilir.
Bir Membership erişim kaydıdır. “Bu kullanıcı bu orgun üyesidir (isteğe bağlı olarak bu takıma da) bu durum ve bu rollerle” sorusunu cevaplar. Kimlik (User) ile erişimi (Membership) ayırmak, yükleniciler, offboarding ve çok-org erişimini modellemeyi kolaylaştırır.
Koda ve UI’a yansıyabilecek basit anlamlar:
- Member: bir orgda veya takımda aktif üyeliğe sahip kullanıcı.
- Role: izin demetinin adı (örneğin Org Admin, Team Manager).
- Permission: tek bir izinli eylem (örneğin “faturaları görüntüle”).
- Tenant boundary: verinin bir org ile sınırlanma kuralı.
Üyeliği basit bir durum makinesi olarak ele alın, boolean değil. Tipik durumlar: invited, active, suspended ve removed. Bu, davetleri, onayları ve offboarding'i tutarlı ve denetlenebilir kılar.
Tek ilişkisel model: temel tablolar ve ilişkiler
İyi bir çok kiracılı şema tek bir fikirle başlar: “kim nerede ait” bilgisini tek bir yerde saklayın ve her şeyi destekleyici tablolar olarak tutun. Böylece temel soruları (orgda kim var, bir takımda kim var, ne yapabilirler) cevaplarken alakasız modellere atlamazsınız.
Genellikle ihtiyaç duyduğunuz temel tablolar:
- organizations: müşteri hesabı için bir satır. İsim, durum, fatura alanları ve değişmeyen bir id tutar.
- teams: organizasyon içindeki gruplar (Support, Sales, Admin). Her zaman bir organization'a aittir.
- users: kişi başına bir satır. Bu globaldir, org-bağımsızdır.
- memberships: bu kullanıcının hangi organizasyona (isteğe bağlı olarak hangi takıma) ait olduğunu söyleyen köprü.
- role_grants (veya role_assignments): bir üyeliğin hangi rollere sahip olduğunu, org düzeyinde, takım düzeyinde veya her ikisinde gösterir.
Anahtarları ve kısıtları sıkı tutun. Her tablo için surrogate primary key (UUID veya bigint) kullanın. Ardından teams.organization_id -> organizations.id ve memberships.user_id -> users.id gibi yabancı anahtarlar ekleyin. Üretimde görülmeden önce çoğaltmaları durduracak birkaç unique kısıtı da ekleyin.
Çoğu kötü veriyi erken yakalayan kurallar:
- Bir org slug veya dış anahtarı:
unique(organizations.slug) - Org başına takım isimleri:
unique(teams.organization_id, teams.name) - Yinelenen org üyeliği yok:
unique(memberships.organization_id, memberships.user_id) - Yinelenen takım üyeliği yok (eğer takım üyeliğini ayrı modelliyorsanız):
unique(team_memberships.team_id, team_memberships.user_id)
Hangi alanların append-only, hangilerinin güncellenebilir olduğuna karar verin. Organizations, teams ve users güncellenebilir olmalıdır. Memberships genellikle mevcut durum için güncellenebilir (active, suspended) ama değişiklikler ayrıca append-only bir erişim günlüğüne yazılmalıdır ki denetimler daha sonra kolay olsun.
Tutarlı kalan davetler ve üyelik durumları
Erişimi temiz tutmanın en kolay yolu daveti kendi kaydı olarak ele almaktır; yarım kalmış bir membership gibi davranmayın. Bir membership “kullanıcının şu anda ait olduğunu” söyler. Bir invitation ise “erişim teklif edildi ama gerçek değil” demektir. Bunları ayırmak hayalet üyeler, yarım oluşan izinler ve “bunu kim davet etti?” gibi gizemleri önler.
Basit, güvenilir bir durum modeli
Üyelikler için herkesin anlayabileceği küçük bir durum seti kullanın:
- active: kullanıcı orga (ve üyeyse takımlara) erişebilir
- suspended: geçici olarak engellenmiş, ama geçmiş korunur
- removed: artık üye değil, denetim ve raporlama için saklanır
Birçok ekip üyelikte “invited” durumunu tutmaktan kaçınır ve “invited”ı yalnızca invitations tablosunda tutar. Bu daha temiz olur: membership satırları yalnızca gerçekten erişimi olan (active) veya eskiden olan (suspended/removed) kullanıcılar için vardır.
Hesap yokken e-posta ile davetler
B2B uygulamaları genellikle e-posta ile, hesabı olmayan kişileri davet eder. Davet kaydında e-postayı, davetin uygulandığı yeri (org veya takım), hedeflenen rolü ve göndereni saklayın. Kişi daha sonra aynı e-postayla kaydolursa, bekleyen davetlerle eşleştirebilir ve kabul etmesine izin verebilirsiniz.
Bir davet kabul edildiğinde, bunu tek bir işlem içinde yönetin: daveti kabul edilmiş olarak işaretleyin, üyeliği oluşturun ve bir denetim girdisi yazın (kimi, ne zaman ve hangi e-posta kullanıldığını belirtin).
Davet için açık bitiş durumlarını tanımlayın:
- expired: süresi dolmuş ve kabul edilemez
- revoked: bir admin tarafından iptal edilmiş ve kabul edilemez
- accepted: üyeliğe dönüştürülmüş
“Org veya takım başına e-posta için yalnızca bir bekleyen davet” kuralını uygulayarak duplicate davetleri engelleyin. Yeniden davet destekliyorsanız, ya mevcut bekleyen davetin süresini uzatın ya da eskisini iptal edip yeni bir token verin.
Erişimi kafa karıştırmadan rol ve miras yönetimi
Çoğu B2B uygulaması iki erişim seviyesine ihtiyaç duyar: birinin organizasyon genelinde ne yapabildiği ve belirli bir takım içinde ne yapabildiği. Bunları tek bir role sütununda karıştırmak uygulamayı tutarsızlaştırır.
Org seviyesindeki roller fatura yönetimi yapma, insanları davet etme veya tüm takımları görme gibi soruları cevaplar. Takım seviyesindeki rollerse belirli bir takımda düzenleme yapma, istekleri onaylama veya sadece görüntüleme gibi soruları cevaplar.
Rol mirası, aşağıdaki tek kuralla yaşanması en kolay olandır: bir org rolü açık olmayan bir takımların aksine her yerde geçerlidir, ancak bir takım açıkça aksi belirtirse takım rolü önceliklidir. Bu davranışı öngörülebilir tutar ve veri çoğaltmayı azaltır.
Bunu modellemenin temiz yolu rol atamalarını kapsamıyla birlikte saklamaktır:
role_assignments:user_id,org_id, opsiyonelteam_id(NULL org-genel demek),role_id,created_at,created_by
Eğer “her kapsam için bir rol” isterseniz, (user_id, org_id, team_id) üzerine unique kısıt ekleyin.
Bir takım için efektif erişim şöyle hesaplanır:
-
Önce takım-spesifik atamayı arayın (
team_id = X). Varsa onu kullanın. -
Yoksa org-genel atamaya geri dönün (
team_id IS NULL).
En az ayrıcalık varsayılanları için, minimal bir org rolü (genellikle “Member”) seçin ve ona gizli admin yetkileri vermeyin. Yeni kullanıcılara gizlice takım erişimi vermeyin; eğer otomatik tahsis yapıyorsanız bunu açıkça team membership oluşturarak yapın.
İstisnalar nadir ve görünür olmalıdır. Örnek: Maria orgda “Manager” (davet edebilir, raporları görebilir) ama Finance takımında “Viewer” olmalı. Maria için bir org-genel atama ve Finance için bir takım-kapsamlı istisna saklayın. İzin kopyalamayın; istisna görünür olsun.
Rol isimleri yaygın desenler için iyi çalışır. Gerçek tekil durumlar için (örneğin "export yapabilir ama düzenleyemez") veya uyumluluk gereksinimleri olduğunda açık izinler kullanın. Yine de aynı kapsam fikrini koruyun ki zihinsel model tutarlı kalsın.
Denetim-dostu değişiklikler: erişimi kim değiştirdiğini kaydetmek
Eğer uygulamanız yalnızca membership satırında mevcut rolü saklıyorsa, hikâyeyi kaybedersiniz. Birisi “Alex'e geçen salı kim admin verdi?” diye sorduğunda güvenilir cevabınız olmaz. Değişiklik geçmişine, sadece mevcut duruma değil, ihtiyacınız var.
En basit yaklaşım, erişim olaylarını kaydeden ayrı bir denetim tablosudur. Bunu append-only günlük gibi düşünün: eski denetim satırlarını düzenlemeyin; sadece yeni satır ekleyin.
Pratik bir denetim tablosunda genellikle şunlar bulunur:
actor_user_id(değişikliği yapan)subject_typevesubject_id(membership, team, org gibi)action(invite_sent, role_changed, membership_suspended, team_deleted)occurred_at(ne zaman oldu)reason(isteğe bağlı serbest metin, örneğin “contractor offboarding”)
“önce” ve “sonra”yı yakalamak için ilgilendiğiniz alanların küçük bir anlık görüntüsünü saklayın. Bunun erişim-kontrol verileriyle sınırlı olmasına dikkat edin, tam kullanıcı profilleri değil. Örneğin: before_role, after_role, before_state, after_state, before_team_id, after_team_id. Daha esnek isterseniz iki JSON sütunu (before, after) kullanabilirsiniz; fakat yükü küçük ve tutarlı tutun.
Memberships ve takımlar için soft delete genellikle hard delete'den daha iyidir. Satırı kaldırmak yerine deleted_at ve deleted_by gibi alanlarla devre dışı bırakın. Bu yabancı anahtarları sağlam tutar ve geçmiş erişimi açıklamayı kolaylaştırır. Hard delete yalnızca gerçekten geçici kayıtlar (örneğin süresi dolmuş davetler) için mantıklı olabilir, ama daha sonra ihtiyacınız olmayacağından emin olun.
Bunlar olduğunda yaygın uyumluluk sorularına hızlıca cevap verebilirsiniz:
- Kim erişim verdi veya kaldırdı, ve ne zaman?
- Tam olarak ne değişti (role, team, state)?
- Erişim normal bir offboarding akışının parçası olarak mı kaldırıldı?
Adım adım: ilişkisel veritabanında şemayı tasarlamak
Basit başlayın: kimin neye ait olduğunu ve nedenini söyleyen tek bir yer. Küçük adımlarla inşa edin ve verinin “neredeyse doğru” hale gelmesini engelleyecek kurallar ekleyin.
PostgreSQL ve diğer ilişkisel veritabanlarında iyi çalışan pratik sıra:
-
organizationsveteamsoluşturun, her biri stabil bir birincil anahtar (UUID veya bigint) ile.teams.organization_idyabancı anahtarı ekleyin ve takım isimlerinin org içinde benzersiz olup olmayacağına erken karar verin. -
usersu üyelikten ayrı tutun. Kimlik alanlarınıusersta (email, status, created_at) tutun. “Hangi org/takıma ait” bilgisinimembershipstablosundauser_id,organization_id, opsiyonelteam_idve birstatesütunu (active, suspended, removed) ile saklayın. -
invitationsı kendi tablosu olarak ekleyin, membership üzerine bir sütun koymayın.organization_id, opsiyonelteam_id,email,token,expires_atveaccepted_atsaklayın. “Org + email + takım için bir açık davet” için benzersizlik sağlayın ki çoğaltmalar oluşmasın. -
Rolleri açık tablolarla modelleyin. Basit bir yaklaşım
roles(admin, member vb.) ve organ/zaman kapsamında işaret edenrole_assignmentskullanmaktır;team_idNULL ise org-geneldir, set ise takım-spesifiktir. Miras kurallarını tutarlı ve test edilebilir tutun. -
Daha başından bir denetim izi ekleyin.
access_eventstablosu ileactor_user_id,target_user_id(veya davetler için e-posta),action(invite_sent, role_changed, removed),scope(org/team) vecreated_atsaklayın.
Bu tablolar oluşturulduktan sonra birkaç temel admin sorgusu çalıştırın: “kimlerin org-genel erişimi var?”, “hangi takımların yöneticisi yok?”, “hangi davetler süresi dolmuş ama hâlâ açık?”. Bu sorular genellikle eksik kısıtları erken ortaya çıkarır.
Karışık veriyi önleyen kurallar ve kısıtlar
Bir şema düzenli kaldığında veritabanı, sadece kod değil, kiracı sınırlarını uygular. En basit kural: tenant-kapsamlı her tablo org_id taşısın ve her aramada bu kullanılsın. Birisi uygulamada filtreyi unutursa bile veritabanı çapraz-org bağlantılarına direnç göstermeli.
Veriyi temiz tutan guardrail'lar
Başlangıç olarak her zaman aynı org içinde işaret eden yabancı anahtarlar koyun. Örneğin, takım üyeliğini ayrı tutuyorsanız, team_memberships satırı team_id ve user_id referanslamalı ama ayrıca org_id de taşımalı. Bileşik anahtarlarla referans verilen takımın aynı org'a ait olduğunu zorunlu kılabilirsiniz.
En yaygın problemlerin önüne geçen kısıtlar:
- Her kullanıcı için org başına bir aktif üyelik:
(org_id, user_id)üzerinde unique, uygun olursa aktif satırlar için partial condition ile. - Org veya takım başına e-posta için bir bekleyen davet:
(org_id, team_id, email)üzerindestate = 'pending'koşuluyla unique. - Davet token'ları global olarak benzersiz ve yeniden kullanılmaz:
invite_tokenüzerinde unique. - Takım tam olarak bir org'a aittir:
teams.org_idNOT NULL veorgs(id)e foreign key. - Üyelikleri silmek yerine sonlandırın:
ended_at(ve isteğe bağlıended_by) saklayın ki denetim geçmişi korunsun.
Gerçek sorgular için indeksleme
Uygulamanızın sık çalıştırdığı sorgulara göre indeksleyin:
(org_id, user_id)“bu kullanıcı hangi orglarda?” için(org_id, team_id)“bu takımın üyelerini listele” için(invite_token)“daveti kabul et” için(org_id, state)“bekleyen davetler” ve “aktif üyeler” için
Org isimlerini taşınabilir tutun. Her yerde değişmeyen orgs.id kullanın ve orgs.name (ve slug) düzenlenebilir alanlar olsun. Yeniden adlandırma sadece bir satırı değiştirir.
Bir takımı orglar arasında taşımak genellikle politika kararıdır. En güvenli seçenek bunu yasaklamaktır (veya takımı klonlamaktır) çünkü üyelikler, roller ve denetim geçmişi org-odaklıdır. Eğer taşımaya izin verecekseniz, bunu tek bir işlemde yapın ve org_id taşıyan tüm alt satırları güncelleyin.
Kullanıcılar ayrıldığında yetim kayıtları önlemek için hard delete'den kaçının. Kullanıcıyı devre dışı bırakın, üyeliklerini sonlandırın ve ebeveyn satırlarda ON DELETE RESTRICT bırakın; sadece gerçekten kademeli kaldırmak istediğiniz yerlerde cascade kullanın.
Örnek senaryo: bir org, iki takım, erişimi güvenle değiştirme
Northwind Co adında bir şirketi ve iki takımını düşünün: Sales ve Support. Bir ay boyunca Support biletlerinde yardımcı olacak bir müteahhit Mia'yı işe alıyorlar. Model burada öngörülebilir kalmalıdır: bir kişi, bir org üyeliği, isteğe bağlı takım üyelikleri ve net durumlar.
Bir org yöneticisi (Ava) Mia'yı e-posta ile davet eder. Sistem orga bağlı, pending statüsünde ve bir sona erme tarihli bir invitation satırı oluşturur. Henüz başka bir şey değişmez; yarım kalmış bir üyelik olmadığı için erişim belirsiz değildir.
Mia kabul ettiğinde, davet accepted olarak işaretlenir ve active durumlu bir org membership satırı oluşturulur. Ava Mia'ya org rolü olarak member (admin değil) atar. Ardından Mia'ya Support takım üyeliği eklenir ve support_agent gibi bir takım rolü atanır.
Bir bükülme ekleyin: Ben tam zamanlı bir çalışan ve org rolü admin, ama Support verilerini görmemeli. Bunu Support için açıkça düşüren bir takım düzeyi override ile ele alabilirsiniz; org-genel admin yetkileri org ayarları için geçerli kalırken Support için takım rolü düşürülmüş olur.
Bir hafta sonra Mia politika ihlali yapar ve askıya alınır. Satırları silmek yerine Ava Mia'nın org membership durumunu suspended yapar. Takım üyelikleri yerinde kalabilir ama org üyeliği aktif olmadığı için etkisiz hale gelir.
Her değişiklik bir olay olarak kaydedildiğinden denetim geçmişi temiz kalır:
- Ava Mia'yı davet etti (kim, ne, ne zaman)
- Mia daveti kabul etti
- Ava Mia'yı Support'a ekledi ve
support_agentatadı - Ava Ben'in Support override'ını ayarladı
- Ava Mia'yı askıya aldı
Bu modelle UI açık bir erişim özeti gösterebilir: org durumu (active veya suspended), org rolü, roller ve override'lar ile takım listesi ve bir “Son erişim değişiklikleri” akışı ki birinin Sales veya Support'u görüp görmediğinin nedenini açıklasın.
Yaygın hatalar ve tuzaklar
Çoğu erişim hatası “neredeyse doğru” veri modellerinden gelir. İlk başta şema iyi görünür, sonra kenar durumlar birikir: yeniden davetler, takım taşımaları, rol değişiklikleri ve offboarding.
Yaygın bir tuzak, davetleri ve üyelikleri aynı satırda karıştırmaktır. “invited” ve “active”i aynı kayıtta barındırırsanız, “kabul etmediyse bu kişi üye midir?” gibi imkânsız sorular ortaya çıkar. Davetleri ve üyelikleri ayırın veya durum makinesini açık ve tutarlı yapın.
Başka sık hata: kullanıcı tablosuna tek bir role sütunu koyup işi bitmiş saymak. Roller genellikle kapsamlıdır (org rolü, takım rolü, proje rolü). Global bir rol, “kullanıcı bir müşteride admin ama diğerinde salt-okuma” gibi hack'leri zorunlu kılar ve çok kiracılı beklentileri bozar.
İleride zarar veren tuzaklar:
- Yanlışlıkla cross-org takım üyeliğine izin vermek (team_id org A'ya, membership org B'ye işaret ediyor)
- Üyelikleri hard delete edip “geçen hafta kim erişimi vardı?” sorusunu yanıtsız bırakmak
- Benzersizlik kurallarının eksik olması ve kullanıcının aynı erişimi birden fazla satırla alması
- Mirasın sessizce yığılmasına izin vermek (org admin + takım üyesi + override) böylece kimse erişimin nedenini açıklayamaz
- “Davet kabul edildi”yi UI olayı olarak görmek, veritabanı gerçeği olarak değil
Hızlı bir örnek: bir yüklenici bir orga davet edilir, Team Sales'e katılır, sonra kaldırılır ve bir ay sonra yeniden davet edilir. Eğer eski satırı üzerine yazarsanız geçmişi kaybedersiniz. Eğer çoğaltmalara izin verirseniz iki aktif üyelik ortaya çıkabilir. Net durumlar, scoped roller ve doğru kısıtlar her ikisini de engeller.
Uygulamaya entegre etme için hızlı kontroller ve sonraki adımlar
Kodlamadan önce modelinizi kağıt üzerinde hızlıca gözden geçirin ve hâlâ mantıklı olup olmadığını kontrol edin. İyi bir çok kiracılı erişim modeli sıkıcı hissettirmeli: aynı kurallar her yerde geçerli olur ve “özel durumlar” nadirdir.
Yaygın boşlukları yakalayacak hızlı checklist:
- Her membership tam olarak bir user ve bir org'a işaret eder; kopyayı önlemek için unique kısıt vardır.
- Davet, üyelik ve kaldırma durumları açık (null ile ima edilmemiş) ve geçişler sınırlıdır (örneğin süresi dolmuş bir daveti kabul edemezsiniz).
- Roller tek bir yerde saklanır ve efektif erişim tutarlı şekilde hesaplanır (miras kuralları dahil, eğer kullanıyorsanız).
- Org/teams/users silmek geçmişi yok etmez (denetim izleri veya soft delete alanları kullanın).
- Her erişim değişikliği actor, hedef, kapsam, zaman damgası ve neden/kaynak ile bir denetim olayı üretir.
Gerçek sorularla tasarımı baskı testine tabi tutun. Bu soruları tek bir sorgu ve açık bir kural ile cevaplayamıyorsanız muhtemelen bir kısıt veya ek durum gerekir:
- Bir kullanıcı iki kez davet edilirse ve sonra e-posta değişirse ne olur?
- Bir takım yöneticisi bir org sahibini o takımdan çıkarabilir mi?
- Bir org rolü tüm takımlara erişim veriyorsa, bir takım onu geçersiz kılabilir mi?
- Bir davet rol değişikliğinden sonra kabul edilirse hangi rol geçerli olur?
- Destek “erişimi kim kaldırdı?” diye sorduğunda hızlıca kanıt gösterebiliyor musunuz?
Yöneticilerin ve destek ekiplerinin anlaması gerekenleri yazın: üyelik durumları (ve onları tetikleyenler), kim davet/çıkarabilir, rol mirasının düz anlamı ve bir olay sırasında denetim olaylarına nereden bakılacağı.
Önce kısıtları uygulayın (unique'ler, foreign key'ler, izin verilen geçişler), sonra iş mantığını bunların etrafında kurun ki veritabanı sizi dürüst tutmaya yardımcı olsun. Mirasın açık/kapalı olması, varsayılan roller, davet süresi gibi politika kararlarını kod sabitleri yerine konfigürasyon tablolarında tutun.
Eğer her şeyi elle backend ve admin ekranı yazmadan kurmak istiyorsanız, AppMaster (appmaster.io) bu tabloları PostgreSQL'de modellemenize ve davet/üyelik geçişlerini açık iş süreçleri olarak uygulamanıza yardımcı olabilir; aynı zamanda üretim dağıtımları için gerçek kaynak kodu üretebilir.
SSS
Kullanıcının global kimliğine değil, bir org (ve isteğe bağlı olarak bir takıma) bağlı rolleri ve erişimi bağlamak için ayrı bir membership kaydı kullanın. Böylece aynı kişi bir orgda Admin, diğerinde Viewer olabilir; hack'lere gerek kalmaz.
Ayırın: bir invitation e-posta, kapsam ve sona erme içeren bir teklif satırıdır; bir membership ise kullanıcının gerçekten erişimi olduğu anlamına gelir. Bu, “hayalet üye”, belirsiz durum ve e-posta değişimlerinde güvenlik açıklarını önler.
Çoğu B2B uygulaması için active, suspended ve removed gibi küçük bir set yeterlidir. Eğer “invited” durumunu yalnızca invitations tablosunda tutarsanız, membership satırları net olur: mevcut veya geçmiş erişimi gösterir, bekleyen erişimi değil.
Org rolleri ve takım rolleri, kapsamla birlikte assignment olarak saklanmalı (team_id NULL ise org-genel, set ise takım-spesifik). Bir takım için erişim kontrolü yaparken önce takım-spesifik atamayı kontrol edin; yoksa org-genel atamaya geri dönün.
Basit ve öngörülebilir bir kuralla başlayın: org rolleri varsayılan olarak her yerde geçerlidir; takım rolleri yalnızca açıkça ayarlandığında geçersiz kılar. İstisnaları nadir ve görünür tutun ki insanlar erişimi tahmin etmek zorunda kalmasınlar.
Bir unique kısıtla “her org/takım için e-posta başına yalnızca bir bekleyen davet” sağlayın ve açık pending/accepted/revoked/expired yaşam döngüsünü uygulayın. Yeniden davet gerekiyorsa mevcut bekleyen davetin süresini uzatın veya eskisini iptal edip yeni bir token verin.
Her tenant-kapsamlı satır org_id taşımalı ve yabancı anahtar/kısıtlar org karışmasını engellemeli (örneğin bir takımın membership içinde referans verilen takımın aynı org'a ait olduğunu zorunlu kılmak). Bu, uygulama kodunda unutulan filtrelerin zararını azaltır.
Kim ne zaman neyi değiştirdiğini, kime yönelik olduğunu, hangi kapsamda olduğunu ve zamanı kaydeden ekli bir erişim olay günlüğü (append-only) tutun. Değişiklik öncesi/sonrası kritik alanları (role, state, team) saklayın ki “geçen salı kim admin verdi?” sorusuna güvenilir cevap verilebilsin.
Üyelikler ve takımlar için hard delete yerine soft delete tercih edin; böylece geçmiş sorgulanabilir kalır ve yabancı anahtarlar bozulmaz. Davetler için de token'ları yeniden kullanmayacak şekilde saklamak genellikle iyi bir fikirdir.
Sıcak yollarınızı indeksleyin: org üyelik kontrolleri için (org_id, user_id), takım üye listeleri için (org_id, team_id), davet kabulü için (invite_token), ve yönetici ekranları için (org_id, state) gibi. İndeksleri gerçek sorgularınıza göre belirleyin.


