🔁 جریان HTTP :
هنگامی که یک کلاینت میخواهد با یک سرور (چه سرور نهایی و چه یک پروکسی میانی) ارتباط برقرار کند، مراحل زیر را طی میکند: 👇
1️⃣ باز کردن اتصال TCP
اتصال TCP برای ارسال یک یا چند درخواست و دریافت پاسخ استفاده میشود. کلاینت ممکن است یک اتصال جدید باز کند، از یک اتصال موجود مجدداً استفاده کند، یا چندین اتصال TCP به سرورها باز کند. 🔗
2️⃣ ارسال پیام HTTP
پیامهای HTTP (قبل از HTTP/2) قابل خواندن توسط انسان هستند. در HTTP/2، این پیامها در Frameها کپسولهسازی میشوند که خواندن مستقیم آنها را غیرممکن میسازد، اما اصل کار یکسان باقی میماند.
📌مثال درخواست (Request):
GET / HTTP/1.1
Host: developer.mozilla.org
Accept-Language: fr
3️⃣ خواندن پاسخ سرور
پاسخ ارسال شده توسط سرور خوانده میشود، مانند:
HTTP/1.1 200 OK
Date: Sat, 09 Oct 2010 14:28:02 GMT
Server: Apache
Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT
ETag: "51142bc1-7449-479b075b2891b"
Accept-Ranges: bytes
Content-Length: 29769
Content-Type: text/html
<!doctype html>… (در اینجا 29769 بایت از صفحه وب درخواستی قرار میگیرد)
4️⃣ بستن یا استفاده مجدد از اتصال
اتصال برای درخواستهای بعدی بسته یا مجدداً استفاده میشود. 🔄
⚠️ ملاحظه Pipelining
اگر پایپلاینینگ HTTP فعال باشد، چندین درخواست میتوانند بدون انتظار برای دریافت کامل اولین پاسخ ارسال شوند. با این حال، پیادهسازی پایپلاینینگ HTTP در شبکههای موجود که بخشهای قدیمی نرمافزار با نسخههای مدرن همزیستی دارند، دشوار است. پایپلاینینگ HTTP در HTTP/2 با Multiplexing قویتر درخواستها در یک Frame جایگزین شده است.
📝 پیامهای HTTP (HTTP Messages)
پیامهای HTTP، همانطور که در HTTP/1.1 و نسخههای قبلی تعریف شدهاند، قابل خواندن توسط انسان هستند. در HTTP/2، این پیامها در یک ساختار دودویی، یعنی Frame، جاسازی میشوند که امکان بهینهسازیهایی مانند فشردهسازی هدرها و Multiplexing را فراهم میکند. حتی اگر تنها بخشی از پیام اصلی HTTP در این نسخه ارسال شود، معناشناسی هر پیام بدون تغییر باقی میماند و کلاینت درخواست اصلی HTTP/1.1 را (به صورت مجازی) بازسازی میکند. بنابراین، درک پیامهای HTTP/2 در قالب HTTP/1.1 مفید است.
دو نوع پیام HTTP وجود دارد: درخواستها (Requests) و پاسخها (Responses) که هر کدام قالب خاص خود را دارند.
🔹️ درخواستها (Requests) 📤
درخواستها شامل عناصر زیر هستند:
• متد HTTP: معمولاً یک فعل مانند GET، POST یا یک اسم مانند OPTIONS یا HEAD که عملیاتی را که کلاینت میخواهد انجام دهد، تعریف میکند. به طور معمول، کلاینت میخواهد یک منبع را واکشی کند (با استفاده از GET) یا مقدار یک فرم HTML را ارسال کند (با استفاده از POST).
• مسیر منبع: URL منبع که عناصر واضح از زمینه، مانند پروتکل (http://)، دامنه (مانند developer.mozilla.org) یا پورت TCP (مانند 80) از آن حذف شده است.
• نسخه پروتکل HTTP.
• هدرها (Headers) اختیاری: که اطلاعات اضافی را برای سرورها منتقل میکنند.
• بدنه (Body): برای برخی متدها مانند POST، مشابه پاسخها، که منبع ارسال شده را شامل میشود.
🔹️ پاسخها (Responses) 📥
پاسخها شامل عناصر زیر هستند:
• نسخه پروتکل HTTP: که از آن پیروی میکنند.
• کد وضعیت (Status Code): نشان میدهد که آیا درخواست موفقیتآمیز بوده یا خیر، و چرا. 🔢
• پیام وضعیت (Status Message): یک توضیح کوتاه غیررسمی از کد وضعیت.
• هدرهای HTTP: مانند هدرهای درخواستها.
• بدنه (Body) اختیاری: که منبع واکشی شده را شامل میشود.
📝 نتیجهگیری
فهمیدیم HTTP یک پروتکل قابل توسعه (Extensible) است که استفاده از آن آسان میباشد. ساختار کلاینت-سرور، همراه با قابلیت افزودن هدرها، به HTTP اجازه میدهد تا همگام با قابلیتهای گسترده وب پیشرفت کند. 📈
اگرچه HTTP/2 با جاسازی پیامهای HTTP در Frameها برای بهبود عملکرد، مقداری پیچیدگی اضافه میکند، ساختار اصلی پیامها از زمان HTTP/1.0 یکسان باقی مانده است. جریان Session همچنان اساسی است و امکان بررسی و Debug کردن آن را با یک HTTP network monitor فراهم میکند. 🔍
🔖 هشتگها:
#HTTP #RequestResponse #Networking #CORS #WebSecurity #Session #TCP #QUIC
🧠 Options Pattern Validation در ASP.NET Core با FluentValidation
اگر با Options Pattern در ASP.NET Core کار کرده باشید، احتمالاً با اعتبارسنجی داخلی DataAnnotations آشنا هستید.
هرچند این روش ساده و کاربردیست، اما وقتی نوبت به سناریوهای پیچیدهتر میرسد، محدودیتهای خودش را نشان میدهد.
📦 Options Pattern
به شما اجازه میدهد تا مقادیر تنظیمات (configuration) را بهصورت کلاسهای strongly-typed در زمان اجرا دریافت کنید.
اما یک مشکل وجود دارد:
🔸 شما نمیتوانید مطمئن باشید که مقادیر پیکربندی واقعاً معتبر هستند تا زمانیکه از آنها استفاده کنید!
راهحل؟
✅ اعتبارسنجی تنظیمات در زمان راهاندازی (Startup) برنامه.
در این پست یاد میگیریم چطور با ترکیب FluentValidation و Options Pattern،
یک سیستم اعتبارسنجی قدرتمند بسازیم که در startup اجرا میشود و از بروز خطاهای پیکربندی جلوگیری میکند.
⚖️ چرا FluentValidation بهتر از DataAnnotations است؟
در حالیکه DataAnnotations برای اعتبارسنجیهای ساده کافی است،FluentValidation امکانات بیشتری ارائه میدهد:
✨ قوانین اعتبارسنجی انعطافپذیرتر و خواناتر
⚙️ پشتیبانی از شرایط پیچیده و شرطی
🧩 جداسازی تمیز منطق اعتبارسنجی از مدلها
🧪 امکان تست سادهی قوانین
🧠 پشتیبانی بهتر از منطق سفارشی
🔌 قابلیت تزریق وابستگیها در Validatorها
⚙️ درک چرخهٔ حیات Options Pattern در ASP.NET Core
قبل از اینکه وارد جزئیات اعتبارسنجی بشیم، لازمه چرخهٔ حیات (Lifecycle) گزینهها در ASP.NET Core رو درک کنیم 👇
1️⃣ گزینهها در Container وابستگی (DI Container) ثبت میشن.
2️⃣ مقادیر پیکربندی (Configuration Values) به کلاسهای Options متصل میشن.
3️⃣ اگر اعتبارسنجی پیکربندی شده باشه، در این مرحله اجرا میشه.
4️⃣ گزینهها هنگام فراخوانی از طریق یکی از این اینترفیسها resolve میشن:
• IOptions<T>
• IOptionsSnapshot<T>
• IOptionsMonitor<T>
🧩 متد ()ValidateOnStart باعث میشه اعتبارسنجی در زمان راهاندازی (Startup) برنامه انجام بشه،
نه وقتی که برای اولین بار Options مورد استفاده قرار میگیرن.
⚠️ خطاهای رایج پیکربندی بدون اعتبارسنجی
بدون اعتبارسنجی، مشکلات پیکربندی میتوانند به چند شکل ظاهر شوند:
خطاهای خاموش (Silent failures): یک گزینهی پیکربندی اشتباه ممکن است باعث شود مقادیر پیشفرض بدون هیچ هشداری استفاده شوند.
🔹️ استثناهای زمان اجرا (Runtime exceptions): مشکلات پیکربندی ممکن است تنها وقتی که برنامه تلاش میکند مقادیر نامعتبر را استفاده کند، ظاهر شوند.
🔹️ خطاهای زنجیرهای (Cascading failures): یک کامپوننت پیکربندیشده نادرست میتواند باعث شکست سیستمهای وابسته شود.
با اعتبارسنجی در زمان Startup برنامه، یک چرخه بازخورد سریع ایجاد میکنید که از بروز این مشکلات جلوگیری میکند. ✅
🧱 تنظیم پایهها (Setting Up the Foundation)
اول، پکیج FluentValidation را به پروژه اضافه میکنیم:
Install-Package FluentValidation # پکیج اصلی
Install-Package FluentValidation.DependencyInjectionExtensions # برای یکپارچگی با DI
در مثال ما، از یک کلاس تنظیمات به نام GitHubSettings استفاده میکنیم که نیاز به اعتبارسنجی دارد:
public class GitHubSettings
{
public const string ConfigurationSection = "GitHubSettings";
public string BaseUrl { get; init; }
public string AccessToken { get; init; }
public string RepositoryName { get; init; }
}
🧩 ایجاد Validator با FluentValidation
سپس یک Validator برای کلاس تنظیمات میسازیم:
public class GitHubSettingsValidator : AbstractValidator<GitHubSettings>
{
public GitHubSettingsValidator()
{
RuleFor(x => x.BaseUrl).NotEmpty();
RuleFor(x => x.BaseUrl)
.Must(baseUrl => Uri.TryCreate(BaseUrl, UriKind.Absolute, out _))
.When(x => !string.IsNullOrWhiteSpace(x.BaseUrl))
.WithMessage($"{nameof(GitHubSettings.BaseUrl)} must be a valid URL");
RuleFor(x => x.AccessToken)
.NotEmpty();
RuleFor(x => x.RepositoryName)
.NotEmpty();
}
}
🏗 ساخت یکپارچهسازی FluentValidation
برای ادغام FluentValidation با Options Pattern، نیاز داریم یک پیادهسازی سفارشی از <IValidateOptions<T بسازیم:
using FluentValidation;
using Microsoft.Extensions.Options;
public class FluentValidateOptions<TOptions>
: IValidateOptions<TOptions>
where TOptions : class
{
private readonly IServiceProvider _serviceProvider;
private readonly string? _name;
public FluentValidateOptions(IServiceProvider serviceProvider, string? name)
{
_serviceProvider = serviceProvider;
_name = name;
}
public ValidateOptionsResult Validate(string? name, TOptions options)
{
if (_name is not null && _name != name)
{
return ValidateOptionsResult.Skip;
}
ArgumentNullException.ThrowIfNull(options);
using var scope = _serviceProvider.CreateScope();
var validator = scope.ServiceProvider.GetRequiredService<IValidator<TOptions>>();
var result = validator.Validate(options);
if (result.IsValid)
{
return ValidateOptionsResult.Success;
}
var type = options.GetType().Name;
var errors = new List<string>();
foreach (var failure in result.Errors)
{
errors.Add($"Validation failed for {type}.{failure.PropertyName} " +
$"with the error: {failure.ErrorMessage}");
}
return ValidateOptionsResult.Fail(errors);
}
}
📝 نکات مهم درباره این پیادهسازی
یک scoped service provider ساخته میشود تا Validator به درستی Resolve شود (چون Validatorها معمولاً به صورت Scoped ثبت میشوند).
گزینههای دارای نام با استفاده از پراپرتی _name مدیریت میشوند.
پیامهای خطای دقیق و اطلاعاتی ساخته میشوند که شامل نام پراپرتی و پیام خطا هستند.
⚙️ نحوه عملکرد یکپارچهسازی FluentValidation
• اینترفیس <IValidateOptions<T نقطه اتصال ASP.NET Core برای اعتبارسنجی Options است.
• کلاس <FluentValidateOptions<T این اینترفیس را پیادهسازی میکند تا ارتباط با FluentValidation برقرار شود.
• وقتی ()ValidateOnStart فراخوانی میشود، ASP.NET Core همه پیادهسازیهای <IValidateOptions<T را Resolve کرده و اجرا میکند.
• اگر اعتبارسنجی شکست بخورد، OptionsValidationException پرتاب میشود و از شروع برنامه جلوگیری میکند. ✅
🛠 ساخت متدهای Extension برای ادغام آسان
حالا بیایید چند extension method بسازیم تا استفاده از FluentValidation در Options Pattern راحتتر شود:
public static class OptionsBuilderExtensions
{
public static OptionsBuilder<TOptions> ValidateFluentValidation<TOptions>(
this OptionsBuilder<TOptions> builder)
where TOptions : class
{
builder.Services.AddSingleton<IValidateOptions<TOptions>>(
serviceProvider => new FluentValidateOptions<TOptions>(
serviceProvider,
builder.Name));
return builder;
}
}
این extension method به ما امکان میدهد هنگام کانفیگ Options، متد ()ValidateFluentValidation را فراخوانی کنیم، مشابه متد داخلی ()ValidateDataAnnotations.
برای راحتی بیشتر، میتوانیم یک extension method دیگر بسازیم تا کل فرآیند کانفیگ را ساده کند:
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddOptionsWithFluentValidation<TOptions>(
this IServiceCollection services,
string configurationSection)
where TOptions : class
{
services.AddOptions<TOptions>()
.BindConfiguration(configurationSection)
.ValidateFluentValidation() // کانفیگ FluentValidation
.ValidateOnStart(); // اعتبارسنجی در هنگام شروع برنامه
return services;
}
}
📝 ثبت و استفاده از اعتبارسنجی
چند روش برای استفاده از این ادغام FluentValidation وجود دارد:
1️⃣: ثبت استاندارد با ثبت دستی Validator
// ثبت Validator
builder.Services.AddScoped<IValidator<GitHubSettings>, GitHubSettingsValidator>();
// کانفیگ Options با اعتبارسنجی
builder.Services.AddOptions<GitHubSettings>()
.BindConfiguration(GitHubSettings.ConfigurationSection)
.ValidateFluentValidation() // فعال کردن FluentValidation
.ValidateOnStart();
2️⃣: استفاده از Extension راحت
// ثبت Validator
builder.Services.AddScoped<IValidator<GitHubSettings>, GitHubSettingsValidator>();
// استفاده از Extension راحت
builder.Services.AddOptionsWithFluentValidation<GitHubSettings>(GitHubSettings.ConfigurationSection);
3️⃣: ثبت خودکار Validatorها
اگر Validatorهای زیادی دارید و میخواهید همه را یکجا ثبت کنید، میتوانید از assembly scanning استفاده کنید:
// ثبت همه Validatorها از assembly
builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);
// استفاده از Extension راحت
builder.Services.AddOptionsWithFluentValidation<GitHubSettings>(GitHubSettings.ConfigurationSection);
⚡️ اتفاقات هنگام اجرای برنامه
با ()ValidateOnStart، اگر هر قانونی نقض شود، برنامه هنگام startup یک استثنا پرتاب میکند.
مثال: اگر در appsettings.json مقدار AccessToken موجود نباشد، پیام خطا شبیه زیر خواهد بود:
Microsoft.Extensions.Options.OptionsValidationException:
Validation failed for GitHubSettings.AccessToken with the error: 'Access Token' must not be empty.
این کار از شروع برنامه با پیکربندی اشتباه جلوگیری میکند و اطمینان حاصل میکند که مشکلات در اوایل چرخه توسعه شناسایی شوند. ✅
⚙️ کار با منابع مختلف کانفیگ
سیستم کانفیگ ASP.NET Core از چندین منبع پشتیبانی میکند. هنگام استفاده از Options Pattern با FluentValidation، به یاد داشته باشید که اعتبارسنجی صرفنظر از منبع کار میکند:
🔹️متغیرهای محیطی (Environment variables)
🔹️Azure Key Vault
🔹️User secrets
🔹️فایلهای JSON
🔹️کانفیگ در حافظه (In-memory configuration)
این ویژگی مخصوصاً برای برنامههای containerized مفید است که کانفیگ از طریق متغیرهای محیطی یا secretهای mount شده میآید.
🧪 تست Validatorهای خود
یکی از مزایای استفاده از FluentValidation این است که Validatorها بسیار آسان تست میشوند:
// استفاده از متدهای کمکی FluentValidation.TestHelper
[Fact]
public void GitHubSettings_WithMissingAccessToken_ShouldHaveValidationError()
{
// Arrange
var validator = new GitHubSettingsValidator();
var settings = new GitHubSettings { RepositoryName = "test-repo" };
// Act
TestValidationResult<GitHubSettings>? result = await validator.TestValidate(settings);
// Assert
result.ShouldNotHaveAnyValidationErrors();
}
✅ خلاصه
با ترکیب FluentValidation با Options Pattern و ()ValidateOnStart، یک سیستم اعتبارسنجی قدرتمند ایجاد میکنیم که تضمین میکند کانفیگ برنامه درست باشد از همان ابتدا.
مزایای این روش:
• قوانین اعتبارسنجی بیانگراتر و انعطافپذیرتر نسبت به Data Annotations
• جداسازی منطق اعتبارسنجی از مدلهای کانفیگ
• کشف خطاهای کانفیگ در زمان startup برنامه
• پشتیبانی از سناریوهای اعتبارسنجی پیچیده
• قابلیت تست آسان
این الگو به ویژه در معماریهای میکروسرویس یا برنامههای containerized ارزشمند است، جایی که خطاهای کانفیگ باید فوراً تشخیص داده شوند و نه در زمان اجرا.
به یاد داشته باشید که Validatorهای خود را به درستی ثبت کنید و از ()ValidateOnStart استفاده کنید تا اعتبارسنجی در زمان شروع برنامه اجرا شود.
🏷 هشتگها:
#ASPNetCore #FluentValidation #OptionsPattern #Validation
💡 اصول طراحی GRASP در تحلیل و طراحی شیءگرا (OOAD)
Object-Oriented Analysis and Design
در تحلیل و طراحی شیءگرا (OOAD)، الگوهای GRASP (General Responsibility Assignment Software Patterns) نقش حیاتی در طراحی نرمافزارهای مؤثر و قابل نگهداری دارند.
GRASP
مجموعهای از راهنماها برای تعیین مسئولیت کلاسها و اشیاء ارائه میدهد تا وابستگی کم، انسجام بالا و پایداری سیستم حفظ شود.
اصول اصلی GRASP:
1️⃣ Creator (ایجادگر):
مسئولیت ایجاد نمونههای یک کلاس را به کلاسی بدهید که بیشترین دانش درباره زمان و نحوه ایجاد آنها را دارد.
2️⃣ Information Expert (کارشناس اطلاعات):
مسئولیت را به کلاسی اختصاص دهید که اطلاعات لازم برای انجام آن را دارد. این کار انسجام بالا و وابستگی کم را تقویت میکند.
3️⃣ Low Coupling (وابستگی کم):
هدف این است که کلاسها کمترین وابستگی را به یکدیگر داشته باشند، تا نگهداری آسانتر و انعطافپذیری سیستم افزایش یابد.
4️⃣ High Cohesion (انسجام بالا):
اطمینان حاصل کنید مسئولیتهای یک کلاس مرتبط و متمرکز باشند. این باعث خوانایی، نگهداری و قابلیت استفاده مجدد بهتر میشود.
5️⃣ Controller (کنترلکننده):
مسئولیت مدیریت رویدادهای سیستم یا هماهنگی فعالیتها را به یک کلاس کنترلکننده بسپارید تا کنترل متمرکز داشته باشید و کلاسها شلوغ نشوند.
6️⃣ Pure Fabrication (ساخت مصنوعی):
کلاسهای جدیدی ایجاد کنید تا مسئولیتها را بدون نقض انسجام و وابستگی انجام دهند و طراحی تمیز و قابل نگهداری داشته باشید.
7️⃣ Indirection (واسطهگری):
از واسطهها یا انتزاعها برای کاهش وابستگی بین کلاسها و افزایش انعطافپذیری طراحی استفاده کنید.
8️⃣ Polymorphism (چندریختی):
از ارثبری و اینترفیسها استفاده کنید تا چندین پیادهسازی برای رفتارها ایجاد شود و سیستم انعطافپذیر و توسعهپذیر باشد.
✨ خلاصه:
با اعمال این اصول GRASP، توسعهدهندگان میتوانند طراحی شیءگرای مقاوم، قابل نگهداری و تطبیقپذیر با تغییرات نیازمندیها ایجاد کنند.
💡 اهمیت اصول GRASP در تحلیل و طراحی شیءگرا (OOAD)
در تحلیل و طراحی شیءگرا (OOAD)، اصول GRASP اهمیت ویژهای دارند زیرا چارچوبی برای طراحی سیستمها با شفافیت، انعطافپذیری و قابلیت نگهداری بالا ارائه میکنند. در ادامه دلایل اهمیت آنها آمده است:
📝 شفافیت طراحی
اصول GRASP کمک میکنند تا کلاسها و مسئولیتها به گونهای سازماندهی شوند که طراحی سیستم قابل فهمتر باشد. مسئولیتهای مشخص و واضح برای هر کلاس باعث میشود توسعهدهندگان معماری سیستم را سریعتر درک کنند.
🔗 وابستگی کم و انسجام بالا
اصول GRASP تشویق میکند کلاسها وابستگی کمی به یکدیگر داشته باشند. این موضوع باعث میشود کد ماژولار و قابل استفاده مجدد باشد. همچنین، انسجام بالا تضمین میکند که هر کلاس هدف مشخص و متمرکزی دارد و سیستم راحتتر نگهداری و تغییر میکند.
⚡️ طراحی انعطافپذیر
با پیروی از اصولی مانند واسطهگری (Indirection) و چندریختی (Polymorphism)، طراحی سیستم انعطافپذیرتر و قابل تطبیق با تغییرات میشود. واسطهگری امکان معرفی موجودیتهای میانی را فراهم میکند که تعاملات پیچیده را سادهتر میکند، و چندریختی اجازه میدهد چندین پیادهسازی برای رفتارها استفاده شود و قابلیت توسعه بهبود یابد.
📈 مقیاسپذیری
اصول GRASP با ترویج طراحیای که میتواند تغییرات و بهبودهای آینده را بدون بازنویسی گسترده بپذیرد، به مقیاسپذیری سیستم کمک میکنند. این مقیاسپذیری برای رشد و توسعه طولانیمدت سیستمها حیاتی است.
🛠 سهولت نگهداری
با اختصاص مسئولیتهای واضح به کلاسها و تعریف روابط روشن بین آنها، نگهداری سیستم سادهتر میشود. توسعهدهندگان میتوانند سریعاً مکان تغییرات را شناسایی کرده و بدون تأثیر ناخواسته بر سایر بخشها، تغییرات را اعمال کنند.
🔄 افزایش قابلیت استفاده مجدد
اصول GRASP ایجاد کلاسها و اشیایی با مسئولیتها و رابطهای مشخص را تشویق میکنند. این باعث میشود کد قابل استفاده مجدد باشد و اجزا به راحتی در بخشهای مختلف سیستم یا پروژههای جدید استفاده شوند، که بهرهوری را افزایش داده و زمان توسعه را کاهش میدهد.
📚 اصول GRASP و مثالهای آن در OOAD
GRASP (General Responsibility Assignment Software Patterns)
مجموعهای از دستورالعملها در تحلیل و طراحی شیءگرا (OOAD) هستند که به ما کمک میکنند مسئولیتها را به کلاسها و اشیا بهصورت مؤثر تخصیص دهیم. در ادامه هر اصل را با یک مثال کاربردی بررسی میکنیم:
1️⃣ Creator – ایجاد کننده
مسئولیت ایجاد نمونههای یک کلاس را به خود کلاس یا یک کلاس کارخانه مرتبط واگذار کنید.
🔹️مثال:
در سیستم مدیریت کتابخانه، وقتی کتاب جدیدی اضافه میشود، یک شیء Book باید ایجاد شود. این مسئولیت میتواند به کلاس Library یا یک کلاس جداگانه مانند BookFactory سپرده شود تا منطق ایجاد شیء Book متمرکز و قابل مدیریت باشد.
2️⃣ Information Expert – کارشناس اطلاعات
مسئولیت را به کلاسی واگذار کنید که اطلاعات لازم برای انجام آن مسئولیت را دارد.
🔹️مثال:
هنگام امانت گرفتن کتاب، بررسی موجود بودن کتاب باید توسط کلاس Book انجام شود. کلاس Book اطلاعات وضعیت موجودی خود را دارد و میتواند بدون وابستگی به کلاسهای دیگر این بررسی را انجام دهد. این کار انسجام بالا و کاهش وابستگی را تضمین میکند.
3️⃣ Low Coupling – وابستگی کم
کلاسها باید حداقل وابستگی را به یکدیگر داشته باشند.
🔹️مثال:
در سیستم مدیریت کتابخانه، کلاس LibraryCatalog مسئول مدیریت کاتالوگ کتابهاست. به جای دسترسی مستقیم به کلاس Book برای بررسی موجودی، میتواند از یک رابط (interface) مانند Searchable استفاده کند. این کار باعث میشود LibraryCatalog وابستگی کمتری به کلاس Book داشته باشد و نگهداری سیستم آسانتر شود.
4️⃣ High Cohesion – انسجام بالا
مسئولیتهای درون یک کلاس باید مرتبط و متمرکز باشند.
🔹️مثال:
کلاس Book باید مسئولیتهای مرتبط با جزئیات کتاب مانند عنوان، نویسنده و موجودی را مدیریت کند. وظایف نامرتبط مثل احراز هویت کاربر باید توسط کلاسهای جداگانه مدیریت شوند تا هر کلاس تمرکز مشخصی داشته باشد و سیستم قابل نگهداری باشد.
5️⃣ Controller – کنترلکننده
مسئولیت مدیریت رویدادهای سیستم یا هماهنگی فعالیتها را به یک کلاس کنترلکننده واگذار کنید.
🔹️مثال:
در یک اپلیکیشن وب کتابخانه، زمانی که کاربر درخواست امانت کتاب میدهد، کلاس BorrowBookController مسئول مدیریت درخواست و هماهنگی با کلاسهای Book، User و Library است. این کار باعث تمرکز منطق کنترل و سازماندهی بهتر سیستم میشود.
6️⃣ Pure Fabrication – ساختگی خالص
کلاسهای جدیدی معرفی کنید تا مسئولیتها را بدون نقض انسجام و وابستگی کم انجام دهند.
🔹️مثال:
برای ارسال ایمیل هنگام امانت یا بازگشت کتاب، به جای افزودن منطق ایمیل به کلاسهای Book یا User، کلاس جداگانه NotificationService ایجاد میکنیم. این کلاس مسئولیت خالص ارسال ایمیل را برعهده دارد و انسجام و وابستگی کم را حفظ میکند.
7️⃣ Indirection – واسطهگری
از واسطهها یا انتزاعها برای کاهش وابستگی کلاسها و افزایش انعطافپذیری طراحی استفاده کنید.
🔹️مثال:
اگر چندین کلاس نیاز به دسترسی به اطلاعات کتاب دارند، میتوان یک رابط BookRepository معرفی کرد. کلاسها به جای دسترسی مستقیم به Book، از این رابط استفاده کنند تا انعطافپذیری و قابلیت تغییر آینده آسانتر شود.
8️⃣ Polymorphism – چندریختی
از ارثبری و اینترفیسها برای پیادهسازی چندین رفتار استفاده کنید.
🔹️مثال:
فرض کنید کتابها به دو نوع FictionBook و NonFictionBook تقسیم شوند و هر کدام قوانین امانت خاص خود را داشته باشند. با تعریف یک اینترفیس مشترک Book و پیادهسازی آن در کلاسهای مختلف، چندریختی اجازه میدهد فرآیند امانت بهطور یکنواخت و مستقل از نوع کتاب مدیریت شود و استفاده مجدد کد سادهتر شود.
🔖هشتگها:
#GRASP #OOAD #ObjectOrientedDesign #LowCoupling #HighCohesion
استفاده از Static class یا Singleton ؟ یا میتوان بهتر عمل کرد؟ 🤔
وقتی میخواستم به شخصی در مورد این سؤال مشاوره بدهم، احساس دوگانگی عجیبی داشتم.
دو گزینهای که او مطرح کرده بود اینها بودند:
1️⃣ استفاده از یک کلاس استاتیک برای اتصال به پایگاه داده
2️⃣ استفاده از یک سینگلتون برای اتصال به پایگاه داده
واقعیت این است که من هیچکدام از این دو گزینه را واقعاً دوست ندارم... اما میخواستم شرایط مطرحشده را در نظر بگیرم. به نظر میرسید این پروژه، از آن دسته اپلیکیشنهایی نبود که نیاز به مقیاسپذیری داشته باشد؛ بیشتر شبیه یک پروژه سرگرمی یا چیزی با چرخه توسعه کوتاهمدت بود.
این موضوع برای من همیشه کمی سخت است که با آن کنار بیایم، چون معمولاً سعی میکنم سیستمها را طوری طراحی کنم که قابل توسعه و ماندگار باشند. اما این محدودیتها وجود داشت و این هم تنها گزینههای مطرحشده بودند.
در این شرایط، من سینگلتون را انتخاب میکنم — و حتی نوشتن این جمله هم برایم کمی ناخوشایند است 😅
حقیقت این است که استفاده از یک کلاس استاتیک برای چیزی که حالت (State) دارد، میتواند خیلی راحت منجر به بروز باگ شود؛ چون عملاً هیچ کنترلی بر اینکه چه کسی میتواند به آن کلاس دسترسی داشته باشد، ندارید.
در مورد سینگلتون هم میتوان همین استدلال را مطرح کرد، اما نکتهای که میخواهم اضافه کنم این است که سینگلتون ذاتاً نیازی ندارد که از همهجا قابل دسترسی باشد… فقط اغلب به این شکل پیادهسازی میشود.
هدف از سینگلتون این است که اطمینان حاصل کنیم بیش از یک نمونه از یک شئ ساخته نمیشود. اما برای رسیدن به این هدف معمولاً به این معنی است که آن شئ از همهجا «قابل دیدن» خواهد بود… معمولاً همینطور است.
به نظر من، شروع با سینگلتون به شما یک نمونه (Instance) میدهد که میتوانید دسترسی به آن را بهتدریج محدود کنید. میتوانید شروع کنید به ارسال آن از طریق سازندهها (Constructors). میتوانید کمکم از تکیه به ویژگی سراسری (Global Instance Property) فاصله بگیرید.
در واقع، این کار یک قدم شما را به سمت طراحی بهتر نزدیکتر میکند؛ یعنی جایی که در ابتدای برنامه، یک نمونه از شئ میسازید و آن را به قسمتهای مختلف پاس میدهید. خوشبختانه بسیاری از فریمورکهای تزریق وابستگی (Dependency Injection) این روند را برای ما بسیار خوب مدیریت میکنند.
پس در نهایت، هیچکدام از این دو گزینه انتخاب مورد علاقه من نیستند، اما سینگلتون بهنظر من «شرّ کمتر» است (هرچند خیلی جزئی!) و ما را کمی به سمت الگوهای طراحیای که ترجیح میدهم، پیش میبرد.
وقتی میخواستم به شخصی در مورد این سؤال مشاوره بدهم، احساس دوگانگی عجیبی داشتم.
دو گزینهای که او مطرح کرده بود اینها بودند:
1️⃣ استفاده از یک کلاس استاتیک برای اتصال به پایگاه داده
2️⃣ استفاده از یک سینگلتون برای اتصال به پایگاه داده
واقعیت این است که من هیچکدام از این دو گزینه را واقعاً دوست ندارم... اما میخواستم شرایط مطرحشده را در نظر بگیرم. به نظر میرسید این پروژه، از آن دسته اپلیکیشنهایی نبود که نیاز به مقیاسپذیری داشته باشد؛ بیشتر شبیه یک پروژه سرگرمی یا چیزی با چرخه توسعه کوتاهمدت بود.
این موضوع برای من همیشه کمی سخت است که با آن کنار بیایم، چون معمولاً سعی میکنم سیستمها را طوری طراحی کنم که قابل توسعه و ماندگار باشند. اما این محدودیتها وجود داشت و این هم تنها گزینههای مطرحشده بودند.
در این شرایط، من سینگلتون را انتخاب میکنم — و حتی نوشتن این جمله هم برایم کمی ناخوشایند است 😅
حقیقت این است که استفاده از یک کلاس استاتیک برای چیزی که حالت (State) دارد، میتواند خیلی راحت منجر به بروز باگ شود؛ چون عملاً هیچ کنترلی بر اینکه چه کسی میتواند به آن کلاس دسترسی داشته باشد، ندارید.
در مورد سینگلتون هم میتوان همین استدلال را مطرح کرد، اما نکتهای که میخواهم اضافه کنم این است که سینگلتون ذاتاً نیازی ندارد که از همهجا قابل دسترسی باشد… فقط اغلب به این شکل پیادهسازی میشود.
هدف از سینگلتون این است که اطمینان حاصل کنیم بیش از یک نمونه از یک شئ ساخته نمیشود. اما برای رسیدن به این هدف معمولاً به این معنی است که آن شئ از همهجا «قابل دیدن» خواهد بود… معمولاً همینطور است.
به نظر من، شروع با سینگلتون به شما یک نمونه (Instance) میدهد که میتوانید دسترسی به آن را بهتدریج محدود کنید. میتوانید شروع کنید به ارسال آن از طریق سازندهها (Constructors). میتوانید کمکم از تکیه به ویژگی سراسری (Global Instance Property) فاصله بگیرید.
در واقع، این کار یک قدم شما را به سمت طراحی بهتر نزدیکتر میکند؛ یعنی جایی که در ابتدای برنامه، یک نمونه از شئ میسازید و آن را به قسمتهای مختلف پاس میدهید. خوشبختانه بسیاری از فریمورکهای تزریق وابستگی (Dependency Injection) این روند را برای ما بسیار خوب مدیریت میکنند.
پس در نهایت، هیچکدام از این دو گزینه انتخاب مورد علاقه من نیستند، اما سینگلتون بهنظر من «شرّ کمتر» است (هرچند خیلی جزئی!) و ما را کمی به سمت الگوهای طراحیای که ترجیح میدهم، پیش میبرد.
🔒 امنیت لایه انتقال (Transport Layer Security - TLS) چیست؟
(TLS)
یا Transport Layer Security یک پروتکل امنیتی پرکاربرد است که برای ایجاد حریم خصوصی و امنیت دادهها در ارتباطات اینترنتی طراحی شده است.
یکی از کاربردهای اصلی TLS، رمزنگاری ارتباط بین برنامههای وب و سرورها است — مانند زمانی که یک مرورگر وب، وبسایتی را بارگذاری میکند.
علاوه بر وب، TLS همچنین میتواند برای رمزنگاری سایر ارتباطات مانند ایمیل ✉️، پیامرسانی 💬 و تماسهای صوتی از طریق اینترنت (VoIP 📞) مورد استفاده قرار گیرد.
در این مقاله، تمرکز ما بر نقش TLS در امنیت برنامههای تحت وب خواهد بود.
🌐 تاریخچهی TLS
(TLS)
توسط سازمان بینالمللی Internet Engineering Task Force (IETF) پیشنهاد شد. اولین نسخه از این پروتکل در سال ۱۹۹۹ منتشر گردید.
جدیدترین نسخه، TLS 1.3 است که در سال ۲۰۱۸ به انتشار رسید و در حال حاضر به عنوان نسخهی استاندارد و امن مورد استفاده قرار میگیرد.
🧩 تفاوت TLS و SSL چیست؟
(TLS)
در واقع نسخهی تکاملیافتهی پروتکل رمزنگاری قبلی به نام SSL (Secure Sockets Layer) است که توسط شرکت Netscape توسعه یافته بود.
جالب است بدانید نسخهی TLS 1.0 در ابتدا به عنوان SSL 3.1 در حال توسعه بود، اما پیش از انتشار، نام آن به TLS تغییر یافت تا نشان دهد دیگر متعلق به Netscape نیست.
به دلیل این پیشینه، بسیاری از افراد هنوز هم اصطلاحات TLS و SSL را به جای یکدیگر به کار میبرند، در حالی که در واقع TLS نسخهی جدیدتر و امنتر SSL است.
🌍 تفاوت TLS و HTTPS چیست؟
HTTPS
در واقع پیادهسازی TLS روی پروتکل HTTP است — یعنی همان پروتکلی که همهی وبسایتها و بسیاری از سرویسهای وب از آن استفاده میکنند.
به زبان ساده، هر وبسایتی که از HTTPS استفاده میکند، در واقع از رمزنگاری TLS برای ایمنسازی ارتباطات خود بهره میبرد.
🏢 چرا کسبوکارها و برنامههای وب باید از TLS استفاده کنند؟
استفاده از رمزنگاری TLS میتواند از برنامههای وب در برابر نشت دادهها، حملات سایبری و شنود ارتباطات محافظت کند.
امروزه استفاده از HTTPS (بر پایه TLS) به یک استاندارد جهانی برای وبسایتها تبدیل شده است.
مرورگر Google Chrome به تدریج وبسایتهای بدون HTTPS را ناامن معرفی کرده و سایر مرورگرها نیز همین سیاست را در پیش گرفتهاند.
کاربران امروزی نیز هنگام مشاهدهی وبسایتها، تنها زمانی احساس اطمینان میکنند که آیکون قفل 🔒 (padlock) در نوار آدرس مرورگرشان دیده شود.
🔐 TLS دقیقاً چه کاری انجام میدهد؟
پروتکل Transport Layer Security (TLS) سه وظیفهی اصلی دارد که هرکدام نقش حیاتی در امنیت ارتباطات اینترنتی ایفا میکنند:
1️⃣ رمزنگاری (Encryption)
🔸 دادههای در حال انتقال را از دید اشخاص ثالث پنهان میکند.
🔸 بهعبارتی، حتی اگر کسی دادهها را در مسیر شنود کند، نمیتواند محتوای آن را بخواند.
2️⃣ احراز هویت (Authentication)
🔸 تضمین میکند طرفین ارتباط (مانند مرورگر و سرور) همان کسانی هستند که ادعا میکنند.
🔸 این کار از حملاتی مثل Man-in-the-Middle جلوگیری میکند.
3️⃣ یکپارچگی داده (Integrity)
🔸 بررسی میکند که دادهها در مسیر انتقال دستکاری یا جعل نشده باشند.
🔸 این تضمین میکند که اطلاعات دقیقاً به همان شکلی که ارسال شده، دریافت میشوند.
📜 گواهی TLS چیست؟
برای اینکه یک وبسایت یا برنامه بتواند از TLS استفاده کند، باید یک گواهی TLS بر روی سرور اصلی (Origin Server) نصب شده باشد.
این گواهی که اغلب به اشتباه SSL Certificate نیز نامیده میشود، توسط مرجع صدور گواهی (Certificate Authority - CA) به مالک دامنه صادر میگردد.
🔹 این گواهی شامل اطلاعات زیر است:
• مشخصات مالک دامنه 🌍
• کلید عمومی سرور (Public Key) 🔑
هر دو مورد برای تأیید هویت سرور ضروری هستند تا مرورگر کاربر مطمئن شود که با سرور اصلی در ارتباط است، نه یک سرور جعلی.
⚙️ TLS چگونه کار میکند؟
هنگامی که کاربر وارد وبسایتی با TLS میشود، فرآیندی به نام TLS Handshake آغاز میگردد.
این فرآیند بین دستگاه کاربر (Client) و سرور وب انجام میشود تا ارتباطی امن برقرار گردد.
در طول TLS Handshake موارد زیر اتفاق میافتد 👇
1️⃣ انتخاب نسخهی TLS (مثلاً TLS 1.2 یا TLS 1.3)
2️⃣ توافق روی مجموعهی رمزنگاری یا Cipher Suite
3️⃣ احراز هویت سرور با استفاده از گواهی TLS
4️⃣ تولید کلیدهای نشست (Session Keys) برای رمزنگاری پیامها پس از اتمام Handshake
🔢 Cipher Suite چیست؟
Cipher Suite
مجموعهای از الگوریتمهاست که تعیین میکند در این ارتباط از چه روشهای رمزنگاری و چه کلیدهایی استفاده شود.
TLS
با استفاده از فناوری رمزنگاری کلید عمومی (Public Key Cryptography) میتواند این کلیدها را حتی از طریق یک کانال رمزنگارینشده، بهصورت ایمن تنظیم کند.
🧩 احراز هویت از طریق کلیدهای عمومی
در این مرحله، معمولاً سرور هویت خود را به کاربر ثابت میکند.
این کار با استفاده از کلید عمومی (Public Key) انجام میشود.
🔸 هر کسی میتواند دادههایی را که با کلید خصوصی سرور رمزگذاری شده، با کلید عمومی رمزگشایی کند و از اصالت آن اطمینان یابد.
🔸 اما تنها خود سرور (دارندهی کلید خصوصی) میتواند دادههای جدیدی را رمز کند.
کلید عمومی سرور در گواهی TLS آن ذخیره میشود.
✅ بررسی تمامیت دادهها (Integrity Check)
پس از رمزنگاری و احراز هویت، دادهها با کد تأیید پیام (Message Authentication Code - MAC) امضا میشوند.
گیرنده میتواند با بررسی MAC اطمینان یابد که دادهها تغییر نکردهاند.
💊 مثال جالب:
این شبیه پلمب آلومینیومی روی بطری قرصهاست — وقتی سالم است، مطمئن میشوید کسی محتویات آن را دستکاری نکرده است.
📘 جمعبندی
TLS
یک لایهی امنیتی حیاتی در ارتباطات اینترنتی است که با رمزنگاری دادهها، از حریم خصوصی کاربران و امنیت اطلاعات محافظت میکند.
امروزه، هیچ وبسایتی بدون TLS امن تلقی نمیشود. 🌐
🔖هشتگها:
#TLS #HTTPS #CyberSecurity #NetworkSecurity #WebDevelopment
🚀 الگوریتم Leaky Bucket
🔹 تعریف:
الگوریتم Leaky Bucket یکی از الگوریتمهای Traffic Shaping (شکلدهی به ترافیک شبکه) است که وظیفه دارد جریان دادهها در شبکه را کنترل و هموارسازی (regulate) کند.
در این روش، بستههای ورودی (packets) در یک بافر با اندازهی ثابت (سطل) ذخیره میشوند و سپس با نرخ ثابتی از سطل خارج و به شبکه ارسال میگردند.
اگر بافر پر شود، بستههای اضافی دور انداخته میشوند (discarded).
📊 ویژگیها:
✅ هموارسازی ترافیک ناگهانی (bursty traffic) با ارسال در نرخ ثابت
✅ حذف بستههای اضافی در صورت پر شدن بافر
مثال: اگر هاستی در حالت عادی با نرخ ۳ Mbps متعهد است، الگوریتم تضمین میکند حتی در زمان ارسال ناگهانی دادهها، نرخ خروجی از ۳ Mbps بیشتر نشود.
⚙️ نحوهی کار الگوریتم Leaky Bucket:
الگوریتم Leaky Bucket معمولاً از یک صف (First In, First Out) FIFOبرای مدیریت بستهها استفاده میکند.
🔸 برای بستههای با اندازهی ثابت:
در هر تیک ساعت (Clock Tick)، تعداد ثابتی از بستهها از صف حذف و ارسال میشوند.
🔸 برای بستههای با اندازهی متغیر (Variable-size packets):
ارسال دادهها بر اساس یک نرخ ثابت بر حسب بایت یا بیت در ثانیه انجام میشود.
🧠 شبهکد الگوریتم برای بستههای با طول متغیر:
1️⃣ مقدار شمارنده (counter) را در هر تیک ساعت برابر n مقداردهی کن.
2️⃣ تا زمانی که n بزرگتر از اندازهی بستهی موجود در سر صف است:
• بستهای از ابتدای صف خارج کن (P)
• بسته P را به شبکه ارسال کن
• شمارنده را به اندازهی سایز بسته کاهش بده
3️⃣ در تیک بعدی ساعت، مقدار شمارنده را مجدداً برابر n قرار بده و مرحله 1 را تکرار کن.
📦 مثال عددی:
فرض کنید:
n = 1000
و اندازهی بستههای موجود در صف به ترتیب:
200, 400, 450 بایت هستند.
🔹 مرحله 1️⃣:
چون n > 200 → بسته ۲۰۰ بایتی ارسال میشود.
n = 1000 - 200 = 800
🔹 مرحله 2️⃣:
چون n > 400 → بسته ۴۰۰ بایتی ارسال میشود.
n = 800 - 400 = 400
🔹 مرحله 3️⃣:
چون n < 450 → الگوریتم در این تیک متوقف میشود.
در تیک بعدی ساعت، n مجدداً برابر ۱۰۰۰ شده و فرآیند از ابتدا تکرار میشود تا زمانی که همهی بستهها ارسال شوند.
🚀 Leaky Bucket Algorithm – C# Implementation
در ادامهی توضیح الگوریتم، اینجا پیادهسازی کامل اون رو در زبان #C میبینیم 👇
💻 کد:
// C# Implementation of Leaking Bucket Algorithm
using System;
class LeakingBucket
{
static void Main(string[] args)
{
int no_of_queries, storage, output_pkt_size;
int input_pkt_size, bucket_size, size_left;
// Initial packets in the bucket
storage = 0;
// Total number of times bucket content is checked
no_of_queries = 4;
// Total number of packets that can be accommodated in the bucket
bucket_size = 10;
// Number of packets that enter the bucket at a time
input_pkt_size = 4;
// Number of packets that exit the bucket at a time
output_pkt_size = 1;
for (int i = 0; i < no_of_queries; i++)
{
size_left = bucket_size - storage; // space left in the bucket
if (input_pkt_size <= size_left)
{
storage += input_pkt_size;
}
else
{
Console.WriteLine("Packet loss = " + input_pkt_size);
}
Console.WriteLine($"Buffer size = {storage} out of bucket size = {bucket_size}");
// Sending packets out of the bucket
storage -= output_pkt_size;
}
}
}
🚀 Leaky Bucket Algorithm – Output & Comparison
💻 خروجی برنامه:
Buffer size= 4 out of bucket size= 10
Buffer size= 7 out of bucket size= 10
Buffer size= 10 out of bucket size= 10
Packet loss = 4
Buffer size= 9 out of bucket size= 10
⚖️ تفاوت بین Leaky Bucket و Token Bucket
🌀 Leaky Bucket:
وقتی میزبان میخواهد بستهای بفرستد، آن بسته داخل سطل قرار میگیرد.
سطل با نرخ ثابت نشت میکند، یعنی دادهها با سرعت یکنواخت از آن خارج میشوند.
این روش ترافیکهای پرنوسان (bursty traffic) را به ترافیک یکنواخت تبدیل میکند.
در عمل، سطل مانند یک صف محدود است که خروجی آن نرخ ثابتی دارد.
🔸 Token Bucket:
در این روش، سطل شامل توکنهایی است که در فواصل زمانی منظم تولید میشوند.
هر زمان بستهای آمادهی ارسال باشد، یک توکن از سطل برداشته و بسته ارسال میشود.
اگر سطل خالی از توکن باشد، بسته نمیتواند ارسال شود.
سطل ظرفیت نهایی دارد و میتواند تا حد مشخصی توکن ذخیره کند.
🌟 مزایای Leaky Bucket نسبت به Token Bucket
✅ بدون هدررفت توکنها:
در Token Bucket ممکن است توکنها بدون استفاده باقی بمانند، اما در Leaky Bucket تنها در زمان وجود ترافیک داده ارسال میشود.
⚡️ تأخیر کمتر:
در Token Bucket، اگر سطل خالی باشد بستهها منتظر میمانند، اما Leaky Bucket با ارسال ثابت، تأخیر را کاهش میدهد.
🔁 انعطافپذیری بالاتر:
Leaky Bucket بهراحتی با تغییرات الگوهای ترافیکی سازگار میشود.
🧩 سادگی در پیادهسازی:
در مقایسه با Token Bucket، پیادهسازی و مدیریت آن آسانتر است.
📡 استفادهی مؤثر از پهنای باند:
با ارسال دادهها با نرخ یکنواخت، از ازدحام شبکه جلوگیری میکند.
🏁 جمعبندی:
🔹 الگوریتم Leaky Bucket یکی از کلیدیترین روشها برای Traffic Shaping در شبکههاست.
🔹 این الگوریتم به کنترل جریان دادهها، جلوگیری از ازدحام، و بهینهسازی عملکرد سیستم کمک زیادی میکند.
🏷 هشتگها:
#CSharp #Networking #LeakyBucket #TokenBucket #RateLimiting #TrafficShaping