C# Geeks (.NET) – Telegram
📩 پیام‌رسانی ساده در NET. با Redis Pub/Sub


ردیس یک انتخاب محبوب برای کش کردن داده‌ها است، اما قابلیت‌های آن بسیار فراتر از این است. یکی از ویژگی‌های کمتر شناخته شده آن، پشتیبانی از Pub/Sub است. کانال‌های Redis یک رویکرد جذاب برای پیاده‌سازی پیام‌رسانی Real-time در برنامه‌های NET. شما ارائه می‌دهند. با این حال، همانطور که به زودی خواهید دید، کانال‌ها دارای نقاط ضعفی نیز هستند. 🧐

💡 در این پست بررسی خواهیم کرد:

• اصول اولیه کانال‌های Redis.

• موارد استفاده عملی برای کانال‌ها.

• پیاده‌سازی یک مثال Pub/Sub در NET.

• ابطال کش (Cache Invalidation) در سیستم‌های توزیع شده.

بیایید وارد جزئیات شویم! 👇

🌐 کانال‌های ردیس (Redis Channels)

Redis channels
کانال‌های ارتباطی نام‌گذاری شده‌ای هستند که الگوی پیام‌رسانی Publish/Subscribe را پیاده‌سازی می‌کنند. 🗣

هر کانال با یک نام منحصر به فرد (مانند notifications یا updates) شناسایی می‌شود. کانال‌ها تحویل پیام از Publishers (ناشران) به Subscribers (مشترکین) را تسهیل می‌کنند.

Publishers
از دستور PUBLISH برای ارسال پیام به یک کانال خاص استفاده می‌کنند.

Subscribers
از دستور SUBSCRIBE برای ثبت علاقه به دریافت پیام از یک کانال استفاده می‌کنند.

🔄 مدل Topic-Based

کانال‌های Redis از یک مدل Publish-Subscribe مبتنی بر Topic (موضوع) پیروی می‌کنند. چندین Publisher می‌توانند به یک کانال پیام ارسال کنند و چندین Subscriber می‌توانند پیام‌ها را از آن کانال دریافت کنند.

⚠️ محدودیت کلیدی: عدم ذخیره پیام
با این حال، توجه به این نکته بسیار حیاتی است که کانال‌های Redis پیام‌ها را ذخیره نمی‌کنند. اگر هنگام انتشار یک پیام، هیچ مشترکی برای یک کانال وجود نداشته باشد، آن پیام بلافاصله دور ریخته می‌شود.

کانال‌های Redis دارای معناشناسی "حداکثر یک بار تحویل" (at-most-once delivery) هستند.
📩 موارد استفاده عملی (Practical Use Cases)

با توجه به اینکه کانال‌های Redis با معناشناسی "حداکثر یک بار تحویل" (at-most-once delivery) کار می‌کنند (پیام‌ها در صورت عدم حضور مشترک ممکن است از دست بروند)، این کانال‌ها برای سناریوهایی مناسب هستند که از دست دادن گاه‌به‌گاه پیام قابل قبول بوده و ارتباط Real-time یا نزدیک به Real-time مطلوب است.

🎯 چند مورد استفاده ممکن:

🔹️فیدهای شبکه‌های اجتماعی: انتشار پست‌ها یا به‌روزرسانی‌های جدید برای کاربران. 📢

🔹️به‌روزرسانی امتیازات زنده: ارسال امتیازات زنده بازی‌ها یا به‌روزرسانی‌های ورزشی برای مشترکین. ⚽️

🔹️اپلیکیشن‌های چت: تحویل پیام‌های چت به صورت Real-time به شرکت‌کنندگان فعال. 💬

🔹️ویرایش مشارکتی: انتشار تغییرات در محیط‌های ویرایش مشارکتی.

🔹️به‌روزرسانی‌های Distributed Cache: ابطال ورودی‌های کش در سرورهای متعدد هنگام تغییر داده‌ها. (این مورد را در ادامه با جزئیات بیشتر پوشش خواهیم داد.)

💡نکته مهم: کانال‌های Redis بهترین انتخاب برای داده‌های حیاتی که از دست دادن پیام در آن‌ها غیرقابل قبول است، نیستند. در چنین مواردی، باید یک سیستم پیام‌رسانی با قابلیت اطمینان بالاتر را در نظر بگیرید. 🔐

💻 پیاده‌سازی Pub/Sub با Redis Channels در NET.


ما برای ارسال پیام‌ها با کانال‌های Redis از کتابخانه StackExchange.Redis استفاده خواهیم کرد.

📥 نصب و راه‌اندازی
ابتدا آن را نصب می‌کنیم:
Install-Package StackExchange.Redis

شما می‌توانید Redis را به صورت محلی در یک کانتینر Docker اجرا کنید. پورت پیش‌فرض 6379 است:
docker run -it -p 6379:6379 redis


📤 سرویس Producer (ناشر)
در اینجا یک سرویس Background ساده آمده است که به عنوان Producer (ناشر) پیام‌های ما عمل می‌کند.

ما با اتصال به نمونه Redis خود یک ConnectionMultiplexer ایجاد می‌کنیم. این به ما امکان می‌دهد تا یک ISubscriber به دست آوریم که می‌توانیم از آن برای پیام‌رسانی Pub/Sub استفاده کنیم. ISubscriber ما را قادر می‌سازد تا با تعیین نام کانال، یک پیام را در آن کانال منتشر کنیم.
public class Producer(ILogger<Producer> logger) : BackgroundService
{
private static readonly string ConnectionString = "localhost:6379";
private static readonly ConnectionMultiplexer Connection =
ConnectionMultiplexer.Connect(ConnectionString);
private const string Channel = "messages";

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var subscriber = Connection.GetSubscriber();
while (!stoppingToken.IsCancellationRequested)
{
var message = new Message(Guid.NewGuid(), DateTime.UtcNow);
var json = JsonSerializer.Serialize(message);
await subscriber.PublishAsync(Channel, json);
logger.LogInformation(
"Sending message: {Channel} - {@Message}",
message);
await Task.Delay(5000, stoppingToken);
}
}
}

حالا نوبت به معرفی یک سرویس Background مجزا برای مصرف پیام‌ها می‌رسد. ⬇️
📩 پیام‌رسانی ساده در NET. با Redis Pub/Sub (ادامه)


📥 سرویس Consumer (مصرف‌کننده)
Consumer
به همان نمونه Redis متصل می‌شود و یک ISubscriber به دست می‌آورد. ISubscriber متد SubscribeAsync را در اختیار ما قرار می‌دهد که می‌توانیم از آن برای اشتراک در دریافت پیام از یک کانال مشخص استفاده کنیم. این متد یک Callback Delegate را می‌پذیرد که می‌توانیم از آن برای رسیدگی به پیام دریافتی استفاده کنیم. 🎧
public class Consumer(ILogger<Consumer> logger) : BackgroundService
{
private static readonly string ConnectionString = "localhost:6379";
private static readonly ConnectionMultiplexer Connection =
ConnectionMultiplexer.Connect(ConnectionString);
private const string Channel = "messages";

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var subscriber = Connection.GetSubscriber();
await subscriber.SubscribeAsync(Channel, (channel, message) =>
{
var message = JsonSerializer.Deserialize<Message>(message);
logger.LogInformation(
"Received message: {Channel} - {@Message}",
channel,
message);
});
}
}
💾 ابطال کش در سیستم‌های توزیع شده (Cache Invalidation)

در یک پروژه اخیر، من یک چالش رایج در سیستم‌های توزیع شده را حل کردم: همگام نگه داشتن کش‌ها. 🧩 ما از یک رویکرد کشینگ دو سطحی استفاده می‌کردیم:

کش in-memory روی هر وب سرور برای دسترسی فوق‌سریع.

کش مشترک Redis برای جلوگیری از ضربه زدن مکرر به پایگاه داده.

مشکل این بود که وقتی داده‌ای در پایگاه داده تغییر می‌کرد، به راهی نیاز داشتیم تا به سرعت به تمام وب سرورها بگوییم که کش‌های in-memory خود را پاک کنند. اینجا بود که Redis Pub/Sub به کمک آمد. ما یک کانال Redis را به طور خاص برای پیام‌های ابطال کش راه‌اندازی کردیم. 📣

💻 پیاده‌سازی CacheInvalidationBackgroundService

هر برنامه یک سرویس CacheInvalidationBackgroundService را اجرا می‌کند که در کانال ابطال کش عضو می‌شود (Subscribe می‌کند):
public class CacheInvalidationBackgroundService(
IServiceProvider serviceProvider)
: BackgroundService
{
public const string Channel = "cache-invalidation";

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await subscriber.SubscribeAsync(Channel, (channel, key) =>
{
var cache = serviceProvider.GetRequiredService<IMemoryCache>();
cache.Remove(key);
return Task.CompletedTask;
});
}
}

هر زمان که داده‌ای در پایگاه داده تغییر می‌کند، ما یک پیام را در این کانال با کلید کش داده به‌روز شده، منتشر می‌کنیم. تمام وب سرورها در این کانال عضو هستند، بنابراین فوراً از حذف داده قدیمی از کش‌های in-memory خود مطلع می‌شوند. از آنجایی که کش in-memory در صورت از کار افتادن برنامه پاک می‌شود، از دست دادن پیام‌های ابطال کش مشکلی ایجاد نمی‌کند. این کار کش‌های ما را سازگار نگه می‌دارد و تضمین می‌کند که کاربران ما همیشه به‌روزترین اطلاعات را می‌بینند.

📝 خلاصه

Redis Pub/Sub
یک راه‌حل جادویی برای هر نیاز پیام‌رسانی نیست، اما سادگی و سرعت آن، آن را به یک ابزار ارزشمند تبدیل می‌کند. کانال‌ها به ما امکان می‌دهند تا به راحتی ارتباط بین مؤلفه‌های با اتصال سست (loosely coupled components) را پیاده‌سازی کنیم. 🔗

کانال‌های Redis دارای معناشناسی "حداکثر یک بار تحویل" (at-most-once delivery) هستند، بنابراین برای مواردی که از دست دادن گاه‌به‌گاه پیام قابل قبول است، بهترین گزینه می‌باشند.

موفق باشید! 👋

🔖 هشتگ‌ها:
#Redis #PubSub #CacheInvalidation #DistributedSystems #DotNet #Messaging #BackgroundService
Forwarded from thisisnabi.dev [Farsi]
کتاب خوب بخونیم.
🌐Overview of HTTP
🌐 مروری بر HTTP

بدانیم که HTTP پروتکلی برای واکشی (fetching) منابعی مانند اسناد HTML است. این پروتکل، بنیان هرگونه تبادل داده در وب محسوب می‌شود و یک پروتکل کلاینت-سرور (Client-Server) است. این بدان معناست که درخواست‌ها توسط گیرنده، که معمولاً مرورگر وب است، آغاز می‌شوند. 🖥 یک سند کامل معمولاً از منابعی مانند محتوای متنی، دستورالعمل‌های طرح‌بندی (layout)، تصاویر، ویدئوها، اسکریپت‌ها و موارد دیگر ساخته می‌شود.

📬 تبادل پیام‌ها (Messages)

کلاینت‌ها (Clients) و سرورها (Servers) از طریق تبادل پیام‌های مجزا با یکدیگر ارتباط برقرار می‌کنند (برخلاف یک جریان داده یا stream).

• پیام‌هایی که توسط کلاینت ارسال می‌شوند، درخواست (Requests) نامیده می‌شوند.

• پیام‌هایی که توسط سرور به عنوان پاسخ ارسال می‌شوند، پاسخ (Responses) نام دارند.

⚙️ ماهیت و تکامل HTTP

ماهیت HTTP که در اوایل دهه ۱۹۹۰ طراحی شد، یک پروتکل قابل توسعه (Extensible) است که در طول زمان تکامل یافته است.

این پروتکل یک پروتکل لایه کاربرد (Application Layer Protocol) است که روی TCP یا یک اتصال TCP رمزگذاری شده با TLS ارسال می‌شود، اگرچه از لحاظ نظری هر پروتکل انتقال قابل اعتماد دیگری می‌تواند استفاده شود.

به دلیل قابلیت توسعه‌پذیری، HTTP نه تنها برای واکشی hypertext documents، بلکه برای تصاویر و ویدئوها یا ارسال محتوا به سرورها (مانند نتایج فرم‌های HTML) نیز استفاده می‌شود. همچنین می‌توان از HTTP برای واکشی بخش‌هایی از اسناد استفاده کرد تا صفحات وب را در صورت نیاز به‌روزرسانی کند. 🔄

📋 در این مقاله موارد زیر بررسی خواهند شد:

• اجزای سیستم‌های مبتنی بر HTTP
• جنبه‌های اساسی HTTP
• آنچه با HTTP قابل کنترل است
• جریان HTTP (HTTP flow)
• پیام‌های HTTP (HTTP Messages)
• و Api های مبتنی بر HTTP
🏗 اجزای سازنده سیستم‌های مبتنی بر HTTP

گفتیم HTTP یک پروتکل کلاینت-سرور (Client-Server) است: درخواست‌ها توسط یک موجودیت به نام User-agent (یا یک پروکسی به نمایندگی از آن) ارسال می‌شوند. 👤

اغلب اوقات،User-agent همان مرورگر وب است، اما می‌تواند هر چیزی باشد؛ برای مثال، یک ربات که وب را می‌خزد تا ایندکس موتور جستجو را پر کرده و نگهداری کند.

هر درخواست مجزا به یک سرور ارسال می‌شود، که آن را مدیریت کرده و پاسخی به نام پاسخ (Response) ارائه می‌دهد. بین کلاینت و سرور، موجودیت‌های متعددی وجود دارند که در مجموع پروکسی‌ها (Proxies) نامیده می‌شوند و عملیات‌های مختلفی را انجام می‌دهند و مثلاً به عنوان دروازه (Gateways) یا کش‌ها (Caches) عمل می‌کنند. 🛡

⚠️ ملاحظات لایه‌های زیرین

در واقعیت، کامپیوترهای بیشتری بین یک مرورگر و سروری که درخواست را مدیریت می‌کند، وجود دارند: مسیریاب‌ها (Routers)، مودم‌ها و غیره. به لطف طراحی لایه‌ای وب، این موارد در لایه‌های Network و Transport پنهان هستند. HTTP در بالا، در Application Layer قرار دارد. اگرچه لایه‌های زیرین برای تشخیص مشکلات شبکه مهم هستند، اما در توصیف HTTP عمدتاً بی‌ربط تلقی می‌شوند.

1️⃣ کلاینت: (User-agent)

گفتیم User-agent هر ابزاری است که از طرف کاربر عمل می‌کند. این نقش در درجه اول توسط مرورگر وب انجام می‌شود، اما همچنین ممکن است توسط برنامه‌هایی که مهندسان و توسعه‌دهندگان وب برای Debug کردن برنامه‌های خود استفاده می‌کنند، ایفا شود. 🛠

مرورگر همیشه موجودیتی است که درخواست را آغاز می‌کند. این کار هرگز توسط سرور انجام نمی‌شود (اگرچه مکانیسم‌هایی در طول سال‌ها برای شبیه‌سازی پیام‌های آغاز شده توسط سرور اضافه شده‌اند).

📖 فرایند نمایش صفحه

برای نمایش یک صفحه وب، مرورگر یک درخواست اولیه برای واکشی سند HTML که نمایانگر صفحه است، ارسال می‌کند. سپس این فایل را تجزیه (Parse) می‌کند و درخواست‌های اضافی مربوط به اسکریپت‌های اجرایی، اطلاعات طرح‌بندی (CSS) برای نمایش، و زیرمنابع موجود در صفحه (معمولاً تصاویر و ویدئوها) را ایجاد می‌کند. سپس مرورگر وب این منابع را ترکیب می‌کند تا سند کامل، یعنی صفحه وب را ارائه دهد. اسکریپت‌هایی که توسط مرورگر اجرا می‌شوند، می‌توانند منابع بیشتری را در مراحل بعدی واکشی کنند و مرورگر صفحه وب را بر اساس آن به‌روز می‌کند. 🔄

🔗 مفهوم Hypertext

یک صفحه وب یک سند Hypertext است. این بدان معنی است که برخی از بخش‌های محتوای نمایش داده شده، لینک هستند که می‌توانند فعال شوند (معمولاً با کلیک ماوس) تا یک صفحه وب جدید واکشی شود و به کاربر اجازه می‌دهد تا عامل کاربر خود را هدایت کرده و در وب ناوبری (Navigate) کند. مرورگر این دستورالعمل‌ها را به درخواست‌های HTTP ترجمه می‌کند و پاسخ‌های HTTP را تفسیر می‌کند تا یک پاسخ واضح به کاربر ارائه دهد.

2️⃣ سرور وب (The Web Server)

در سمت مخالف کانال ارتباطی، سرور قرار دارد که سندی را که توسط کلاینت درخواست شده است، سرو (Serve) می‌کند. 💾

یک سرور از نظر مجازی فقط به عنوان یک ماشین واحد ظاهر می‌شود؛ اما در واقع ممکن است مجموعه‌ای از سرورها باشد که بار را به اشتراک می‌گذارند (Load Balancing)، یا نرم‌افزارهای دیگری (مانند کش‌ها، یک سرور پایگاه داده، یا سرورهای تجارت الکترونیک)، که سند را به طور کامل یا جزئی بر اساس درخواست تولید می‌کنند.

نکته: یک سرور لزوماً یک ماشین واحد نیست، بلکه چندین نمونه نرم‌افزار سرور می‌توانند روی یک ماشین میزبانی شوند. با HTTP/1.1 و هدر Host، حتی ممکن است آدرس IP یکسانی داشته باشند. 🏠

3️⃣ پروکسی‌ها (Proxies)

بین مرورگر وب و سرور، کامپیوترها و ماشین‌های متعددی پیام‌های HTTP را منتقل می‌کنند. به دلیل ساختار لایه‌ای پشته وب، اکثر این‌ها در لایه‌های انتقال، شبکه یا فیزیکی عمل می‌کنند و در لایه HTTP شفاف می‌شوند و به طور بالقوه تأثیر قابل توجهی بر عملکرد دارند. ⚙️

آن‌هایی که در لایه‌های کاربرد عمل می‌کنند، عموماً پروکسی‌ها نامیده می‌شوند. این پروکسی‌ها می‌توانند شفاف (Transparent) باشند و درخواست‌هایی را که دریافت می‌کنند بدون هیچ تغییری به جلو ارسال کنند، یا غیرشفاف (Non-transparent) باشند، که در این صورت قبل از ارسال درخواست به سرور، آن را به نحوی تغییر خواهند داد.

🎯 وظایف پروکسی‌ها

پروکسی‌ها ممکن است عملکردهای متعددی را انجام دهند:

🔹️کشینگ (Caching): (کش می‌تواند عمومی یا خصوصی باشد، مانند کش مرورگر)

🔹️فیلترینگ (Filtering): (مانند اسکن آنتی‌ویروس یا کنترل والدین) 🛡

🔹️توزیع بار (Load Balancing): (برای اجازه دادن به سرورهای متعدد برای ارائه درخواست‌های مختلف)

🔹️احراز هویت (Authentication): (برای کنترل دسترسی به منابع مختلف)

🔹️ثبت وقایع (Logging): (اجازه ذخیره اطلاعات تاریخی) 📜
🔑 جنبه‌های اساسی HTTP
1️⃣کلا HTTP ساده است (HTTP is simple)

به طور کلی طوری طراحی شده است که برای انسان‌ها قابل خواندن باشد، حتی با پیچیدگی‌های اضافه شده در HTTP/2 از طریق کپسوله‌سازی پیام‌های HTTP در Frameها. 📖

پیام‌های HTTP را می‌توان توسط انسان خواند و درک کرد، که آزمایش را برای توسعه‌دهندگان آسان‌تر کرده و پیچیدگی را برای تازه‌واردها کاهش می‌دهد.

2️⃣ HTTP قابل توسعه است (HTTP is extensible)

با معرفی HTTP Headers در HTTP/1.0، این پروتکل به راحتی قابل توسعه و آزمایش است. 🛠 عملکرد جدید حتی می‌تواند از طریق توافق بین یک کلاینت و یک سرور در مورد معناشناسی (semantics) یک هدر جدید معرفی شود.

3️⃣ و HTTP بدون وضعیت است، اما بدون Session نیست (HTTP is stateless, but not sessionless)
بدون وضعیت (Stateless) است: هیچ ارتباطی بین دو درخواستی که به طور متوالی روی یک اتصال انجام می‌شوند، وجود ندارد.

اما در حالی که هسته HTTP خودش بدون وضعیت است، کوکی‌های HTTP اجازه استفاده از Sessionهای با وضعیت (Stateful Sessions) را می‌دهند. با استفاده از قابلیت توسعه هدر، کوکی‌های HTTP به گردش کار اضافه می‌شوند و امکان ایجاد Session در هر درخواست HTTP را برای به اشتراک گذاشتن زمینه (Context) یا همان وضعیت (State) فراهم می‌کنند. 🍪

4️⃣حالا نوبت HTTP و اتصالات (HTTP and connections)

اتصال در لایه انتقال کنترل می‌شود و بنابراین اساساً خارج از حوزه HTTP است. HTTP نیاز ندارد که پروتکل انتقال زیرین مبتنی بر اتصال باشد؛ بلکه تنها نیازمند این است که قابل اعتماد (reliable) باشد، یا پیام‌ها را از دست ندهد (حداقل در چنین مواردی خطا ارائه دهد).

از بین دو پروتکل انتقال رایج در اینترنت، TCP قابل اعتماد است و UDP نیست. بنابراین HTTP بر استاندارد TCP تکیه دارد که مبتنی بر اتصال است. 🌐

⚙️ بهینه‌سازی اتصالات

قبل از اینکه یک کلاینت و سرور بتوانند یک جفت درخواست/پاسخ HTTP را تبادل کنند، باید یک اتصال TCP ایجاد کنند، فرآیندی که نیاز به چندین رفت و برگشت (round-trips) دارد. 🐢

رفتار پیش‌فرض HTTP/1.0 این بود که یک اتصال TCP مجزا برای هر جفت درخواست/پاسخ HTTP باز کند. این کار در هنگام ارسال درخواست‌های متعدد در فاصله زمانی کوتاه، کارایی کمتری دارد.

برای کاهش این نقص، HTTP/1.1 پایپ‌لاینینگ (pipelining) (که پیاده‌سازی آن دشوار بود) و اتصالات پایدار (persistent connections) را معرفی کرد: اتصال TCP زیرین می‌تواند با استفاده از هدر Connection تا حدی کنترل شود.

بعد HTTP/2 یک گام فراتر رفت و با Multiplexing پیام‌ها روی یک اتصال واحد، به گرم نگه داشتن اتصال و کارایی بیشتر کمک کرد.

آزمایش‌هایی برای طراحی یک پروتکل انتقال بهتر که مناسب‌تر برای HTTP باشد، در حال انجام است. برای مثال، Google در حال آزمایش QUIC است که بر پایه UDP ساخته شده تا یک پروتکل انتقال قابل اعتمادتر و کارآمدتر را فراهم کند.
🔁 جریان 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 ایجاد کلاس‌ها و اشیایی با مسئولیت‌ها و رابط‌های مشخص را تشویق می‌کنند. این باعث می‌شود کد قابل استفاده مجدد باشد و اجزا به راحتی در بخش‌های مختلف سیستم یا پروژه‌های جدید استفاده شوند، که بهره‌وری را افزایش داده و زمان توسعه را کاهش می‌دهد.