با پیشرفت بیشتر تکنولوژی وب در
سالهای اخیر و رشد کاربران فضای اینترنتی، خدمات و پیچیدگیهای بیشتری به نرم
افزارها اضافه شده و به همین دلیل استفاده از میکروسرویسها بجای حالت قدیمی
مونولوتیک (یک برنامه همه کاره) طرفداران بیشتری پیدا کردهاست. در این حالت برنامه به
قسمتهای خرد و مجزایی تبدیل شده و هر پروژه ساختار و تکنولوژی مخصوص به خود را
مدیریت میکند و در این بین با استفاده روشهای متفاوتی به ایجاد ارتباط با یکدیگر
میپردازند .
مشکلی که در این حالت میتواند رخ دهد، زیاد شدن مسیرهای متفاوت برای
اتصال به هر یک از سرویسها و سختتر شدن به روزرسانی این مسیرها میباشد. به همین
دلیل در این بخش،
نیاز به ابزاری میباشد تا بتوان از طریق آن، مسیردهی سادهای را ایجاد کرد و در
پشت صحنه مسیردهیهای متفاوتی را کنترل
نمود. با ایجاد چنین ابزاری در واقع شما API Gateway
ایجاد نمودهاید. یکی از معروفترین کتابخانههای این حوزه، Ocelot میباشد. کار با این ابزار بسیار ساده بوده و
امکانات بسیار زیاد و قدرتمندی را فراهم مینماید.
برای
اینکار ابتدا سه پروژه را میسازیم که موارد زیر را شامل میگردد:
پروژه
اول نوع Api : با دریافت Id در
اکشنمتد مورد نظر، شیء user
بازگردانده میشود:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public static List<User> GetUsers()
{
return new List<User>()
{
new()
{
Id = 1,
FirstName = "علی",
LastName = "یگانه مقدم",
UserName = "yeganehaym"
},
new ()
{
Id = 2,
FirstName = "وحید",
LastName = "نصیری",
UserName = "VahidN"
},
};
}
}
[ApiController]
[Route("/api/[controller]/{id?}")]
public class UserController : ControllerBase
{
[HttpGet]
public User GetUser(int id)
{
var users = Users.User.GetUsers();
var user = users.FirstOrDefault(x => x.Id == id);
return user;
}
}
پروژه
دوم نوع Api :
دریافت لیستی از محصولات:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Price { get; set; }
public int Quantity { get; set; }
public static List<Product> GetProducts()
{
return new List<Product>()
{
new()
{
Id = 1,
Name = "LCD",
Price = 20000,
Quantity = 10
},
new()
{
Id = 1,
Name = "Mouse",
Price = 320000,
Quantity = 15
},
new()
{
Id = 1,
Name = "Keyboard",
Price = 50000,
Quantity = 25
},
};
}
}
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
[HttpGet]
public List<Product> GetProducts()
{
return Product.GetProducts();
}
}
پروژه
سوم همان ApiGateway
هست و همینکه یک پروژهی وب خالی باشد، کفایت میکند. در این پروژه Ocelot را نصب نموده و سپس فایلی با نام ocelot.json را
با محتوای زیر به ریشهی پروژه همانند فایلهای appsettings.json
اضافه میکنیم:
{
"Routes":[
{
"DownstreamPathTemplate":"/api/User/{id}",
"DownstreamScheme":"https",
"DownstreamHostAndPorts":[
{
"Host":"localhost",
"Port":"7279"
}
],
"UpstreamPathTemplate":"/GetUser/{id}",
"UpstreamHttpMethod":[
"GET"
]},
{
"DownstreamPathTemplate":"/api/Product",
"DownstreamScheme":"https",
"DownstreamHostAndPorts":[
{
"Host":"localhost",
"Port":"7261"
}
],
"UpstreamPathTemplate":"/Products",
"UpstreamHttpMethod":[
"GET"
]
}
]
}
این فایلها شامل دو قسمتUpStream و DownStream میشوند. آپاستریمها در واقع
آدرسی است که شما قصد اتصال به آنرا دارید و قسمت داوناستریم، سرویس مقصدی است که
ocelot باید درخواست شما را به سمت
آن ارسال نماید. بهعنوان مثل شما با ارسال درخواستی به آدرس Products ،
در پشت صحنه به آدرس localhost:7261/api/product ارسال
میگردد. بدین صورت سیستم نهایی تنها به یک دامنه و آدرس منسجم ارسال شده، ولی در
پشت صحنه این آدرسها ممکن است به تعداد زیادی سرویس در آدرسهای متفاوتی ارسال گردند.
جهت
راه اندازی نهایی، کد زیر را به فایل Program.cs
اضافه میکنیم:
builder.Services.AddOcelot();
پس
از اضافه کردن پیکربندی و middleware آن،
کد زیر را نیز جهت شناسایی فایل ocelot به فایل Program.cs
نیز اضافه مینماییم:
builder.Configuration.SetBasePath(builder.Environment.ContentRootPath)
.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
همچنین
در صورت تمایل میتوانید کد را به شکل زیر هم نوشته تا بتوانید تنظیمات متفاوتی را برای محیط اجرایی متفاوتی ایجاد نمایید:
builder.Configuration.SetBasePath(builder.Environment.ContentRootPath)
.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true)
.AddJsonFile($"ocelot.{builder.Environment.EnvironmentName}.json", optional: false, reloadOnChange: true);
هر
سه برنامه را با هم اجرا نمایید و با استفاده از برنامهی PostMan درخواستی را برای هر یک از موارد مورد نظر /Products و /GetUser/{1,2} به سمت پروژه ApiGateway
ارسال نمایید.
Ocelot
موارد دیگری از قبیل تنظیم Load Balancer
بین سرویس ها، اتصال به سرویسهای Service Discoveryچون Consul یا یوریکا و
کش کردن و ... را نیز فراهم مینماید.
عملیات کشینگ
جهت
بحث کشینگ، ابتدا بسته زیر را اضافه نمایید:
Install-Package Ocelot.Cache.CacheManager
سپس
پیکربندی ابتدایی را به شکل زیر تغییر دهید:
builder.Services.AddOcelot()
.AddCacheManager(x => x.WithDictionaryHandle());
در ادامه در فایل Ocelot جیسون،
برای هر بخشی که مدنظر شماست تا کشی را انجام دهد، کد زیر اضافه نمایید:
"FileCacheOptions":{
"TtlSeconds":30,
"Region":"custom"
}
TtlSeconds : مدت
زمان کش به ثانیه
Region : یک
عبارت رشتهای همانند یک عنوان یا نام که بعدا میتوانید از طریق api ها به آن متصل شوید و عملیاتی چون خالی کردن کش را صادر نمایید.
حال
برای بخش محصولات این تنظیمات ذکر میگردد:
{
"Routes":[
{
"DownstreamPathTemplate":"/api/User/{id}",
"DownstreamScheme":"https",
"DownstreamHostAndPorts":[
{
"Host":"localhost",
"Port":"7279"
}
],
"UpstreamPathTemplate":"/GetUser/{id}",
"UpstreamHttpMethod":[
"GET"
]
},
{
"DownstreamPathTemplate":"/api/Product",
"DownstreamScheme":"https",
"DownstreamHostAndPorts":[
{
"Host":"localhost",
"Port":"7261"
}
],
"UpstreamPathTemplate":"/Products",
"UpstreamHttpMethod":[
"GET"
],
"FileCacheOptions":{
"TtlSeconds":30,
"Region":"custom"
}
}
]
}
برای اینکه متوجه عملکرد آن شوید یک
نقطه توقف را در اکشن دریافت محصول قرار دهید و سپس برنامه را در حالت دیباگ اجرا
نمایید. در
مرتبه اول باید نقطه توقف بتواند اجرای کد را به شما نمایش دهد ولی تا 30 ثانیه
آینده هر چقدر از طریق Postman درخواستی را
ارسال نمایید نقطه توقف اجرا نخواهد گردید، ولی نتیجهی قبل برای شما ارسال خواهد شد.
این
مورد را برای بخش کاربران هم انجام دهید و میبینید که برای هر userId و
هر شکل Url، یک پاسخ منحصر به فرد، دریافت و کش خواهد شد.
جلوگیری از درخواستهای بیش از حد
یکی
دیگر از ویژگیهای Ocelot،
جلوگیری از درخواست بیش از حد میباشد. به
همین علت ابتدا کد زیر را به هر درخواستی که مدنظر شماست اضافه نمایید:
"RateLimitOptions":{
"ClientWhitelist":[
],
"EnableRateLimiting":true,
"Period":"5s",
"PeriodTimespan":1,
"Limit":1,
"HttpStatusCode":429
}
WhiteClients : برای
مشخص کردن کلاینتهایی که نباید اعمال محدودیت روی آنها صورت بگیرد.
EnableRateLimiting : این
مورد باعث فعالسازی آن میگردد.
Period: مدت
زمانیکه حداکثر تعداد درخواست باید در آن بازه صورت بگیرد. به ترتیب برای ثانیه، دقیقه، ساعت و روز حروف s - m - h و d استفاده میگردد.
PeriodTimespan: بعد
از محدود شدن، بعد از چه مدتی دوباره بتواند درخواستی را ارسال نماید. در اینجا بعد
از محدودیت ارسال درخواست، بعد از یک ثانیه مجدد اجازه ارسال درخواست باز میگردد.
Limit: در
بازه زمانی مشخص شده چند درخواست مورد قبول واقع میشود و بعد از آن دیگر اجازه
ارسال درخواست را نخواهد داشت.
HttpStatusCode: در
صورت فیلتر شدن درخواستهای رسیده، چه کد وضعیتی باید برگردانده شود که عدد 429 به
معنای Too Many Request
میباشد.
با تنظیمات بالا هر کلاینت میتواند در 5 ثانیه، نهایتا یک درخواست را ارسال نماید و با ارسال بقیه درخواستها، Ocelot بجای هدایت درخواست به سرویس مربوطه، کد وضعیت 429 را باز میگرداند و یک ثانیه بعد از گذشت 5 ثانیه میتواند مجددا درخواست خود را ارسال نماید.
در نهایت به یک فایل مشابه زیر میرسیم:
{
"Routes":[
{
"DownstreamPathTemplate":"/api/User/{id}",
"DownstreamScheme":"https",
"DownstreamHostAndPorts":[
{
"Host":"localhost",
"Port":"7279"
}
],
"UpstreamPathTemplate":"/GetUser/{id}",
"UpstreamHttpMethod":[
"GET"
],
"FileCacheOptions":{
"TtlSeconds":30,
"Region":"custom"
}
},
{
"DownstreamPathTemplate":"/api/Product",
"DownstreamScheme":"https",
"DownstreamHostAndPorts":[
{
"Host":"localhost",
"Port":"7261"
}
],
"UpstreamPathTemplate":"/Products",
"UpstreamHttpMethod":[
"GET"
],
"RateLimitOptions":{
"ClientWhitelist":[
],
"EnableRateLimiting":true,
"Period":"5s",
"PeriodTimespan":1,
"Limit":1,
"HttpStatusCode":429
}
}
],
"DangerousAcceptAnyServerCertificateValidator": true
}
برای تست آن با استفاد از PostMan مرتبا به آدرس Products/ درخواست ارسال نمایید.
فایل پروژه : Ocelot.zip