🏢 معماری چند-مستأجره (Multi-Tenancy) با EF Core (قسمت ۱):
یک دیتابیس برای همه اپلیکیشنهای مدرن SaaS (نرمافزار به عنوان سرویس) یک ویژگی مشترک دارن: چند-مستأجری (Multi-Tenancy).
یعنی یک اپلیکیشن به چندین مشتری (مستأجر یا tenant) سرویس میده، ولی دادههای هر مشتری کاملاً از بقیه ایزوله هست. امروز در قسمت اول از این سری، با این معماری و روش پیادهسازی "یک دیتابیس برای همه" با EF Core آشنا میشیم.
1️⃣ دو رویکرد اصلی برای چند-مستأجری
برای ایزوله کردن دادههای مشتریان، دو راه اصلی وجود داره:
🧠 ایزولهسازی منطقی (Logical Isolation):
یک دیتابیس واحد برای همه مشتریان، که دادهها با یک شناسه مثل TenantId از هم جدا میشن. (موضوع این پست)
🏬 ایزولهسازی فیزیکی (Physical Isolation):
یک دیتابیس کاملاً مجزا برای هر مشتری.
انتخاب بین این دو، به نیاز پروژه بستگی داره. برای صنایعی مثل حوزه سلامت که به ایزولهسازی بالا نیاز دارن، روش دوم الزامیه.
2️⃣ ابزار اصلی ما: EF Core Query Filters 🔍
برای پیادهسازی روش "یک دیتابیس برای همه"، ابزار اصلی ما در EF Core، فیلترهای کوئری سراسری (Global Query Filters) هست. این قابلیت به ما اجازه میده یه شرط WHERE رو به صورت خودکار به تمام کوئریهای یک انتیتی خاص اضافه کنیم. یک بار پیادهسازیش میکنیم و دیگه تقریباً فراموشش میکنیم!
3️⃣ پیادهسازی قدم به قدم
برای این کار به دو چیز نیاز داریم: ۱. راهی برای شناسایی مستأجر فعلی. ۲. راهی برای فیلتر کردن دادهها برای اون مستأجر.
• قدم اول: شناسایی مستأجر با TenantProvider
اول از همه، باید بفهمیم درخواست فعلی برای کدوم مستأجره. ما این منطق رو تو یه کلاس جدا به اسم TenantProvider کپسوله میکنیم. این کلاس، TenantId رو از هدر (X-TenantId) درخواست HTTP میخونه.
public sealed class TenantProvider
{
private const string TenantIdHeaderName = "X-TenantId";
private readonly IHttpContextAccessor _httpContextAccessor;
public TenantProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string TenantId => _httpContextAccessor
.HttpContext?
.Request
.Headers[TenantIdHeaderName];
}
💡نکته: راههای امنتر دیگری هم برای گرفتن TenantId وجود داره مثل JWT Claim یا API Key.
• قدم دوم: اعمال فیلتر سراسری در DbContext
حالا که میتونیم مستأجر فعلی رو پیدا کنیم، باید به EF Core بگیم که تمام کوئریها رو بر اساس اون فیلتر کنه. بهترین جا برای این کار، متد OnModelCreating در DbContext ماست.
public class OrdersDbContext : DbContext
{
private readonly string _tenantId;
public OrdersDbContext(
DbContextOptions<OrdersDbContext> options,
TenantProvider tenantProvider)
: base(options)
{
// TenantId رو از TenantProvider میگیریم
_tenantId = tenantProvider.TenantId;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// این فیلتر به صورت خودکار به تمام کوئریهای انتیتی Order اضافه میشه
modelBuilder
.Entity<Order>()
.HasQueryFilter(o => o.TenantId == _tenantId);
}
}
حالا هر کوئریای که روی جدول Order زده بشه، EF Core به صورت خودکار شرط WHERE TenantId = 'current_tenant_id' رو بهش اضافه میکنه!
🔖 هشتگها:
#CSharp #ASPNETCore #DotNet #MultiTenancy #EntityFrameworkCore #SoftwareArchitecture #Backend