سناریویی را در نظر بگیرید که یک برنامه وب نوشته شده، قرار است به چندین
مستاجر (مشتری یا tenant) خدماتی را ارائه کند. در این حالت اطلاعات هر مشتری
به صورت کاملا جدا شده از دیگر مشتریان در سیستم قرار دارد و فقط به همان
قسمتها دسترسی دارد.
مثلا یک برنامه مدیریت رستوران را در نظر بگیرید که برای هر مشتری، در دامین
مخصوص به خود قرار دارد و همه آنها به یک سیستم متمرکز متصل شده و اطلاعات
خود را از آنجا دریافت میکنند.
در معماری Multi-Tenancy، چندین کاربر میتوانند از یک نمونه (Single
Instance) از اپلیکیشن نرمافزاری استفاده کنند. یعنی این نمونه روی سرور
اجرا میشود و به چندین کاربر سرویس میدهد. هر کاربر را یک Tenant
مینامیم. میتوان به Tenantها امکان تغییر و شخصیسازی بخشی از اپلیکیشن
را داد؛ مثلا امکان تغییر رنگ رابط کاربری و یا قوانین کسبوکار، اما آنها نمیتوانند
کدهای اپلیکیشن را شخصیسازی کنند.
بدون داشتن دانش کافی، پیاده سازی معماری multi tenant میتواند تبدیل یه
یک چالش بزرگ شود. مخصوصا در نسخهی قبلی ASP.NET که یکپارچه نبودن فریم ورکهای مختلف میتوانست باعث ایجاد چندین پیاده سازی مختلف در برنامه شود.
موضوع وقتی پیچیدهتر میشد که شما قصد داشتید در یک برنامه چندین فریم ورک
مختلف مثل SignalR, MVC, Web API را مورد استفاده قرار دهید.
خوشبختانه اوضاع با وجود OWIN بهتر شده و ما در این مطلب قصد استفاده از یک تولکیت را به نام
SaasKit، برای پیاده سازی این معماری در ASP.NET Core داریم. هدف از این toolkit،
سادهتر کردن هر چه بیشتر ساخت برنامههای SaaS (Software as a Service)
هست. با استفاده از OWIN ما قادریم که بدون در نظر گرفتن فریم ورک مورد
استفاده، رفتار مورد نظر خودمان را مستقیما در یک چرخه درخواست HTTP پیاده
سازی کنیم و البته به لطف طراحی خاص ASP.NET Core 1.0 و استفاده از میان
افزارهایی مشابه OWIN در برنامه، کار ما با SaasKit باز هم راحتتر خواهد
بود.
شروع کار
یک پروژه ASP.NET Core جدید را ایجاد کنید و سپس ارجاعی را به فضای نام SaasKit.Multitenancy (موجود در
Nuget) بدهید.
PM> Install-Package SaasKit.Multitenancy
بعد از اینکار ما باید به SaasKit اطلاع دهیم
که چطور مستاجرهای ما را شناسایی کند.
شناسایی مستاجر (tenant)
اولین جنبه در معماری multi-tenant، شناسایی مستاجر بر اساس اطلاعات درخواست جاری میباشد که میتواند از hostname ، کاربر جاری یا یک HTTP header باشد.
ابتدا به تعریف کلاس مستاجر میپردازیم:
public class AppTenant
{
public string Name { get; set; }
public string[] Hostnames { get; set; }
}
سپس از طریق پیاده سازی اینترفیس ITenantResolver و نوشتن یک tenant resolver به SaasKit اطلاع میدهیم که چطور مستاجر جاری را بر اساس اطلاعات درخواست جاری شناسایی کند و در صورتیکه موفق به شناسایی شود، یک وهله از نوع <TenantContext<TTenant را بازگشت خواهد داد.
public class AppTenantResolver : ITenantResolver<AppTenant>
{
IEnumerable<AppTenant> tenants = new List<AppTenant>(new[]
{
new AppTenant {
Name = "Tenant 1",
Hostnames = new[] { "localhost:6000", "localhost:6001" }
},
new AppTenant {
Name = "Tenant 2",
Hostnames = new[] { "localhost:6002" }
}
});
public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context)
{
TenantContext<AppTenant> tenantContext = null;
var tenant = tenants.FirstOrDefault(t =>
t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower())));
if (tenant != null)
{
tenantContext = new TenantContext<AppTenant>(tenant);
}
return tenantContext;
}
}
در نظر داشته باشید که اینجا ما اطلاعات مستاجر را از روی hostname استخراج کردیم؛ اما از آنجا که شما به شیء HttpContext دسترسی کاملی دارید، میتوانید از هر چیزی که مایل باشید استفاده کنید؛ مثل URL، اطلاعات کاربر، هدرهای HTTP و غیره. در اینجا فعلا مشخصات مستاجرهای خودمان را در کد نوشتیم. اما شما میتوانید در برنامه خودتان این اطلاعات را از فایل تنظیمات برنامه و یا یک بانک اطلاعاتی دریافت کنید.
سیم کشی کردن
بعد از پیاده سازی این اینترفیس نوبت به سیم کشیهای SaasKit میرسد. من در اینجا سعی کردم که مثل الگوی برنامههای ASP.NET Core عمل کنم. ابتدا نیاز داریم که وابستگیهای SaasKit را ثبت کنیم. فایل startups.cs را باز کنید و کدهای زیر را در متد ConfigureServices اضافه نمایید:
public void ConfigureServices(IServiceCollection services)
{
services.AddMultitenancy<AppTenant, AppTenantResolver>();
}
سپس باید میان افزار SaasKit را ثبت کنیم. کدهای زیر را به متد Configure اضافه کنید:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// after .UseStaticFiles()
app.UseMultitenancy<AppTenant>();
// before .UseMvc()
}
دریافت مستاجر جاری
حالا هر جا که نیاز به وهلهای از شیء مستاجر جاری داشتید، میتوانید به روش زیر عمل کنید:
public class HomeController : Controller
{
private AppTenant tenant;
public HomeController(AppTenant tenant)
{
this.tenant = tenant;
}
}
به عنوان مثال قصد داریم نام مستاجر را در عنوان سایت نمایش دهیم. برای
اینکار ما از قابلیت جدید MVC Core یعنی تزریق سرویسها به View استفاده
خواهیم کرد. در فایل Layout.cshtml_ تکه کد زیر را به بالای صفحه اضافه کنید:
@inject AppTenant Tenant;
این کد، AppTenant را برای ما در تمامی Viewها از طریق شی Tenant قابل دسترس میکند. حالا میتوانیم در View خود از جزییات مستاجر به شکل زیر استفاده کنیم:
<a asp-controller="Home" asp-action="Index">@Tenant.Name</a>
اجرای نمونه مثال
فایل project.json را باز کنید و مقدار web را به شکل زیر مقدار دهی کنید: (در اینجا برای سایت خود 3 آدرس را نگاشت کردیم)
"commands": {
"web": "Microsoft.AspNet.Server.Kestrel --server.urls=http://localhost:6000;http://localhost:6001;http://localhost:6002",
},
سپس کنسول را در محل ریشه پروژه باز نموده و دستور زیر را اجرا کنید:
حال اگر در مرورگر خود آدرس http://localhost:6000 را وارد کنیم، مستاجر 1 را خواهیم دید:
و اگر آدرس http://localhost:6002 را وارد کنیم، مستاجر 2 را مشاهده میکنیم:
قابل پیکربندی کردن مستاجر ها
از آنجائیکه نوشتن مشخصات مستاجرها در کد زیاد جالب نیست، برای همین
تصمیم داریم که این مشخصات را با استفاده از قابلیتهای ASP.NET Core از
فایل appsettings.json دریافت کنیم. تنظیمات مستاجرها را مطابق اطلاعات زیر به این فایل اضافه کنید:
"Multitenancy": {
"Tenants": [
{
"Name": "Tenant 1",
"Hostnames": [
"localhost:6000",
"localhost:6001"
]
},
{
"Name": "Tenant 2",
"Hostnames": [
"localhost:6002"
]
}
]
}
سپس کلاسی را که بیانگر تنظیمات چند مستاجری باشد، مینویسیم:
public class MultitenancyOptions
{
public Collection<AppTenant> Tenants { get; set; }
}
حالا نیاز داریم که به برنامه اعلام کنیم تا تنظیمات مورد نیاز خود را از فایل appsettings.json بخواند. کد زیر را به ConfigureServices اضافه کنید:
services.Configure<MultitenancyOptions>(Configuration.GetSection("Multitenancy"));
سپس کدهای resolver خود را جهت دریافت اطلاعات از MultitenancyOptions مطابق زیر تغییر میدهیم:
public class AppTenantResolver : ITenantResolver<AppTenant>
{
private readonly IEnumerable<AppTenant> tenants;
public AppTenantResolver(IOptions<MultitenancyOptions> options)
{
this.tenants = options.Value.Tenants;
}
public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context)
{
TenantContext<AppTenant> tenantContext = null;
var tenant = tenants.FirstOrDefault(t =>
t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower())));
if (tenant != null)
{
tenantContext = new TenantContext<AppTenant>(tenant);
}
return Task.FromResult(tenantContext);
}
}
برنامه را یکبار re-build کرده و اجرا کنید .
در آخر
اولین قدم در پیاده سازی یک معماری multi-tenant، تصمیم گیری درباره این
موضوع است که شما چطور مستاجر خود را شناسایی کنید. به محض این شناسایی شما میتوانید عملیاتهای بعدی خود را مثل تفکیک بخشی از برنامه، فیلتر کردن دادهای، نمایش یک view خاص برای هر مستاجر و یا بازنویسی قسمتهای مختلف
برنامه بر اساس هر مستاجر، انجام دهید.
_ سورس مثال بالا در گیت هاب قابل دریافت میباشد.
_ منبع: اینجا