C# Geeks (.NET) – Telegram
3️⃣Publish/Subscribe Pattern
3️⃣ Publish/Subscribe Pattern 📣📡(بخش سوم)

ءPublish/Subscribe Pattern یک الگوی پیام‌رسانی غیرهمزمان است که در آن Publisher‌ها پیام‌ها یا Eventها را به یک Message Broker یا Event Bus مرکزی ارسال می‌کنند، بدون اینکه بدانند چه کسی مصرف‌کننده آن‌هاست.

در مقابل، Subscriberها علاقه‌مندی خود را به انواع خاصی از پیام‌ها ثبت می‌کنند و به‌صورت خودکار هنگام انتشار آن‌ها را دریافت می‌کنند. این الگو منجر به یک Event-Driven Architecture با Coupling بسیار کم می‌شود.

🔍 تفاوت اصلی Publish/Subscribe با Point To Point Async Integration

ءPublish/Subscribe فرض می‌کند که چندین Subscriber می‌توانند برای یک نوع Event وجود داشته باشند

ءPoint To Point Async Integration فرض می‌کند که فقط یک Subscriber برای هر نوع پیام وجود دارد

🔧 How it works:

🔹️ءPublisherها پیام‌ها یا Eventها را به Topic یا Channel‌های Message Broker ارسال می‌کنند بدون اینکه از Subscriberها اطلاعی داشته باشند

🔸️ءMessage Broker پیام‌ها را دریافت، ذخیره و توزیع می‌کند

🔹️ءSubscriberها علاقه‌مندی خود را به Topic یا Event Type خاص ثبت می‌کنند

🔸️با انتشار یک پیام، Broker نسخه‌ای از آن را به تمام Subscriberهای فعال ارسال می‌کند

🔹️چندین Subscriber می‌توانند هم‌زمان و مستقل یک پیام یکسان را پردازش کنند

🔸️این الگو از ارتباط One-to-Many پشتیبانی می‌کند

🔹️اضافه یا حذف Subscriberها نیازی به تغییر در Publisher ندارد

🔸️امکان Message Filtering وجود دارد تا هر Subscriber فقط پیام‌های مرتبط را دریافت کند

Benefits:

• ءDecoupling کامل بین Publisher و Subscriber

• ءScalability بالا با پردازش موازی پیام‌ها توسط چند Subscriber

• مناسب برای Event-Driven Architecture

• افزودن قابلیت‌های جدید فقط با اضافه کردن Subscriber جدید

• ءResilience بهتر؛ خطای یک Subscriber روی بقیه تأثیر ندارد

Drawbacks:

• ءMessage Broker یک وابستگی مهم است و می‌تواند Single Point of Failure باشد

• ءDebugging و Tracing سخت‌تر به‌دلیل ماهیت غیرهمزمان

• چالش‌های Eventual Consistency

• نیاز به طراحی دقیق برای Ordering پیام‌ها و Duplicate Handling

🎯 Use cases:

🔹️سیستم‌های Event-Driven که چند سرویس باید به یک Event واکنش نشان دهند

🔸️ءReal-time Notification (چت، داشبوردها، مانیتورینگ)

🔹️ءMicroservices برای همگام‌سازی داده‌ها از طریق Integration Events

🔸️سناریوهایی که سرویس‌های جدید باید بدون تغییر Publisher به Eventها گوش دهند

🔹️ءWorkflowهایی که با یک Event چند مرحله در سرویس‌های مختلف فعال می‌شوند
Forwarded from Sonora.Dev
🛠 حل مشکل Double Booking در سیستم‌های رزرو

تمام پلتفرم‌های رزرو مدرن با چالش Double Booking روبرو هستند: وقتی دو یا چند کاربر به‌طور همزمان تلاش می‌کنند یک منبع محدود را رزرو کنند.

این مشکل، یک race condition است که می‌تواند اعتماد کاربر را نابود کند و برای سیستم‌های پرترافیک، بحرانی است.

1️⃣ Pessimistic Locking

مکانیزم: قفل روی رکورد دیتابیس (SELECT ... FOR UPDATE)

مزایا: تضمین Consistency، جلوگیری از race condition

معایب: Throughput محدود، Deadlock Risk، مقیاس‌پذیری پایین

مناسب برای: Low-traffic / کم‌رقابت (مثل Web Check-in هواپیما)

2️⃣ Optimistic Locking


مکانیزم: بدون قفل، با استفاده از Versioning

مزایا: عملکرد خواندن بالا، افزایش concurrency

معایب: Conflict و Retry در High Contention، افزایش load روی DB

مناسب برای: Moderate traffic و منابع کم‌رقابت (رزرو هتل، رستوران)

3️⃣ In-Memory Distributed Locking


مکانیزم: Lock توزیع‌شده در Redis / In-Memory Cache

مزایا: کاهش فشار روی دیتابیس، High Concurrency، Low Latency

معایب: پیچیدگی زیرساخت، مدیریت crash و expiration، ریسک Lock ناتمام

مناسب برای: Popular events با 1K–10K RPS

4️⃣ Virtual Waiting Queue


مکانیزم: Async Queue + Backpressure + FIFO

مزایا:

محافظت از دیتابیس و cache در برابر surge

بهبود تجربه کاربری و fairness

مقیاس‌پذیری بسیار بالا (High Throughput)

معایب: پیچیدگی عملیاتی، نیاز به SSE یا WebSocket برای اطلاع‌رسانی

مناسب برای: Ultra High Traffic events (کنسرت‌ها، فیلم‌های بلاک‌باستر)

جمع‌بندی فنی

هیچ راه‌حل واحدی برای همه سناریوها وجود ندارد

انتخاب معماری به الگوی ترافیک، سطح رقابت و محدودیت منابع وابسته است

سیستم‌های High-Traffic باید Lock-free + Async + Fair Queue داشته باشند تا Tail Latency و double booking کنترل شود

Monitoring، Retry Policies و Backpressure، اجزای کلیدی در طراحی سیستم رزرو مقیاس‌پذیر هستند


#SystemDesign #DistributedSystems #Scalability #Concurrency #BackendArchitecture #HighTraffic #BookingSystems #Microservices #Queueing
بهترین رهبران پر سروصدا نیستند.
برخی از مورد اعتمادترین افراد در جمع، کمترین صحبت را می‌کنند.

شما این نوع افراد را می‌شناسید:

🔹️حرفشان را عملی می‌کنند و به قول خود پایبندند
🔸️قبل از صحبت کردن با دقت گوش می‌دهند
🔹️برای دیگران فضا ایجاد می‌کنند بدون اینکه دنبال اعتبار باشند
🔸️آن‌ها در میان آشوب، آرامش می‌آورند. نیازی ندارند قدرت یا نفوذ خود را نشان دهند.

آن‌ها این اعتماد را به دست آورده‌اند.
اغلب ما رهبر بودن را با کاریزما، دیده‌شدن یا جسارت مرتبط می‌کنیم، اما قابل اعتماد بودن، تواضع و شایستگی، توان رهبر بودن را چند برابر می‌کند.

اگر می‌خواهید بدون داشتن عنوان، رهبری کنید، ابتدا با کسی باشید که دیگران بتوانند روی او حساب کنند، به‌ویژه زمانی که اوضاع پیچیده و نامنظم است.
4️⃣ Outbox Pattern
4️⃣ Outbox Pattern 📤📦(بخش چهارم)

ءOutbox Pattern با ذخیره‌کردن Eventها در یک جدول دیتابیس (Outbox) در همان Transaction تغییرات داده‌های بیزینسی، انتشار قابل‌اعتماد Eventها را تضمین می‌کند.

سپس یک فرآیند جداگانه، Eventها را از Outbox خوانده و به Message Broker منتشر می‌کند.
به این شکل تضمین می‌شود که Eventها اگر و فقط اگر Transaction بیزینسی موفق باشد منتشر شوند.

🔧 How it works:

🔹️وقتی یک سرویس داده بیزینسی را تغییر می‌دهد، هم‌زمان رکورد Event را در جدول Outbox و در همان Transaction دیتابیس درج می‌کند

🔸️ءTransaction دیتابیس، Atomicity بین تغییر داده و ایجاد Event را تضمین می‌کند

🔹️یک فرآیند Background یا Worker به‌صورت مداوم جدول Outbox را برای Eventهای منتشرنشده بررسی می‌کند

🔸️این فرآیند Eventها را خوانده و به Message Broker ارسال می‌کند

🔹️بعد از انتشار موفق، Event به‌عنوان پردازش‌شده علامت‌گذاری یا از Outbox حذف می‌شود

🔸️اگر انتشار شکست بخورد، Event در Outbox باقی می‌ماند و به‌صورت خودکار Retry می‌شود

🔹️این الگو مشکل Dual-Write (ذخیره داده موفق، اما انتشار Event ناموفق) را حذف می‌کند

🔸️ءEventها حداقل یک‌بار (At-Least-Once) منتشر می‌شوند و Consumerها باید Duplicateها را مدیریت کنند

Benefits:

• تضمین می‌کند Eventها فقط در صورت موفقیت Transaction بیزینسی منتشر شوند

• مشکل Dual-Write بین دیتابیس و Message Broker را به‌طور کامل حذف می‌کند

• یک Audit Trail قابل‌اعتماد از تمام Eventهای سیستم در دیتابیس فراهم می‌کند

• امکان Event Replay و Recovery با نگه‌داشتن تاریخچه Eventها

Drawbacks:

• ایجاد Eventual Consistency چون Eventها بلافاصله منتشر نمی‌شوند و Polling دارند

• نیاز به زیرساخت اضافی برای Outbox Processor و مانیتورینگ آن

• ایجاد سربار عملکردی به‌دلیل نوشتن‌های اضافی در دیتابیس و Polling

• نیاز به مدیریت Duplicate Eventها در Consumer (می‌توان از InBox Pattern + Idempotence استفاده کرد)

🎯 Use cases:

🔹️ءMicroservices که باید انتشار Event بعد از تغییر داده‌ها را قطعاً تضمین کنند

🔹️سیستم‌های Event Sourcing که هر تغییر وضعیت باید به‌عنوان Event ثبت شود

🔹️هر سناریویی که Consistency بین سرویس‌ها حیاتی است و از دست رفتن پیام غیرقابل‌قبول است
گنجینه‌های مخفی که هنوز کشف نشده‌اند!
🧠 4️⃣2️⃣ سؤال مهم مصاحبه برای Software Architect

اگر قراره در نقش Software Architect مصاحبه بدی (یا مصاحبه بگیری)، این سؤال‌ها فقط دانش فنی رو نمی‌سنجن؛
بلکه طرز فکر معماری، تصمیم‌گیری و تجربه‌ی واقعی تو رو محک می‌زنن.

1️⃣ چطور بین Monolith، Modular Monolith و Microservices برای یک سیستم جدید تصمیم می‌گیری؟

2️⃣ ءTrade-off بین Layered Architecture، Vertical Slice و Hexagonal Architecture چیه؟

3️⃣ چطور اصل Principle of Least Surprise رو در طراحی کامپوننت‌ها رعایت می‌کنی؟

4️⃣ موقع Scale کردن سیستم، چطور جلوی Accidental Complexity رو می‌گیری؟

5️⃣ چه زمانی Synchronous و چه زمانی Asynchronous Communication رو انتخاب می‌کنی؟

6️⃣ چطور Idempotent Operation طراحی می‌کنی وقتی سیستم Retry داره؟

7️⃣ در یک Workflow با throughput بالا، چطور از Race Condition جلوگیری می‌کنی؟

8️⃣ چه زمانی از Queue، چه زمانی از Stream و چه زمانی از Direct Call استفاده می‌کنی؟

9️⃣ نقش Saga Pattern در workflowهای طولانی چیه؟

🔟 چطور سیستم رو برای Exactly-once یا Effectively-once processing طراحی می‌کنی؟

1️⃣1️⃣ با Partial Failure در سیستم‌های توزیع‌شده چطور برخورد می‌کنی؟

2️⃣1️⃣ چه Patternهایی به حفظ Consistency بین چند سرویس کمک می‌کنن؟

3️⃣1️⃣ چطور سیستمی طراحی می‌کنی که در برابر Failure وابستگی‌های خارجی دوام بیاره؟

4️⃣1️⃣ رویکردت برای تشخیص و ایزوله کردن سرویس‌های کند چیه؟

5️⃣1️⃣ چطور Caching Layer (L1/L2) طراحی می‌کنی که هم Stale Data نداشته باشه هم Thundering Herd ایجاد نکنه؟

6️⃣1️⃣ چه نشانه‌هایی میگه سیستم به Vertical Scaling نیاز داره یا Horizontal Scaling؟

7️⃣1️⃣ چطور Read-heavy workload طراحی می‌کنی برای بیشترین throughput؟

8️⃣1️⃣ چه Patternهایی باعث کاهش Load دیتابیس می‌شن بدون ضربه زدن به Consistency؟

9️⃣1️⃣ با مشکل Hot Partition چطور برخورد می‌کنی؟

0️⃣2️⃣ سیستم‌های با Write Volume بالا رو چطور مدیریت می‌کنی؟

1️⃣2️⃣ چطور بین Relational Database و Document Database انتخاب می‌کنی؟

2️⃣2️⃣ چه زمانی Distributed Transaction و چه زمانی Eventual Consistency؟

3️⃣2️⃣ چطور یک سیستم Audit-friendly طراحی می‌کنی بدون اینکه Performance نابود بشه؟

4️⃣2️⃣ استراتژیت برای Schema Evolution بدون Downtime چیه؟

🎯 جمع‌بندی

این سؤال‌ها دنبال جواب کتابی نیستن.
مصاحبه‌کننده می‌خواد بدونه:

• چطور فکر می‌کنی
• چطور تصمیم می‌گیری
• چطور با Trade-offها کنار میای

اگه بتونی پشت هر جواب، تجربه یا منطق داشته باشی، یعنی واقعاً Architect هستی نه فقط عنوانش رو داری.
چگونه با GitHub Actions و NET. یک CI/CD Pipeline بسازیم

آیا می‌خواهید فرآیند توسعه نرم‌افزار خود را ساده‌تر کنید و چرخه‌های انتشار را سریع‌تر انجام دهید؟ 🚀
تصور کنید بتوانید با هر تغییر کد، برنامه‌های NET. خود را به‌صورت خودکار build، test و deploy کنید.

با CI/CD می‌توانید به‌طور قابل‌توجهی کارهای دستی را کاهش دهید و تمرکز بیشتری روی ساخت نرم‌افزار داشته باشید، در نتیجه انتشارهای سریع‌تر و قابل‌اعتمادتری خواهید داشت.
و شروع CI/CD هیچ‌وقت به این اندازه آسان نبوده است.
ءGitHub Actions کاملاً رایگان و ساده برای استفاده هستند.

بنابراین، در این مطلب موارد زیر را بررسی می‌کنیم:

• معرفی CI/CD و GitHub Actions
• ساخت pipeline برای build و test در NET.
• ساخت pipeline برای deployment روی Azure App Service

ءContinuous Integration و Continuous Delivery چیست؟

قبل از اینکه به GitHub Actions بپردازیم، سعی می‌کنم به‌صورت خلاصه توضیح بدهم CI/CD چیست.

ءCI/CD روشی است برای افزایش دفعات تحویل قابلیت‌های جدید، با اضافه کردن اتوماسیون به workflow توسعه نرم‌افزار.

ءContinuous Integration یا «CI» به فرآیند خودکار همگام‌سازی کد جدید با repository اشاره دارد. هر تغییر جدید در کد برنامه بلافاصله build، test و merge می‌شود.

ءContinuous Delivery یا Deployment یا «CD» به خودکارسازی بخش deployment از workflow اشاره دارد. زمانی که تغییری ایجاد می‌کنید که در repository merge می‌شود، این مرحله مسئول deploy کردن آن تغییرات روی محیط production (یا هر محیط دیگری) است.

ءContinuous Integration با GitHub Actions

اگر از GitHub استفاده می‌کنید، شروع Continuous Integration هیچ‌وقت به این راحتی نبوده است.

می‌توانید از GitHub Actions برای خودکارسازی pipeline مربوط به build، test و deployment استفاده کنید. می‌توانید workflowهایی بسازید که هر commit روی repository شما را build و test کنند، یا زمانی که یک tag جدید ساخته می‌شود، deploy به production انجام دهند.

برای ساخت یک GitHub Action، شما یک workflow می‌نویسید که هنگام وقوع یک event خاص در repository اجرا شود. نمونه‌ای از این eventها شامل commit روی branch اصلی، ایجاد یک tag یا اجرای دستی workflow است.

در ادامه یک workflow در GitHub Actions برای build و test یک پروژه NET. آورده شده است:
name: Build & Test 🧪

on:
push:
branches:
- main

env:
DOTNET_VERSION: '7.0.x'

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup .NET 📦
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Install dependencies 📂
run: dotnet restore WebApi

- name: Build 🧱
run: dotnet build WebApi --configuration Release --no-restore

- name: Test 🧪
run: dotnet test WebApi --configuration Release --no-build

بیایید ببینیم اینجا دقیقاً چه اتفاقی می‌افتد 🔍

• تعریف یک event برای trigger کردن workflow ⚡️

• راه‌اندازی NET SDK. با نسخه‌ای که از env.DOTNET_VERSION خوانده می‌شود 📦

• ءRestore، build و test کردن پروژه با استفاده از ابزار dotnet CLI 🧪🧱

می‌توانید همین امروز این workflow را به repository گیت‌هاب خود اضافه کنید 🧑‍💻 و به‌محض commit کردن کد، بازخورد فوری دریافت کنید 🚀

وقتی اجرای workflow به دلیل خطای build یا شکست تست‌ها fail شود ، یک ایمیل اعلان دریافت خواهید کرد 📧
ءContinuous Delivery به Azure با GitHub Actions ☁️

ءContinuous Integration نقطه شروع بسیار خوبی برای CI/CD است، اما ارزش واقعی زمانی مشخص می‌شود که فرآیند deployment را خودکار کنید 🤖

این سناریو را تصور کنید 👇

• شما تغییری در کد ایجاد می‌کنید ✏️

• ءcommit باعث trigger شدن pipeline deployment می‌شود 🔄

• چند دقیقه بعد، تغییرات شما در production در دسترس هستند 🌍

معمولاً موضوع کمی پیچیده‌تر است، چون باید به پیکربندی‌ها، migration دیتابیس و موارد دیگر هم فکر کنیم ⚙️🗄
اما سعی کنید تصویر کلی را ببینید 🧠

اگر برنامه خود را در cloud اجرا می‌کنید، مثلاً روی Azure ☁️، به احتمال زیاد یک GitHub Action آماده برای این کار وجود دارد که می‌توانید از آن استفاده کنید.

در ادامه یک deployment pipeline آورده شده که من برای انتشار برنامه‌ام روی Azure App Service استفاده می‌کنم 🚀
name: Publish 🚀

on:
push:
branches:
- main

env:
AZURE_WEBAPP_NAME: web-api
AZURE_WEBAPP_PACKAGE_PATH: './publish'
DOTNET_VERSION: '7.0.x'

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup .NET 📦
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Build and Publish 📂
run: |
dotnet restore WebApi
dotnet build WebApi -c Release --no-restore
dotnet publish WebApi -c Release --no-build
--output '${{ env.AZURE_WEBAPP_PACKAGE_PATH }}'

- name: Deploy to Azure 🌌
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}
package: '${{ env.AZURE_WEBAPP_PACKAGE_PATH }}'

این workflow شباهت زیادی به workflow قبلی دارد، با این تفاوت‌ها 🔍

اضافه شدن مرحله publish و پیکربندی مسیر خروجی 📤

استفاده از اکشن azure/webapps-deploy@v2 برای deploy روی Azure ☁️

اگر نیاز دارید مقادیر حساس (secret) را به‌صورت امن در workflowها استفاده کنید 🔐، می‌توانید از GitHub secrets استفاده کنید.
شما می‌توانید secrets را در GitHub تعریف کنید و بدون اضافه کردن آن‌ها به source control، در actionها از آن‌ها استفاده کنید.

در workflow مربوط به deployment، من از secrets.AZURE_PUBLISH_PROFILE برای دسترسی به publish profile مربوط به App Service استفاده می‌کنم 🔑

جمع‌بندی 🧩

ءContinuous Integration و Continuous Delivery می‌توانند فرآیند توسعه شما را متحول کنند ⚡️ و سرعت انتشار تغییرات را به‌شدت افزایش دهند 🚀

سعی کنید محاسبه کنید چقدر زمان صرف deployment می‌کنید ⏱️
تقریباً مطمئنم از میزان زمانی که می‌توانید با خودکارسازی ذخیره کنید شگفت‌زده خواهید شد 😮

نکته خوب اینجاست که معمولاً pipelineهای build و deployment را یک‌بار راه‌اندازی می‌کنید و سپس در تمام طول عمر پروژه از مزایای آن‌ها استفاده می‌کنید ♻️

ممنون که خوندید 🙏
امیدوارم مفید بوده باشه
اگر هر مشکلی را برای تیمت حل کنی، در واقع رهبر نیستی. داری جلوی رشدشان را می‌گیری. 🚧🧠

حتی اگر در ظاهر این‌طور به نظر نرسد.

این وسوسه کاملاً طبیعی است، مخصوصاً وقتی باتجربه‌ای.
• مشکل را می‌بینی 👀
• راه‌حل را می‌دانی
• و دلت می‌خواهد کمک کنی 🤝

اما اگر هر بار خودت وارد عمل شوی، این اتفاق‌ها می‌افتد:

1️⃣ تیم به تو وابسته می‌شود
2️⃣ مهارت حل مسئله در آن‌ها رشد نمی‌کند
3️⃣ بدون اینکه بفهمی، خودت تبدیل به گلوگاه سیستم می‌شوی ⛔️

به‌جای آن، این رویکرد را امتحان کن 👇

راهنمایی بده، نه جواب آماده 🧭

بپرس: «به نظرت خودمون باید چیکار کنیم؟» 🤔

اجازه بده افراد (در حد معقول) تقلا کنند تا رشد کنند 💪

کانتکست و دید کلی بده، نه فقط تصمیم نهایی یا اقدام آماده 📚
ءServer-Sent Events در ASP.NET Core و NET 10. 📡🚀

به‌روزرسانی‌های Real-time دیگر یک قابلیت «خوب است داشته باشیم» نیستند.
بیشتر رابط‌های کاربری مدرن انتظار دارند به نوعی جریان زنده‌ای از داده را از سمت سرور دریافت کنند.
سال‌ها در اکوسیستم NET. ، پاسخ پیش‌فرض برای این نیاز SignalR بوده است.
در حالی که SignalR فوق‌العاده قدرتمند است، اما برای سناریوهای ساده‌تر، داشتن گزینه‌های دیگر هم بسیار مفید است

با انتشار ASP.NET Core 10، بالاخره یک API بومی و سطح‌بالا برای Server-Sent Events (SSE) داریم 🎉
این قابلیت فاصله بین polling ساده‌ی HTTP و WebSocketهای دوطرفه از طریق SignalR را پر می‌کند.

🤔 چرا SSE به‌جای SignalR؟

ءSignalR یک ابزار بسیار قدرتمند است که WebSockets، Long Polling و SSE را به‌صورت خودکار مدیریت می‌کند و یک کانال ارتباطی دوطرفه (Full-Duplex) فراهم می‌کند.
اما این قدرت، هزینه‌هایی هم دارد:

• استفاده از یک پروتکل مشخص (Hubs)
• نیاز به کتابخانه‌ی سمت کلاینت
• نیاز به Sticky Session یا Backplane (مثل Redis) برای مقیاس‌پذیری

ءSSE متفاوت است، چون:

➡️ یک‌طرفه (Unidirectional) است: مخصوص استریم داده از سرور به کلاینت

🌐 ءHTTP بومی است: فقط یک درخواست استاندارد HTTP با text/event-stream

🔄 ءReconnect خودکار دارد: مرورگرها به‌صورت Native با API به نام EventSource اتصال مجدد را مدیریت می‌کنند

🪶 سبک و ساده است: بدون کتابخانه‌های سنگین سمت کلاینت یا منطق handshake پیچیده

ساده‌ترین Endpoint برای Server-Sent Events

زیبایی API جدید SSE در NET 10.، سادگی آن است.
می‌توانید از Results.ServerSentEvents استفاده کنید تا یک جریان از رویدادها را از هر <IAsyncEnumerable<T برگردانید.

از آنجایی که IAsyncEnumerable نمایانگر یک جریان داده است که در طول زمان می‌رسد، سرور متوجه می‌شود که باید اتصال HTTP را باز نگه دارد، به‌جای اینکه بعد از اولین «chunk» آن را ببندد 🔓

در اینجا یک مثال مینیمال از یک Endpoint برای SSE وجود دارد که ثبت سفارش‌ها را به‌صورت Real-time استریم می‌کند 📦📊
app.MapGet("orders/realtime", (
ChannelReader<OrderPlacement> channelReader,
CancellationToken cancellationToken) =>
{
// 1. ReadAllAsync یک IAsyncEnumerable برمی‌گرداند
// 2. Results.ServerSentEvents به مرورگر می‌گوید: «این اتصال را باز نگه دار»
// 3. به محض ورود داده‌ی جدید به Channel، داده به کلاینت Push می‌شود
return Results.ServerSentEvents(
channelReader.ReadAllAsync(cancellationToken),
eventType: "orders");
});


🔍 وقتی کلاینت این Endpoint را صدا می‌زند چه اتفاقی می‌افتد؟

🔹️سرور هدر Content-Type: text/event-stream را ارسال می‌کند 📬

🔸️اتصال باز می‌ماند و در حالت انتظار برای داده قرار می‌گیرد

🔹️به محض اینکه اپلیکیشن شما یک سفارش جدید داخل Channel قرار دهد:

🔸️ءIAsyncEnumerable آن آیتم را yield می‌کند

🔹️ءNET. بلافاصله آن را از طریق همان اتصال HTTP باز به مرورگر ارسال می‌کند ⚡️

این یک روش فوق‌العاده بهینه برای پیاده‌سازی اعلان‌های Push است، بدون سربار یک پروتکل stateful.

🧠 نکته‌ی پایانی

در این مثال، از Channel فقط به‌عنوان یک ابزار استفاده شده است.
در یک اپلیکیشن واقعی، ممکن است:

یک Background Service داشته باشید 🛠

به یک صف پیام مثل RabbitMQ یا Azure Service Bus گوش دهید 📮

یا به تغییرات دیتابیس واکنش نشان دهید 🗄

و سپس رویدادهای جدید را داخل Channel قرار دهید تا کلاینت‌های متصل آن‌ها را مصرف کنند.
مدیریت رویدادهای از‌دست‌رفته (Handling Missed Events) 🔄📡

ءEndpoint ساده‌ای که همین الان ساختیم عالی است، اما یک ضعف مهم دارد:
تاب‌آوری (Resilience) ندارد.

یکی از بزرگ‌ترین چالش‌ها در استریم‌های Real-time، قطع شدن اتصال است.
تا زمانی که مرورگر به‌صورت خودکار دوباره وصل شود، ممکن است چندین رویداد ارسال شده و از دست رفته باشند 😕

برای حل این مشکل، SSE یک مکانیزم داخلی دارد:
هدر Last-Event-ID.
وقتی مرورگر reconnect می‌شود، این ID را دوباره برای سرور ارسال می‌کند.

در NET 10. می‌توانیم از نوع <SseItem<T استفاده کنیم تا داده را به‌همراه متادیتاهایی مثل ID و retry interval بسته‌بندی کنیم.

با ترکیب یک OrderEventBuffer ساده‌ی درون‌حافظه‌ای و مقدار Last-Event-ID که مرورگر ارسال می‌کند، می‌توانیم رویدادهای از‌دست‌رفته را هنگام reconnect دوباره ارسال کنیم 🔁
app.MapGet("orders/realtime/with-replays", (
ChannelReader<OrderPlacement> channelReader,
OrderEventBuffer eventBuffer,
[FromHeader(Name = "Last-Event-ID")] string? lastEventId,
CancellationToken cancellationToken) =>
{
async IAsyncEnumerable<SseItem<OrderPlacement>> StreamEvents()
{
// 1. بازپخش رویدادهای از‌دست‌رفته از buffer
if (!string.IsNullOrWhiteSpace(lastEventId))
{
var missedEvents = eventBuffer.GetEventsAfter(lastEventId);
foreach (var missedEvent in missedEvents)
{
yield return missedEvent;
}
}

// 2. استریم رویدادهای جدید به‌محض ورود به Channel
await foreach (var order in channelReader.ReadAllAsync(cancellationToken))
{
var sseItem = eventBuffer.Add(order); // Buffer یک ID یکتا اختصاص می‌دهد
yield return sseItem;
}
}

return TypedResults.ServerSentEvents(StreamEvents(), "orders");
});


فیلتر کردن Server-Sent Events بر اساس کاربر 👤🔐

ءSSE روی HTTP استاندارد ساخته شده است.
چون یک درخواست GET معمولی است، زیرساخت فعلی شما بدون تغییر کار می‌کند:

ءSecurity 🔐: می‌توانید JWT را به‌صورت عادی در هدر Authorization ارسال کنید

ءUser Context 👤: می‌توانید به HttpContext.User دسترسی داشته باشید و استریم را بر اساس UserId فیلتر کنید
→ فقط داده‌هایی که متعلق به همان کاربر هستند ارسال می‌شوند

مثال یک Endpoint SSE که فقط سفارش‌های کاربر لاگین‌شده را استریم می‌کند:
app.MapGet("orders/realtime", (
ChannelReader<OrderPlacement> channelReader,
IUserContext userContext, // کانتکست تزریق‌شده شامل اطلاعات کاربر
CancellationToken cancellationToken) =>
{
// UserId از JWT توسط IUserContext استخراج می‌شود
var currentUserId = userContext.UserId;

async IAsyncEnumerable<OrderPlacement> GetUserOrders()
{
await foreach (var order in channelReader.ReadAllAsync(cancellationToken))
{
// فقط داده‌های متعلق به کاربر احراز هویت‌شده ارسال می‌شود
if (order.CustomerId == currentUserId)
{
yield return order;
}
}
}

return Results.ServerSentEvents(GetUserOrders(), "orders");
})
.RequireAuthorization(); // Authorization استاندارد ASP.NET Core


⚠️ نکته مهم:

وقتی یک پیام داخل Channel نوشته می‌شود، به تمام کلاینت‌های متصل broadcast می‌شود.
این رفتار برای استریم‌های per-user ایده‌آل نیست.
در محیط production، احتمالاً به راهکار قوی‌تری نیاز دارید.

مصرف Server-Sent Events در JavaScript 🌐🧠

در سمت کلاینت، نیازی به نصب حتی یک پکیج npm هم ندارید 🙌 API بومی مرورگر یعنی EventSource تمام کارهای سنگین را انجام می‌دهد،
از جمله reconnect خودکار و ارسال Last-Event-ID.
const eventSource = new EventSource('/orders/realtime/with-replays');

// گوش دادن به event type مشخص‌شده در C#
eventSource.addEventListener('orders', (event) => {
const payload = JSON.parse(event.data);
console.log(New Order ${event.lastEventId}:, payload.data);
});

// وقتی اتصال برقرار می‌شود
eventSource.onopen = () => {
console.log('Connection opened');
};

// پیام‌های عمومی (در صورت وجود)
eventSource.onmessage = (event) => {
console.log('Received message:', event);
};

// مدیریت خطا و reconnect
eventSource.onerror = () => {
if (eventSource.readyState === EventSource.CONNECTING) {
console.log('Reconnecting...');
}
};
جمع‌بندی 🧩
ءSSE در NET 10. یک نقطه‌ی تعادل عالی است برای به‌روزرسانی‌های ساده و یک‌طرفه مثل:
داشبوردها 📊
نوتیفیکیشن‌ها 🔔
ءProgress barها
سبک است، مبتنی بر HTTP است و به‌راحتی با middlewareهای امنیتی فعلی شما ایمن می‌شود.

با این حال، SignalR همچنان انتخاب قدرتمند و battle-tested برای ارتباط‌های دوطرفه‌ی پیچیده یا مقیاس بسیار بالا (با backplane) باقی می‌ماند.

هدف جایگزینی SignalR نیست
هدف این است که برای کارهای ساده، ابزار ساده‌تری داشته باشید 🛠

سبک‌ترین ابزاری را انتخاب کنید که مشکل شما را حل می‌کند.
امیدوارم مفید بوده باشد.😊