C# Geeks (.NET)
#مهندس_فکر_کن قسمت:4️⃣ 🎯مصاحبهکننده: ءCache Stampede چیست؟ فرض کن یک کلید در Cache داری: Product:123 و TTL آن 5 دقیقه است. در لحظهای که این کلید منقضی میشود، همزمان 5000 درخواست برای همان محصول میآید. اتفاقی که میافتد: Client 1 ----\ Client 2 -----\…
.NET Tips
Cache Stampede: وقتی کش به جای محافظت، به دشمن سیستم تبدیل میشود
کش (Cache) یکی از رایجترین ابزارهایی است که توسعهدهندگان داتنت برای بهبود کارایی سرویسها به کار میگیرند. ایده پشت آن سادهاست: نتیجه یک عملیات پرهزینه — مثل یک کوئری سنگین تجمیعی (Aggregation Query) یا فراخوانی یک سرویس بیرونی — را برای مدتی نگه میداریم…
⏰ چرا شرکتهای بزرگ یک Reminder Service جداگانه میسازند؟
اوایل پروژه همه چیز ساده به نظر میرسد.
کاربر یک Task ایجاد میکند و برای آن یک زمان یادآوری تعیین میکند.
در زمان مقرر هم همان سرویس اصلی یک Notification ارسال میکند و تمام.
اما وقتی تعداد کاربران به صدها هزار یا میلیونها نفر میرسد، این رویکرد دیگر پاسخگو نیست.
اگر میلیونها Reminder برای ساعت ۹ صبح برنامهریزی شده باشند چه اتفاقی میافتد؟
همینجاست که مفهوم Reminder Service وارد معماری سیستم میشود.
به جای اینکه هر سرویس خودش مسئول زمانبندی و ارسال Reminder باشد، یک سرویس مستقل فقط مسئول مدیریت زمان، زمانبندی و اجرای Reminderها خواهد بود.
🏗 معماری سنتی
User
│
▼
Task Service
│
├── Database
│
└── Send Reminder
هر سرویس خودش:
• زمان Reminder را ذخیره میکند.
• ءScheduler مخصوص خودش را اجرا میکند.
• ءNotification ارسال میکند.
🏗 معماری با Reminder Service
User
│
▼
Task Service
│
Save Reminder Request
│
▼
Reminder Service
│
┌───────────┴────────────┐
▼ ▼
Schedule Engine Delay Queue
│ │
└───────────┬────────────┘
▼
Notification Service
│
┌───────────┼────────────┐
▼ ▼ ▼
Email SMS Push
تمام سرویسها فقط درخواست ایجاد Reminder را ثبت میکنند.
اجرای Reminder کاملاً بر عهده Reminder Service خواهد بود.
✅ مزایا
1️⃣ جداسازی مسئولیتها (Separation of Concerns)
ءBusiness Service فقط منطق کسبوکار را مدیریت میکند.
زمانبندی و اجرای Reminderها کاملاً جدا میشود.
2️⃣ مدیریت میلیونها Reminder
ءReminder Service میتواند میلیونها Reminder را:
✅ زمانبندی کند.
✅ اولویتبندی کند.
✅ دستهبندی کند.
بدون اینکه سرویسهای اصلی تحت فشار قرار بگیرند.
3️⃣ ءRetry خودکار
اگر Notification ارسال نشود:
• ءRetry انجام میشود.
• ءExponential Backoff اعمال میشود.
• در صورت شکست نهایی وارد Dead Letter Queue میشود.
4️⃣ مقیاسپذیری مستقل
اگر تعداد Reminderها زیاد شود فقط Reminder Service Scale میشود.
نیازی نیست کل سیستم Scale شود.
5️⃣ پشتیبانی از انواع Reminder
یک سرویس میتواند انواع مختلف Reminder را مدیریت کند:
📧 Email Reminder
📱 SMS Reminder
🔔 Push Notification
📅 Calendar Reminder
💬 Slack Reminder
6️⃣ مدیریت زمان (Time Zone)
یکی از سختترین قسمتهای Reminder همین موضوع است.Reminder Service میتواند:
• Time Zone کاربران
• Daylight Saving
• Local Time
را به صورت متمرکز مدیریت کند.
7️⃣ قابلیت Delay Scheduling
مثلاً:
Send after 5 minutes
Send tomorrow at 9 AM
Send every Monday
Send after 30 days
Send every month
همه این موارد توسط Reminder Service مدیریت میشوند.
❌ معایب
1️⃣ پیچیدگی بیشتر
دیگر با یک Scheduler ساده طرف نیستید.
مباحثی مانند:
• Distributed Scheduling
• Delay Queue
• Cron Engine
• Retry
• Dead Letter Queue
و...
وارد سیستم میشوند.
2️⃣ Single Point of Failure
اگر Reminder Service از کار بیفتد،
تمام Reminderهای سیستم متوقف خواهند شد.
بنابراین باید High Availability داشته باشد.
3️⃣ مدیریت دقیق زمان
Clock Drift
Time Zone
Leap Year
Leap Second
Daylight Saving
همگی باید به درستی مدیریت شوند.
4️⃣ جلوگیری از ارسال تکراری
اگر سرویس هنگام ارسال Crash کند،
نباید Reminder دوباره برای کاربر ارسال شود.
بنابراین باید: Idempotency
و Delivery Tracking
وجود داشته باشد.
📋 نکاتی که حتماً باید رعایت شوند
🔸 استفاده از UTC برای ذخیره زمان
🔸 تبدیل Time Zone فقط هنگام نمایش
🔸 استفاده از Delay Queue
🔸 پشتیبانی از Retry
🔸 استفاده از Idempotency Key
🔸 ثبت کامل Audit Log
🔸 پشتیبانی از Cron Expression
🔸 مانیتورینگ Queueها
🔸 قابلیت Cancel کردن Reminder
🔸 قابلیت Reschedule
⭐️ برای بهتر شدن Reminder Service چه کارهایی انجام دهیم؟
✅ Quartz.NET یا Hangfire برای Job Scheduling
✅ RabbitMQ Delay Queue یا Kafka Delay Topic
✅ Outbox Pattern
✅ Distributed Lock
✅ High Availability
✅ Retry Policy
✅ Dead Letter Queue
✅ Monitoring و Alerting
✅ Metrics با OpenTelemetry
✅ Sharding برای Reminderهای حجیم
#مهندس_فکر_کن
قسمت:6️⃣
🎯 مصاحبهکننده
فرض کن در ASP.NET Core این کد را نوشتهای:
این متد روزانه ۱ میلیون بار فراخوانی میشود.
🔸️سؤال
این کد چه مشکلی دارد؟
چرا در Production توصیه نمیشود؟
راهحل استاندارد در ASP.NET Core چیست؟
چیزی که ازت انتظار دارم
اگر فقط بگویی:
کافی نیست.
باید توضیح بدهی:
مشکل دقیق چیست؟
چرا اتفاق میافتد؟
قسمت:6️⃣
🎯 مصاحبهکننده
فرض کن در ASP.NET Core این کد را نوشتهای:
public async Task<string> GetDataAsync()
{
using var client = new HttpClient();
return await client.GetStringAsync("https://api.example.com");
}
این متد روزانه ۱ میلیون بار فراخوانی میشود.
🔸️سؤال
این کد چه مشکلی دارد؟
چرا در Production توصیه نمیشود؟
راهحل استاندارد در ASP.NET Core چیست؟
چیزی که ازت انتظار دارم
اگر فقط بگویی:
"از IHttpClientFactory استفاده میکنیم."
کافی نیست.
باید توضیح بدهی:
مشکل دقیق چیست؟
چرا اتفاق میافتد؟
Forwarded from thisisnabi.dev [Farsi] (Nabi Karampour)
اگر قرار باشه دوباره درگیر ساختار های دست و پاگیر در فرایند توسعه نرم افزار بشیم باز رسیدن به خروجی زمانبر خواهد بود.
و این دوباره تمرکز ما رو از نیت اصلی دور میکنه.
چند روز پیش با یک پروداکت منیجر بصورت تمرینی روی یک PRD کار می کردم و با هم pair شده بودیم، یک داشبورد کامل رو با درنظر گرفتن اکثر جزئیات بیزینسی در 4.5 ساعت کار آماده کردیم.
فرایند ها بنظرم کمی به این سمت میره که بیزینس دیگه از شما نمیپزیره یک کار رو چند ماه طول بدین، باید خیلی سریع به فکر ارائه خروجی باشید.
برای همین بشخصه تمرکزم روی تغییر مدل کاری تیم و چیدن ساختاری برای رسیدن سریعتر به خروجی هست.
حتی یک موضوعی که این روزها فکرم رو درگیر کرده اینه که دیگه اینقدر راحت مثل قبل نیرو در اختیارمون قرار نمیدن،
دیدگاه رفته به سمتی که چرا یه بیزینس میلیارد دلاری توی سیلیکون ولی با 10 نفر پیش میره، شما هم برید به اون سمت، کارهای دست و پاگیرتون رو با AI پیش ببرید.
این تغییر نشونه بیکارشدن ما نیست ولی بیزینس چی براش کمتر هزینه داشته باشه میره به اون سمت، تا بوده همین بوده.
شما سعی کنید مسبب این کاهش هزینه باشید.
و این دوباره تمرکز ما رو از نیت اصلی دور میکنه.
چند روز پیش با یک پروداکت منیجر بصورت تمرینی روی یک PRD کار می کردم و با هم pair شده بودیم، یک داشبورد کامل رو با درنظر گرفتن اکثر جزئیات بیزینسی در 4.5 ساعت کار آماده کردیم.
فرایند ها بنظرم کمی به این سمت میره که بیزینس دیگه از شما نمیپزیره یک کار رو چند ماه طول بدین، باید خیلی سریع به فکر ارائه خروجی باشید.
برای همین بشخصه تمرکزم روی تغییر مدل کاری تیم و چیدن ساختاری برای رسیدن سریعتر به خروجی هست.
حتی یک موضوعی که این روزها فکرم رو درگیر کرده اینه که دیگه اینقدر راحت مثل قبل نیرو در اختیارمون قرار نمیدن،
دیدگاه رفته به سمتی که چرا یه بیزینس میلیارد دلاری توی سیلیکون ولی با 10 نفر پیش میره، شما هم برید به اون سمت، کارهای دست و پاگیرتون رو با AI پیش ببرید.
این تغییر نشونه بیکارشدن ما نیست ولی بیزینس چی براش کمتر هزینه داشته باشه میره به اون سمت، تا بوده همین بوده.
شما سعی کنید مسبب این کاهش هزینه باشید.
(#باور_غلط_یا_واقعیت؟)
«هرچه تعداد Design Patternهایی که در پروژه استفاده کنیم بیشتر باشد، کیفیت طراحی هم بالاتر است.»
ءDesign Patternها قرار نیست کیفیت طراحی را ایجاد کنند.آنها قرار است یک مسئله تکرارشونده را با یک راهحل شناختهشده حل کنند.
اگر مسئله وجود نداشته باشد، Pattern هم ارزشی ایجاد نمیکند.
بارها دیدهام پروژههایی که پر از Factory، Strategy، Decorator، Mediator و... هستند.
اما تغییر دادن یک قابلیت ساده در آنها، ساعتها زمان میبرد.نه به خاطر پیچیدگی مسئله...
بلکه به خاطر پیچیدگیای که خودمان ساختهایم.
از طرف دیگر، پروژههایی را هم دیدهام که شاید حتی یک Pattern را نتوان روی آنها نامگذاری کرد.
اما خوانا بودند.
تستپذیر بودند.
و تغییر دادنشان ساده بود.
نکته مهم اینجاست: Design Pattern، هدف نیست. فقط یک ابزار است.
همانطور که چکش برای هر چیزی ساخته نشده، Design Pattern هم برای هر کدی مناسب نیست.
مهندسان باتجربه معمولاً هنگام طراحی از خودشان نمیپرسند:
«کدام Pattern را اینجا استفاده کنم؟»
آنها میپرسند: «اینجا چه مشکلی دارم؟»
اگر Pattern آن مشکل را حل کند، از آن استفاده میکنند.
اگر نه، سادهترین راهحل را انتخاب میکنند.
کدی که هیچ Pattern مشهوری ندارد اما بهراحتی قابل فهم، تغییر و نگهداری است،
از کدی که فقط برای نمایش دانش الگوهای طراحی نوشته شده،ارزش مهندسی بیشتری دارد.چون در مهندسی نرمافزار،سادگی یک مزیت است؛ نه نشانه کمتجربگی.
❌ باور غلط
«هرچه تعداد Design Patternهایی که در پروژه استفاده کنیم بیشتر باشد، کیفیت طراحی هم بالاتر است.»
✅ واقعیت
ءDesign Patternها قرار نیست کیفیت طراحی را ایجاد کنند.آنها قرار است یک مسئله تکرارشونده را با یک راهحل شناختهشده حل کنند.
اگر مسئله وجود نداشته باشد، Pattern هم ارزشی ایجاد نمیکند.
بارها دیدهام پروژههایی که پر از Factory، Strategy، Decorator، Mediator و... هستند.
اما تغییر دادن یک قابلیت ساده در آنها، ساعتها زمان میبرد.نه به خاطر پیچیدگی مسئله...
بلکه به خاطر پیچیدگیای که خودمان ساختهایم.
از طرف دیگر، پروژههایی را هم دیدهام که شاید حتی یک Pattern را نتوان روی آنها نامگذاری کرد.
اما خوانا بودند.
تستپذیر بودند.
و تغییر دادنشان ساده بود.
نکته مهم اینجاست: Design Pattern، هدف نیست. فقط یک ابزار است.
همانطور که چکش برای هر چیزی ساخته نشده، Design Pattern هم برای هر کدی مناسب نیست.
مهندسان باتجربه معمولاً هنگام طراحی از خودشان نمیپرسند:
«کدام Pattern را اینجا استفاده کنم؟»
آنها میپرسند: «اینجا چه مشکلی دارم؟»
اگر Pattern آن مشکل را حل کند، از آن استفاده میکنند.
اگر نه، سادهترین راهحل را انتخاب میکنند.
💡 جمعبندی
کدی که هیچ Pattern مشهوری ندارد اما بهراحتی قابل فهم، تغییر و نگهداری است،
از کدی که فقط برای نمایش دانش الگوهای طراحی نوشته شده،ارزش مهندسی بیشتری دارد.چون در مهندسی نرمافزار،سادگی یک مزیت است؛ نه نشانه کمتجربگی.
🧅 معماری Onion در برابر 🧼 معماری Clean؛ تفاوت در چیست؟
هر دو معماری حول یک ایدهی مشترک شکل گرفتهاند:
• جهت وابستگیها به سمت مرکز است.
• هستهی سیستم شامل موجودیتهای دامنه (Domain Entities) و قوانین کسبوکار (Business Rules) است.
• هستهی سیستم باید از هرگونه وابستگی به مسائل خارجی مستقل باشد.
ایدهی اصلی در اینجا کنترل Coupling (وابستگی) است.
موجودیتهای دامنه و قوانین کسبوکار در مرکز سیستم قرار دارند. آنها به هیچ چیز وابسته نیستند و این موضوع ارزشمند است، زیرا باعث میشود پایدار باقی بمانند. اجزای اطراف Domain ممکن است تغییر کنند، اما این تغییرات نباید روی موجودیتهای دامنه یا قوانین کسبوکار تأثیر بگذارند.
به خاطر داشته باشید که Coupling به میزان وابستگی متقابل بین ماژولها و کامپوننتهای یک سیستم گفته میشود.
هر دو معماری Clean و Onion تلاش میکنند به اهداف یکسانی برسند، اما با رویکردی کمی متفاوت.
در هر دو، لایهی Domain وجود دارد.
در Clean Architecture، هسته شامل Use Caseها است، در حالی که در Onion Architecture، از Domain Services و Application Services استفاده میشود.
در بیرونیترین لایه نیز دغدغههای مربوط به Infrastructure قرار میگیرند.
هر تفاوتی که میان این دو مشاهده میکنید، زمانی که نوبت به پیادهسازی آنها در یک پروژه واقعی میرسد، به سرعت کمرنگ میشود؛ که این موضوع کاملاً طبیعی است، زیرا هر دو در حال پیادهسازی مجموعهای از اصول یکسان هستند.
عامل مهم دیگری که باید به آن توجه کنید Cohesion (انسجام) است. Cohesion به میزان ارتباط و هماهنگی اجزای داخلی یک ماژول (کلاس، تابع، کامپوننت و...) برای انجام یک هدف مشخص و واحد گفته میشود.
در واقع، همین مفهوم مهمترین نیروی محرک Vertical Slice Architecture محسوب میشود.
#فرهنگ_مهندسی (Engineering Culture)
یکی از نشانههای یک تیم مهندسی بالغ، این نیست که کمتر خطا میکند.
این است که از خطاها چیزی پنهان نمیکند.
در بعضی تیمها، وقتی Incident رخ میدهد، اولین سؤال این است:
«کارِ چه کسی بود؟»
اما در تیمهای مهندسی بالغ، سؤال فرق میکند:
«چطور ممکن است دوباره این اتفاق نیفتد؟»
این تفاوت، کوچک به نظر میرسد.
اما فرهنگ تیم را از ریشه تغییر میدهد.
وقتی افراد از سرزنش شدن بترسند،
مشکلات را دیرتر گزارش میکنند.
ریسکها را پنهان میکنند.
و گاهی ترجیح میدهند باگ را موقتاً دور بزنند تا اینکه درباره آن صحبت کنند.
اما وقتی بدانند هدف، پیدا کردن مقصر نیست،
بلکه پیدا کردن علت است،
اطلاعات سریعتر به اشتراک گذاشته میشود.
و تیم، با هر Incident قویتر از قبل میشود.
به همین دلیل است که Blameless Postmortem فقط یک جلسه بعد از Incident نیست.
یک طرز فکر است.
طرز فکری که میگوید:
«انسانها اشتباه میکنند؛ وظیفه سیستم این است که اجازه ندهد یک اشتباه کوچک، به یک فاجعه بزرگ تبدیل شود.»
فرهنگ مهندسی از جایی شروع میشود که اعضای تیم،
برای گفتن حقیقت، احساس امنیت کنند.
چون تیمی که از اشتباهاتش یاد میگیرد،
خیلی سریعتر از تیمی رشد میکند که فقط سعی میکند اشتباهاتش را پنهان کند.
یکی از نشانههای یک تیم مهندسی بالغ، این نیست که کمتر خطا میکند.
این است که از خطاها چیزی پنهان نمیکند.
در بعضی تیمها، وقتی Incident رخ میدهد، اولین سؤال این است:
«کارِ چه کسی بود؟»
اما در تیمهای مهندسی بالغ، سؤال فرق میکند:
«چطور ممکن است دوباره این اتفاق نیفتد؟»
این تفاوت، کوچک به نظر میرسد.
اما فرهنگ تیم را از ریشه تغییر میدهد.
وقتی افراد از سرزنش شدن بترسند،
مشکلات را دیرتر گزارش میکنند.
ریسکها را پنهان میکنند.
و گاهی ترجیح میدهند باگ را موقتاً دور بزنند تا اینکه درباره آن صحبت کنند.
اما وقتی بدانند هدف، پیدا کردن مقصر نیست،
بلکه پیدا کردن علت است،
اطلاعات سریعتر به اشتراک گذاشته میشود.
و تیم، با هر Incident قویتر از قبل میشود.
به همین دلیل است که Blameless Postmortem فقط یک جلسه بعد از Incident نیست.
یک طرز فکر است.
طرز فکری که میگوید:
«انسانها اشتباه میکنند؛ وظیفه سیستم این است که اجازه ندهد یک اشتباه کوچک، به یک فاجعه بزرگ تبدیل شود.»
فرهنگ مهندسی از جایی شروع میشود که اعضای تیم،
برای گفتن حقیقت، احساس امنیت کنند.
چون تیمی که از اشتباهاتش یاد میگیرد،
خیلی سریعتر از تیمی رشد میکند که فقط سعی میکند اشتباهاتش را پنهان کند.
🚨 قابلیت جدید field در C# 14 همیشه هم بیخطر نیست!یکی از جذابترین قابلیتهای C# 14 معرفی کلیدواژه
field است.به کمک آن دیگر لازم نیست برای هر Property یک Backing Field جداگانه تعریف کنید.
قبلاً مینوشتیم:
private decimal _price;
public decimal Price
{
get => _price;
set => _price = value;
}
حالا میتوانیم بنویسیم:
public decimal Price
{
get;
set => field = value;
}
کد تمیزتر، خواناتر و Boilerplate کمتر...
اما یک نکته مهم وجود دارد که خیلیها به آن توجه نمیکنند. 👇
⚠️ مشکل از کجا شروع میشود؟
وقتی از
field استفاده میکنید، دیگر فیلدی به نام _price وجود ندارد.کامپایلر خودش یک Backing Field مخفی ایجاد میکند.
چیزی شبیه:
<Price>k__BackingField
این یعنی اگر جایی از پروژه مستقیماً به
_price وابسته باشد...ممکن است برنامه بدون هیچ خطای کامپایل، در Runtime دچار مشکل شود.
🔥 یکی از رایجترین قربانیها: EF Core
فرض کنید قبلاً این Mapping را نوشتهاید:
builder.Property(x => x.Quantity)
.HasField("_quantity");
بعد تصمیم میگیرید از قابلیت جدید C# 14 استفاده کنید.
public int Quantity
{
get;
set => field = value;
}
نتیجه؟
هنگام اجرای برنامه با خطای:
InvalidOperationException
روبهرو خواهید شد.
چون دیگر فیلدی به نام
_quantity وجود ندارد.⚠️ ءReflection هم ممکن است از کار بیفتد
کدهایی مانند:
GetField("_quantity")یا
GetRuntimeField(...)
بعد از Refactor مقدار
null برمیگردانند.و معمولاً چند خط بعد...
به یک
NullReferenceException ختم میشوند.✅ قبل از استفاده از field این موارد را بررسی کنید🔹 تمام
HasField(...)های پروژه🔹 ءAttributeهای
BackingField🔹 استفاده از
Reflection🔹 ءMapperهای سفارشی
🔹 ءSerializerهایی که روی Fieldها کار میکنند
🔹 ءUnit Testهایی که به نام Field وابستهاند
💡 نکته مهم
استفاده از
field فقط یک Refactor ظاهری نیست.اگر زیرساخت پروژه به نام Backing Field وابسته باشد، در واقع دارید Contract داخلی برنامه را تغییر میدهید.
و این دسته از خطاها معمولاً نه توسط کامپایلر شناسایی میشوند و نه توسط بسیاری از Unit Testها؛ بلکه اولین بار در محیط Production خودشان را نشان میدهند.
🎯 جمعبندی
قابلیت
field در C# 14 یکی از بهترین امکانات جدید زبان است و باعث کاهش Boilerplate و خواناتر شدن کد میشود.اما قبل از مهاجرت، مطمئن شوید پروژه شما به نام Backing Fieldها وابسته نیست؛ در غیر این صورت، یک Refactor ساده میتواند به خطاهای Runtime، شکست Mappingهای EF Core و مشکلات Reflection منجر شود.
🔗Source
🔖 هشتگها:
#dotnet #csharp14 #EFCore #Reflection #Runtime #DotNetTips
🖼 چرا شرکتهای بزرگ یک Media Service جداگانه میسازند؟
اوایل پروژه همه چیز ساده است.
کاربر یک تصویر آپلود میکند، فایل داخل wwwroot ذخیره میشود و مسیر آن داخل دیتابیس قرار میگیرد.
اما وقتی سیستم بزرگتر میشود، ناگهان هر سرویس شروع میکند به مدیریت فایلها، آپلود تصاویر، Resize کردن، ساخت Thumbnail، حذف فایلها، CDN، Storage و دهها مسئولیت دیگر.
همینجاست که مفهوم Media Service وارد میشود.
به جای اینکه هر سرویس خودش مسئول مدیریت فایلها باشد، یک سرویس مرکزی فقط روی مدیریت Media تمرکز میکند.
🏗 معماری با Media Service
Upload File
↓
Media Service
↓
Object Storage (S3, MinIO)
↓
CDN / Image Cache
┌─────────┼─────────┐
↓ ↓ ↓
Service A Service B Service C
تمام سرویسها فقط از Media Service درخواست Upload یا Download میکنند و دیگر درگیر جزئیات ذخیرهسازی فایل نیستند.
✅ مزایا
1️⃣ جداسازی مسئولیتها (Single Responsibility)
ءBusiness Serviceها فقط روی منطق کسبوکار تمرکز میکنند. Media Service فقط فایل مدیریت میکند.
2️⃣ یکپارچگی Storage
تمام فایلها در یک محل ذخیره میشوند.
فرقی ندارد از:
• Local Storage
• MinIO
• Amazon S3
• Azure Blob Storage
• Google Cloud Storage
استفاده کنید.
تمام سرویسها فقط با Media Service صحبت میکنند.
3️⃣ حذف کدهای تکراری
دیگر لازم نیست هر سرویس جداگانه پیادهسازی کند:
• Upload Endpoint
• Delete File
• Resize Image
• Compress Image
• Generate Thumbnail
• Virus Scan
4️⃣ توسعهپذیری بیشتر
اگر امروز از Local Storage استفاده میکنید و فردا بخواهید به S3 مهاجرت کنید،
فقط Media Service تغییر میکند.
هیچ Business Serviceای نیاز به تغییر ندارد.
5️⃣ مدیریت سادهتر فایلها
تمام عملیات مربوط به فایل در یک نقطه انجام میشود:
• Versioning
• Metadata
• Tags
• File Ownership
• Expiration
• Access Policy
6️⃣ ءPerformance بهتر
ءMedia Service میتواند:
• Image Resize
• Compression
• Lazy Loading
• CDN Integration
• Cache
را به صورت متمرکز مدیریت کند.
❌ معایب
1️⃣ Single Point Of Failure
اگر Media Service از دسترس خارج شود،
آپلود و دانلود فایلها نیز متوقف میشود.
2️⃣ پیچیدگی بیشتر
دیگر فقط ذخیره فایل مطرح نیست.
موضوعاتی مانند:
• CDN
• Object Storage
• Streaming
• Chunk Upload
• Signed URL
• Image Processing
نیز وارد سیستم میشوند.
3️⃣ هزینه بیشتر
نگهداری Storage، CDN و Backup هزینه دارد.
4️⃣ مدیریت امنیت
دسترسی مستقیم کاربران به فایلها باید کنترل شود.خصوصاً برای فایلهای Private.
📋 نکاتی که حتماً باید رعایت شوند
🔸 فایلها داخل Database ذخیره نشوند (بهجز موارد خاص)
🔸 از Object Storage استفاده شود.
🔸 فایلهای Private با Signed URL ارائه شوند.
🔸 محدودیت حجم فایل اعمال شود.
🔸 ءMIME Type اعتبارسنجی شود.
🔸 نام فایلها به GUID یا Hash تبدیل شوند.
🔸 ءThumbnailها به صورت Async تولید شوند.
🔸 فایلهای بلااستفاده پاکسازی شوند.
🔸 از CDN برای فایلهای عمومی استفاده شود.
🔸 تمام عملیات Upload و Delete لاگ شوند.
⭐️ برای بهتر شدن Media Service چه کارهایی انجام دهیم؟
✅ Chunk Upload برای فایلهای بزرگ
✅ Resume Upload
✅ Background Image Processing
✅ Automatic Compression
✅ Automatic Thumbnail Generation
✅ Video Transcoding
✅ Signed URL
✅ CDN Integration
✅ Metadata Search
✅ Duplicate Detection (Hash)
✅ Virus Scanning
✅ Versioning
✅ Lifecycle Management
✅ Backup & Replication
💡 مهمترین نکته
ءMedia Service فقط یک سرویس Upload فایل نیست.
در سیستمهای بزرگ، این سرویس مسئول مدیریت کل چرخهی عمر فایلها است؛ از لحظهی آپلود تا حذف، آرشیو، Cache، امنیت، پردازش تصویر و توزیع از طریق CDN.
هرچه زودتر مدیریت فایلها را از Business Serviceها جدا کنید، توسعه سیستم سادهتر، مقیاسپذیری بیشتر و نگهداری زیرساخت بسیار آسانتر خواهد شد.
🔖 هشتگها:
#mediaservice #objectstorage #minio #amazons3
#فرمون_دادن
بعضی وقتها فکر میکنیم ارزش یک مهندس را تعداد تصمیمهایی که میگیرد مشخص میکند.
اما واقعیت این است که...
خیلی از مهندسان باتجربه، بیشتر از اینکه تصمیم بگیرند،
تصمیم نگرفتن را بلدند.
آنها هر ایدهای را تبدیل به Feature نمیکنند.
هر تکنولوژی جدیدی را وارد پروژه نمیکنند.
هر مشکلی را هم فوراً به Refactoring ربط نمیدهند.
چون یک چیز را خوب میدانند.
هر تصمیمی که امروز میگیری،
از فردا به بعد باید هزینه نگهداریاش را هم پرداخت کنی.
هر کلاس جدید...
هر سرویس جدید...
هر Dependency جدید...
هر Feature جدید...
تا روزی که پروژه زنده است، از تو هزینه میگیرد.
به همین دلیل، بلوغ مهندسی فقط در این نیست که بدانی چه چیزی را اضافه کنی.
بلوغ واقعی این است که بدانی به چه چیزی باید «نه» بگویی.
چون در نهایت،
پروژهها بیشتر از اینکه زیر بار کمبود قابلیتها خم شوند،
زیر بار تصمیمهایی خم میشوند که هیچوقت لازم نبود گرفته شوند.
بعضی وقتها فکر میکنیم ارزش یک مهندس را تعداد تصمیمهایی که میگیرد مشخص میکند.
اما واقعیت این است که...
خیلی از مهندسان باتجربه، بیشتر از اینکه تصمیم بگیرند،
تصمیم نگرفتن را بلدند.
آنها هر ایدهای را تبدیل به Feature نمیکنند.
هر تکنولوژی جدیدی را وارد پروژه نمیکنند.
هر مشکلی را هم فوراً به Refactoring ربط نمیدهند.
چون یک چیز را خوب میدانند.
هر تصمیمی که امروز میگیری،
از فردا به بعد باید هزینه نگهداریاش را هم پرداخت کنی.
هر کلاس جدید...
هر سرویس جدید...
هر Dependency جدید...
هر Feature جدید...
تا روزی که پروژه زنده است، از تو هزینه میگیرد.
به همین دلیل، بلوغ مهندسی فقط در این نیست که بدانی چه چیزی را اضافه کنی.
بلوغ واقعی این است که بدانی به چه چیزی باید «نه» بگویی.
چون در نهایت،
پروژهها بیشتر از اینکه زیر بار کمبود قابلیتها خم شوند،
زیر بار تصمیمهایی خم میشوند که هیچوقت لازم نبود گرفته شوند.
🔍 وقتی یک تراکنش وسط مسیر Fail میشود، واقعاً از کجا شروع به Debug میکنید؟فرض کنید یک کاربر روی دکمه پرداخت کلیک میکند. چند ثانیه بعد...
❌ پرداخت ناموفق میشود.
حالا سؤال اینجاست:
مشکل دقیقاً کجا رخ داده است؟
آیا API Gateway درخواست را دریافت نکرده؟
آیا Validation در Command Handler شکست خورده؟
آیا داده داخل Database ذخیره نشده؟
آیا Outbox پردازش نشده؟
آیا پیام وارد RabbitMQ نشده؟
یا Consumer پیام را دریافت کرده ولی پردازش آن Fail شده؟
اگر هر سرویس فقط لاگ خودش را داشته باشد...
عملاً وارد یک کابوس خواهید شد.
😵 سناریوی رایج
فرض کنید سیستم شما از این بخشها تشکیل شده است:
Client
│
▼
API Gateway (Ocelot)
│
▼
Payment Service
│
▼
Command Handler
│
▼
Database + Outbox
│
▼
Background Job
│
▼
RabbitMQ
│
▼
Notification Service
حالا برای پیدا کردن علت خطا باید:
🔹 لاگ Gateway را باز کنید.
🔹 بعد لاگ Payment Service را بررسی کنید.
🔹 بعد لاگ Background Service را.
🔹 بعد لاگ RabbitMQ Consumer را.
🔹 بعد لاگ Notification Service را.
هر کدام روی یک Instance جدا... با Timestampهای مختلف... و فقط امیدوار باشید که ساعت همه سرورها Sync باشد.
📌راهحل؟🎯 تجربه واقعی در Hub پرداخت Dr. Link
ءCentralized Logging + Correlation ID + OpenTelemetry
در هاب پرداخت Dr. Link تعداد زیادی سرویس مستقل وجود دارد.
پرداخت فقط یک درخواست HTTP نیست.
بعد از ثبت درخواست، چندین اتفاق پشت سر هم رخ میدهد:
✅ ذخیره اطلاعات
✅ اجرای Business Ruleها
✅ ثبت Domain Event
✅ ذخیره در Outbox
✅ ءPublish شدن پیام توسط MassTransit
✅ پردازش توسط سرویسهای دیگر
اگر Correlation نداشته باشیم...
ردیابی مسیر تقریباً غیرممکن میشود.
🆔 همه چیز از Correlation ID شروع میشود
به محض اینکه درخواست وارد Ocelot API Gateway میشود...
یک Correlation ID تولید میشود.
مثلاً:
a17d59d8-78d2-4b1f-bd89-40d3f52b44f3
این شناسه همراه درخواست به تمام سرویسها ارسال میشود.
X-Correlation-Id:
a17d59d8-78d2-4b1f-bd89-40d3f52b44f3
از این لحظه...
تمام لاگهای مربوط به این درخواست همین شناسه را خواهند داشت.
🌐 اما داستان فقط HTTP نیست...
قسمت سخت ماجرا زمانی شروع میشود که سیستم وارد دنیای Asynchronous Messaging میشود.
در همین نقطه رد درخواست گم میشود.
📨 ءCorrelation ID داخل پیام هم قرار میگیرد
هنگام Publish کردن پیام توسط MassTransit همان Correlation ID داخل Envelope پیام ذخیره میشود.
یعنی پیام چیزی شبیه این خواهد داشت:
CorrelationId
a17d59d8-78d2-4b1f-bd89-40d3f52b44f3
در نتیجه...
وقتی Consumer پیام را دریافت میکند...
همان شناسه هنوز همراه پیام است.
هیچ بخشی از مسیر گم نمیشود.
📝 ءSerilog هم به صورت خودکار این شناسه را وارد Logها میکند
به کمک یک Serilog Enricher
ءCorrelation ID مستقیماً از:
✅ HTTP Header
یا
✅ MassTransit Message
خوانده میشود.
در نتیجه دیگر لازم نیست بنویسیم:
Log.Information(
"Payment {CorrelationId}",
correlationId);
تمام Logها به صورت خودکار این مقدار را خواهند داشت.
⚙️ نقش MediatR Pipeline Behavior
قبلاً برای Cross-Cutting Concernها از Pipeline Behavior استفاده کرده بودیم.
حالا همان Pipeline یک مسئولیت جدید هم دارد.
در ابتدای اجرای هر Command:
یک Logging Scope باز میکند.
BeginScope()
CorrelationId
از این لحظه...
هر لاگی که داخل:
Repository
Domain Service
Application Service
EF Core
یا هر بخش دیگری ثبت شود...
به صورت خودکار همین Correlation ID را خواهد داشت.
بدون اینکه حتی یک بار آن را دستی پاس بدهیم.
📊 نتیجه نهایی
وقتی یک تراکنش Fail میشود...
دیگر لازم نیست:
❌ وارد لاگ Gateway شوید.
❌ بعد Payment Service.
❌ بعد Background Job.
❌ بعد RabbitMQ.
❌ بعد Notification Service.
فقط کافی است در Seq این شناسه را جستجو کنید:
a17d59d8-78d2-4b1f-bd89-40d3f52b44f3
و کل مسیر درخواست را ببینید.
از اولین HTTP Request...
تا آخرین Consumer...
کاملاً مرتب و به ترتیب زمانی.
🔭 اگر بخواهیم یک قدم جلوتر برویم...
ءCorrelation ID فقط برای لاگها نیست.
اگر از OpenTelemetry استفاده کنید...
همین شناسه تبدیل به یک Distributed Trace میشود.
در آن صورت میتوانید ببینید:
⏱️ هر سرویس چقدر زمان صرف کرده است.
📡 کدام درخواست به کدام سرویس ارسال شده است.
🐢 گلوگاه سیستم دقیقاً کجاست.
💥 کدام Span با خطا مواجه شده است.
یعنی علاوه بر مشاهده لاگها، کل سفر یک درخواست را بهصورت تصویری دنبال میکنید.
💡 مهمترین نکته
در سیستمهای Monolith معمولاً Debug کردن فقط با یک فایل Log انجام میشود.
اما در معماریهای Microservices یا حتی Modular Monolith که از Message Broker، Outbox Pattern و Background Processing استفاده میکنند، بدون Correlation ID عملاً هر سرویس جزیرهای جداگانه است.
ءCentralized Logging، Correlation ID و OpenTelemetry فقط ابزارهای لاگگیری نیستند؛ آنها ستون فقرات Observability سیستم هستند. هرچه سیستم بزرگتر و توزیعشدهتر شود، ارزش این سه ابزار بیشتر نمایان میشود، چون در زمان بروز Incident، تفاوت بین چند دقیقه و چند ساعت عیبیابی را رقم میزنند.
🔖 هشتگها:
#aspnetcore #microservices #serilog #opentelemetry #observability #logging
#روایت_تجربه (Storytelling)
چند وقت پیش، یکی از مهندسهای ارشد در یک کنفرانس جملهای گفت که توجهم را جلب کرد.
گفت:
«هر وقت وارد یک کدبیس جدید میشوم، قبل از اینکه اولین خط کد را تغییر دهم، سعی میکنم بفهمم تیم از تغییر دادن کدام قسمت میترسد.»
ابتدا فکر کردم منظورش کیفیت کد است.
اما ادامه داد:
«در همه پروژهها یک یا چند فایل وجود دارد که وقتی اسمشان میآید، همه میگویند:
لطفاً تا جای ممکن به آن دست نزن...»
نه چون آن فایل مهمترین بخش سیستم است.
بلکه چون هیچکس مطمئن نیست بعد از تغییرش چه اتفاقی میافتد.
او میگفت:
«همان فایل، معمولاً بیشترین بدهی فنی پروژه را دارد؛ نه لزوماً بزرگترین فایل.»
این حرف برایم جالب بود.
چون ما معمولاً بدهی فنی را با تعداد خطوط کد، پیچیدگی یا قدمت پروژه اندازه میگیریم.
اما شاید معیار بهتری وجود داشته باشد.
ترس تیم از تغییر.
اگر تیم از تغییر دادن بخشی از سیستم واهمه داشته باشد،
احتمالاً آن بخش، مدتهاست که از کنترل خارج شده است.
از آن روز، هر وقت درباره سلامت یک کدبیس صحبت میشود، کمتر به تعداد کلاسها یا متدها فکر میکنم.
بیشتر به این فکر میکنم که:
«اگر فردا یک تغییر کوچک در این قسمت لازم باشد، آیا تیم با اطمینان آن را انجام میدهد؟»
چون در مهندسی نرمافزار،
گاهی خطرناکترین بخش سیستم،
بخشی نیست که بیشترین باگ را دارد.
بخشی است که همه از تغییر دادنش میترسند.
چند وقت پیش، یکی از مهندسهای ارشد در یک کنفرانس جملهای گفت که توجهم را جلب کرد.
گفت:
«هر وقت وارد یک کدبیس جدید میشوم، قبل از اینکه اولین خط کد را تغییر دهم، سعی میکنم بفهمم تیم از تغییر دادن کدام قسمت میترسد.»
ابتدا فکر کردم منظورش کیفیت کد است.
اما ادامه داد:
«در همه پروژهها یک یا چند فایل وجود دارد که وقتی اسمشان میآید، همه میگویند:
لطفاً تا جای ممکن به آن دست نزن...»
نه چون آن فایل مهمترین بخش سیستم است.
بلکه چون هیچکس مطمئن نیست بعد از تغییرش چه اتفاقی میافتد.
او میگفت:
«همان فایل، معمولاً بیشترین بدهی فنی پروژه را دارد؛ نه لزوماً بزرگترین فایل.»
این حرف برایم جالب بود.
چون ما معمولاً بدهی فنی را با تعداد خطوط کد، پیچیدگی یا قدمت پروژه اندازه میگیریم.
اما شاید معیار بهتری وجود داشته باشد.
ترس تیم از تغییر.
اگر تیم از تغییر دادن بخشی از سیستم واهمه داشته باشد،
احتمالاً آن بخش، مدتهاست که از کنترل خارج شده است.
از آن روز، هر وقت درباره سلامت یک کدبیس صحبت میشود، کمتر به تعداد کلاسها یا متدها فکر میکنم.
بیشتر به این فکر میکنم که:
«اگر فردا یک تغییر کوچک در این قسمت لازم باشد، آیا تیم با اطمینان آن را انجام میدهد؟»
چون در مهندسی نرمافزار،
گاهی خطرناکترین بخش سیستم،
بخشی نیست که بیشترین باگ را دارد.
بخشی است که همه از تغییر دادنش میترسند.
این سؤال را چند وقت پیش دیدم و به نظرم سؤال جالبی بود.
سؤال این بود:
برداشت اولیه این سؤال این است که انگار اگر در یک حوزه خاص کار کنید، خودبهخود درآمد بالایی خواهید داشت.
اما واقعیت کمی متفاوت است.
شرکتهای مختلف، کشورهای مختلف و حتی دو استارتاپ در یک صنعت، مدلهای پرداخت کاملاً متفاوتی دارند.
یک استارتاپ تازهتأسیس ممکن است با بودجه محدود حقوق پرداخت کند، در حالی که یک شرکت بزرگ فناوری برای همان نقش چند برابر بیشتر هزینه کند.
بنابراین ارتباط مستقیم بین نوع مسئله و میزان حقوق همیشه وجود ندارد.
در واقع این رابطه، یک رابطه علت و معلولی ساده نیست.
بر اساس تجربهای که در شرکتهای مختلف دیدهام، مهندسهایی که بیشترین ارزش را ایجاد میکنند معمولاً در یکی از این سه دسته قرار میگیرند:
🚀 1️⃣ حل مسائل با ارزش تجاری بالا در حوزههای تخصصی
آنها روی مسائلی کار میکنند که شاید افراد کمی توانایی حلشان را داشته باشند، اما تأثیر بسیار بزرگی روی کسبوکار دارند.
مثلاً:
بهینهسازی یک سیستم پرداخت که روزانه میلیونها تراکنش پردازش میکند.
طراحی زیرساختی که هزینه سرورها را ۳۰٪ کاهش میدهد.
افزایش Availability یک سرویس حیاتی.
🤝 2️⃣ تبدیل شدن به یک «Multiplier»
این افراد فقط خودشان سریع کدنویسی نمیکنند.
آنها باعث میشوند کل تیم سریعتر و بهتر کار کند.
مثلاً:
طراحی معماری مناسب
ایجاد ابزارهای داخلی
ءMentor کردن اعضای تیم
بهبود فرآیندهای توسعه
حذف گلوگاههای فنی
گاهی ارزش واقعی یک مهندس، در تعداد خط کدی که مینویسد نیست؛ بلکه در تعداد افرادی است که بهرهوری آنها را افزایش میدهد.
📈 3️⃣ ایجاد ارزش پایدار در بخشهای مختلف سیستم
برخی مهندسها فقط روی یک Feature کار نمیکنند.
آنها به صورت مداوم روی بخشهای مختلف سیستم اثر مثبت میگذارند.
برای مثال:
بهبود Performance
افزایش Security
کاهش هزینههای زیرساخت
افزایش Reliability
سادهتر کردن فرآیند توسعه
💡 اگر بخواهیم خیلی ساده بیان کنیم...
میتوان گفت:
هرچه خروجی شما ارزشمندتر باشد و افراد یا بخشهای بیشتری از آن بهره ببرند، احتمال دریافت مسئولیتها و جبران خدمات بالاتر نیز بیشتر خواهد بود.
نه همیشه.
داشتن تخصص عمیق در یک حوزه خاص میتواند بسیار ارزشمند باشد.
اما اگر تمام استراتژی شغلی خود را فقط بر اساس یک مهارت بسیار محدود و خاص بنا کنید، ریسک بالایی دارد.
ممکن است آن مهارت در بازار امروز بسیار پرتقاضا باشد، اما چند سال بعد شرایط کاملاً تغییر کند.
🎯 پس روی چه چیزی سرمایهگذاری کنیم؟
اگر هدف رشد حرفهای است، بهتر است روی مهارتهایی تمرکز کنید که در هر پروژه و هر سازمانی ارزش ایجاد میکنند.
مانند:
✅ توانایی حل مسئله
✅ طراحی معماری
✅ تصمیمگیری مهندسی
✅ درک Trade-offها
✅ ارتباط مؤثر با تیم
✅ ایجاد ارزش برای کسبوکار، نه فقط نوشتن کد
💬 مهمترین نکته
بسیاری از افراد فکر میکنند:
اما در عمل، تفاوت اصلی آنها معمولاً در میزان ارزشی است که برای کسبوکار ایجاد میکنند، نه صرفاً در تعداد خطوط کدی که مینویسند.
در نهایت، شرکتها برای «کد» پول پرداخت نمیکنند؛ برای حل مسئله و ایجاد ارزش هزینه میکنند.
سؤال این بود:
مهندسهای نرمافزاری که حقوقهای بسیار بالایی میگیرند، دقیقاً چه نوع مسائلی را حل میکنند؟
برداشت اولیه این سؤال این است که انگار اگر در یک حوزه خاص کار کنید، خودبهخود درآمد بالایی خواهید داشت.
اما واقعیت کمی متفاوت است.
شرکتهای مختلف، کشورهای مختلف و حتی دو استارتاپ در یک صنعت، مدلهای پرداخت کاملاً متفاوتی دارند.
یک استارتاپ تازهتأسیس ممکن است با بودجه محدود حقوق پرداخت کند، در حالی که یک شرکت بزرگ فناوری برای همان نقش چند برابر بیشتر هزینه کند.
بنابراین ارتباط مستقیم بین نوع مسئله و میزان حقوق همیشه وجود ندارد.
در واقع این رابطه، یک رابطه علت و معلولی ساده نیست.
🎯 چیزی که واقعاً ارزش شما را تعیین میکند چیست؟
بر اساس تجربهای که در شرکتهای مختلف دیدهام، مهندسهایی که بیشترین ارزش را ایجاد میکنند معمولاً در یکی از این سه دسته قرار میگیرند:
🚀 1️⃣ حل مسائل با ارزش تجاری بالا در حوزههای تخصصی
آنها روی مسائلی کار میکنند که شاید افراد کمی توانایی حلشان را داشته باشند، اما تأثیر بسیار بزرگی روی کسبوکار دارند.
مثلاً:
بهینهسازی یک سیستم پرداخت که روزانه میلیونها تراکنش پردازش میکند.
طراحی زیرساختی که هزینه سرورها را ۳۰٪ کاهش میدهد.
افزایش Availability یک سرویس حیاتی.
🤝 2️⃣ تبدیل شدن به یک «Multiplier»
این افراد فقط خودشان سریع کدنویسی نمیکنند.
آنها باعث میشوند کل تیم سریعتر و بهتر کار کند.
مثلاً:
طراحی معماری مناسب
ایجاد ابزارهای داخلی
ءMentor کردن اعضای تیم
بهبود فرآیندهای توسعه
حذف گلوگاههای فنی
گاهی ارزش واقعی یک مهندس، در تعداد خط کدی که مینویسد نیست؛ بلکه در تعداد افرادی است که بهرهوری آنها را افزایش میدهد.
📈 3️⃣ ایجاد ارزش پایدار در بخشهای مختلف سیستم
برخی مهندسها فقط روی یک Feature کار نمیکنند.
آنها به صورت مداوم روی بخشهای مختلف سیستم اثر مثبت میگذارند.
برای مثال:
بهبود Performance
افزایش Security
کاهش هزینههای زیرساخت
افزایش Reliability
سادهتر کردن فرآیند توسعه
💡 اگر بخواهیم خیلی ساده بیان کنیم...
میتوان گفت:
حقوق ≈ ارزشی که ایجاد میکنید × گستره تأثیرگذاری شما
هرچه خروجی شما ارزشمندتر باشد و افراد یا بخشهای بیشتری از آن بهره ببرند، احتمال دریافت مسئولیتها و جبران خدمات بالاتر نیز بیشتر خواهد بود.
⚠️ آیا متخصص شدن در یک حوزه بسیار خاص بهترین راه است؟
نه همیشه.
داشتن تخصص عمیق در یک حوزه خاص میتواند بسیار ارزشمند باشد.
اما اگر تمام استراتژی شغلی خود را فقط بر اساس یک مهارت بسیار محدود و خاص بنا کنید، ریسک بالایی دارد.
ممکن است آن مهارت در بازار امروز بسیار پرتقاضا باشد، اما چند سال بعد شرایط کاملاً تغییر کند.
🎯 پس روی چه چیزی سرمایهگذاری کنیم؟
اگر هدف رشد حرفهای است، بهتر است روی مهارتهایی تمرکز کنید که در هر پروژه و هر سازمانی ارزش ایجاد میکنند.
مانند:
✅ توانایی حل مسئله
✅ طراحی معماری
✅ تصمیمگیری مهندسی
✅ درک Trade-offها
✅ ارتباط مؤثر با تیم
✅ ایجاد ارزش برای کسبوکار، نه فقط نوشتن کد
💬 مهمترین نکته
بسیاری از افراد فکر میکنند:
مهندسهایی که بیشترین حقوق را میگیرند، فقط کدنویسهای بهتری هستند.
اما در عمل، تفاوت اصلی آنها معمولاً در میزان ارزشی است که برای کسبوکار ایجاد میکنند، نه صرفاً در تعداد خطوط کدی که مینویسند.
در نهایت، شرکتها برای «کد» پول پرداخت نمیکنند؛ برای حل مسئله و ایجاد ارزش هزینه میکنند.
در طول سالهای گذشته، سیستم اعتبارسنجی مدل (Model Validation) توکار در اکوسیستم داتنت که عمدتاً بر پایهی فضای نام
این طراحی سنتی، پیادهسازی قواعد اعتبارسنجی پیچیدهای را که به عملیاتهای ورودی/خروجی (I/O-bound) وابسته بودند با چالش مواجه میکرد؛ سناریوهایی مانند:
✅ بررسی منحصربهفرد بودن نام کاربری در پایگاه داده
✅ استعلام وضعیت یک شناسه از طریق Web API خارجی
✅ سنجش موجودی انبار
توسعهدهندگان برای پیادهسازی این زیرساختها ناچار بودند یا به ابزارهای شخص ثالث مانند کتابخانه محبوب FluentValidation روی آورند، یا متدهای ناهمگام را به صورت ناامن و مسدودکننده (Thread Blocking) با استفاده از امضاهایی مانند
این رویکرد نامناسب، مستقیماً ریسک بروز گلوگاه در پایگاه ریسمانها (Thread Pool Starvation) را افزایش میداد. 📉
در پی درخواستهای مکرر جامعه توسعهدهندگان، مایکروسافت بالاخره در NET 11 Preview 6. زیرساخت اعتبارسنجی ناهمگام را به صورت بومی به هسته فریمورک اضافه کرد تا به یکی از پررأیترین درخواستهای گیتهاب پاسخ دهد. 🎉
این تغییرات زیرساختی، معماری اعتبارسنجی داتنت را متحول کرده است. 🔥
در این مکانیزم جدید، سه افزونه اساسی به فضای نام
اضافه شده است که به توسعهدهندگان اجازه میدهد منطق ناهمگام خود را بدون مسدود کردن ریسمانها پیادهسازی کنند:
🟢 کلاس انتزاعی
این کلاس پایه جدید، جایگزین مستقیم
🟢 اینترفیس
همتای ناهمگام اینترفیس شناختهشدهی
🟢 متدهای نوین در کلاس static اعتبارسنجی (
معرفی متدهای جدید از جمله:
✅
✅
✅
✅
جهت اجرای غیرمسدودکننده عملیات سنجش صحت دادهها. 🚀
در نمونه کد زیر، شیوه تعریف یک قانون اعتبارسنجی ناهمگام جهت بررسی منحصربهفرد بودن نام کاربری به سادهترین شکل ممکن ارائه شده است. ✨
متد سنتی IsValid به صورت پیشفرض برای سازگاری عقبرو (Backward Compatibility) مقدار ValidationResult.Success را برمیگرداند و منطق اصلی در IsValidAsync به همراه پشتیبانی از CancellationToken پیادهسازی میشود.
🚀 اجرای اعتبارسنجی ناهمگام
جهت فراخوانی و اعتبارسنجی ناهمگام این مدل در خط لولههای دستی یا خارج از لایه کنترلر، موتور Validator متدهای الحاقی جدید خود را ارائه میدهد.
System.ComponentModel.DataAnnotations استوار است، ساختاری کاملاً همگام (Synchronous) داشت. ⚙️این طراحی سنتی، پیادهسازی قواعد اعتبارسنجی پیچیدهای را که به عملیاتهای ورودی/خروجی (I/O-bound) وابسته بودند با چالش مواجه میکرد؛ سناریوهایی مانند:
✅ بررسی منحصربهفرد بودن نام کاربری در پایگاه داده
✅ استعلام وضعیت یک شناسه از طریق Web API خارجی
✅ سنجش موجودی انبار
توسعهدهندگان برای پیادهسازی این زیرساختها ناچار بودند یا به ابزارهای شخص ثالث مانند کتابخانه محبوب FluentValidation روی آورند، یا متدهای ناهمگام را به صورت ناامن و مسدودکننده (Thread Blocking) با استفاده از امضاهایی مانند
.Result یا .Wait() فراخوانی کنند. ⚠️این رویکرد نامناسب، مستقیماً ریسک بروز گلوگاه در پایگاه ریسمانها (Thread Pool Starvation) را افزایش میداد. 📉
در پی درخواستهای مکرر جامعه توسعهدهندگان، مایکروسافت بالاخره در NET 11 Preview 6. زیرساخت اعتبارسنجی ناهمگام را به صورت بومی به هسته فریمورک اضافه کرد تا به یکی از پررأیترین درخواستهای گیتهاب پاسخ دهد. 🎉
🆕 معماری و قابلیتهای جدید در NET 11.
این تغییرات زیرساختی، معماری اعتبارسنجی داتنت را متحول کرده است. 🔥
در این مکانیزم جدید، سه افزونه اساسی به فضای نام
System.ComponentModel.DataAnnotationsاضافه شده است که به توسعهدهندگان اجازه میدهد منطق ناهمگام خود را بدون مسدود کردن ریسمانها پیادهسازی کنند:
🟢 کلاس انتزاعی
AsyncValidationAttributeاین کلاس پایه جدید، جایگزین مستقیم
ValidationAttribute برای سناریوهای ناهمگام است و امکان نوشتن اتریبیوتهای سفارشی را با بازنویسی (Override) متد IsValidAsync فراهم میکند.🟢 اینترفیس
IAsyncValidatableObjectهمتای ناهمگام اینترفیس شناختهشدهی
IValidatableObject که به خودِ مدل یا DTO اجازه میدهد منطق اعتبارسنجی درونیاش را به صورت اسنک (Async) پیادهسازی کند.🟢 متدهای نوین در کلاس static اعتبارسنجی (
Validator)معرفی متدهای جدید از جمله:
✅
ValidateObjectAsync✅
TryValidateObjectAsync✅
ValidatePropertyAsync✅
ValidateValueAsyncجهت اجرای غیرمسدودکننده عملیات سنجش صحت دادهها. 🚀
🧩 پیادهسازی عملی اتریبیوت اعتبارسنجی ناهمگام
در نمونه کد زیر، شیوه تعریف یک قانون اعتبارسنجی ناهمگام جهت بررسی منحصربهفرد بودن نام کاربری به سادهترین شکل ممکن ارائه شده است. ✨
متد سنتی IsValid به صورت پیشفرض برای سازگاری عقبرو (Backward Compatibility) مقدار ValidationResult.Success را برمیگرداند و منطق اصلی در IsValidAsync به همراه پشتیبانی از CancellationToken پیادهسازی میشود.
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
public sealed class UniqueUserNameAttribute : AsyncValidationAttribute
{
// سازگاری با خط لولههای همگام قدیمی
protected override ValidationResult? IsValid(object? value, ValidationContext context)
=> ValidationResult.Success;
// پیادهسازی اصلی برای خط لوله ناهمگام داتنت ۱۱
protected override async Task<ValidationResult?> IsValidAsync(
object? value, ValidationContext context, CancellationToken cancellationToken)
{
// دریافت مستقیم سرویسها از کانتینر DI با استفاده از Context موجود
var users = context.GetRequiredService<IUserStore>();
bool isTaken = await users.ExistsAsync((string?)value, cancellationToken);
return isTaken
? new ValidationResult("این نام کاربری قبلاً توسط کاربر دیگری انتخاب شده است.")
: ValidationResult.Success;
}
}
public class RegistrationRequest
{
[Required]
[UniqueUserName]
public string UserName { get; set; } = "";
}
🚀 اجرای اعتبارسنجی ناهمگام
جهت فراخوانی و اعتبارسنجی ناهمگام این مدل در خط لولههای دستی یا خارج از لایه کنترلر، موتور Validator متدهای الحاقی جدید خود را ارائه میدهد.
// اجرای فرآیند اعتبارسنجی ناهمگام بدون Block کردن Thread
var context = new ValidationContext(model, serviceProvider, items: null);
await Validator.ValidateObjectAsync(
model,
context,
validateAllProperties: true);
📚 36 سؤال #C که تقریباً در هر مصاحبهای پرسیده میشوند
1️⃣ ءBoxing چیست، چه زمانی بهصورت ضمنی (Implicit) اتفاق میافتد و چه هزینهای دارد؟
2️⃣ چرا زمانی که یک struct یک interface را پیادهسازی میکند، Boxing رخ میدهد؟
3️⃣ء ref struct چیست و چه محدودیتهایی دارد؟
4️⃣ تفاوت record struct و record class از نظر رفتار (Semantics) و Performance چیست؟
5️⃣ اگر Equals را Override کنید اما GetHashCode را Override نکنید، چه مشکلاتی به وجود میآید؟
6️⃣ چرا عملگر == برای متغیرهای string با متغیرهایی از نوع object که رشته را نگه میدارند، رفتار متفاوتی دارد؟
7️⃣ ءString Interning چیست و در چه شرایطی دو رشته با محتوای یکسان از نظر Reference برابر نیستند؟
8️⃣ تفاوت float، double و decimal چیست و برای محاسبات مالی از کدام باید استفاده کرد؟
9️⃣ تفاوت DateTime، DateTimeOffset، DateOnly و TimeOnly چیست؟
🔟 ءJIT چگونه کدهای Generic را برای Value Typeها و Reference Typeها بهینهسازی (Specialize) میکند؟
1️⃣1️⃣ ءCovariance و Contravariance در Genericها را توضیح دهید.
2️⃣1️⃣ چرا آرایهها (Arrays) در #C دارای Covariance هستند؟
3️⃣1️⃣ اعضای static abstract در Interfaceها چیستند و چه کاربردی دارند؟
4️⃣1️⃣ آیا استفاده از async یک Thread جدید ایجاد میکند؟ هنگام رسیدن به await دقیقاً چه اتفاقی میافتد؟
5️⃣1️⃣ کامپایلر برای یک متد async چه State Machineای تولید میکند؟
6️⃣1️⃣ تفاوت Task و <ValueTask<T چیست؟
7️⃣1️⃣ چرا استفاده از async void خطرناک است؟
8️⃣1️⃣ چرا فراخوانی Result. یا ()Wait. در محیطی که SynchronizationContext دارد، میتواند باعث Deadlock شود؟
9️⃣1️⃣ ءConfigureAwait(false) دقیقاً چه کاری انجام میدهد؟
0️⃣2️⃣ هزینه واقعی استفاده از Task.Run در یک برنامه سمت سرور چیست؟
1️⃣2️⃣ ءThread Pool Starvation چیست و چگونه آن را تشخیص میدهید؟
2️⃣2️⃣ چگونه باید از CancellationToken بهدرستی استفاده کرد؟
3️⃣2️⃣ مدیریت Exception در Task.WhenAll چه تفاوتی با یک await معمولی دارد؟
4️⃣2️⃣ کلمه کلیدی lock به چه چیزی کامپایل میشود و System.Threading.Lock در C# 13 چه بهبودهایی ارائه میدهد؟
5️⃣2️⃣ کلمه کلیدی volatile چه تضمینهایی ارائه میدهد و در چه شرایطی باید به جای آن از Interlocked استفاده کرد؟
6️⃣2️⃣ چه زمانی باید از <Channel<T به جای <BlockingCollection<T استفاده کرد؟
7️⃣2️⃣ تفاوت <AsyncLocal<T و <ThreadLocal<T چیست؟
8️⃣2️⃣ نسلهای مختلف Garbage Collector (GC Generations) و مفهوم Object Promotion را توضیح دهید. چرا جمعآوری نسل دوم (Gen 2) پرهزینه است؟
9️⃣2️⃣ ءLarge Object Heap (LOH) چیست و چرا برای سرویسهای Long-Running اهمیت دارد؟
0️⃣3️⃣ وجود Finalizer چه تأثیری بر طول عمر یک شیء دارد و چرا در متد Dispose از GC.SuppressFinalize استفاده میشود؟
1️⃣3️⃣ تفاوت Workstation GC و Server GC چیست؟
2️⃣3️⃣ ء<Span<T چه مشکلی را حل میکند و در چه شرایطی باعث حذف Allocationها میشود؟
3️⃣3️⃣ چه زمانی باید از <ArrayPool<T استفاده کرد؟
4️⃣3️⃣ ءClosureها چگونه متغیرها را Capture میکنند؟
5️⃣3️⃣ ءDeferred Execution در LINQ چیست؟
6️⃣3️⃣ ءNative AOT چه مزایایی ارائه میدهد و چه قابلیتهایی را پشتیبانی نمیکند؟
🧠 مفاهیم پایه #C
1️⃣ ءBoxing چیست، چه زمانی بهصورت ضمنی (Implicit) اتفاق میافتد و چه هزینهای دارد؟
2️⃣ چرا زمانی که یک struct یک interface را پیادهسازی میکند، Boxing رخ میدهد؟
3️⃣ء ref struct چیست و چه محدودیتهایی دارد؟
4️⃣ تفاوت record struct و record class از نظر رفتار (Semantics) و Performance چیست؟
5️⃣ اگر Equals را Override کنید اما GetHashCode را Override نکنید، چه مشکلاتی به وجود میآید؟
6️⃣ چرا عملگر == برای متغیرهای string با متغیرهایی از نوع object که رشته را نگه میدارند، رفتار متفاوتی دارد؟
7️⃣ ءString Interning چیست و در چه شرایطی دو رشته با محتوای یکسان از نظر Reference برابر نیستند؟
8️⃣ تفاوت float، double و decimal چیست و برای محاسبات مالی از کدام باید استفاده کرد؟
9️⃣ تفاوت DateTime، DateTimeOffset، DateOnly و TimeOnly چیست؟
⚙️ CLR و Generics
🔟 ءJIT چگونه کدهای Generic را برای Value Typeها و Reference Typeها بهینهسازی (Specialize) میکند؟
1️⃣1️⃣ ءCovariance و Contravariance در Genericها را توضیح دهید.
2️⃣1️⃣ چرا آرایهها (Arrays) در #C دارای Covariance هستند؟
3️⃣1️⃣ اعضای static abstract در Interfaceها چیستند و چه کاربردی دارند؟
🚀 Async و Threading
4️⃣1️⃣ آیا استفاده از async یک Thread جدید ایجاد میکند؟ هنگام رسیدن به await دقیقاً چه اتفاقی میافتد؟
5️⃣1️⃣ کامپایلر برای یک متد async چه State Machineای تولید میکند؟
6️⃣1️⃣ تفاوت Task و <ValueTask<T چیست؟
7️⃣1️⃣ چرا استفاده از async void خطرناک است؟
8️⃣1️⃣ چرا فراخوانی Result. یا ()Wait. در محیطی که SynchronizationContext دارد، میتواند باعث Deadlock شود؟
9️⃣1️⃣ ءConfigureAwait(false) دقیقاً چه کاری انجام میدهد؟
0️⃣2️⃣ هزینه واقعی استفاده از Task.Run در یک برنامه سمت سرور چیست؟
1️⃣2️⃣ ءThread Pool Starvation چیست و چگونه آن را تشخیص میدهید؟
2️⃣2️⃣ چگونه باید از CancellationToken بهدرستی استفاده کرد؟
3️⃣2️⃣ مدیریت Exception در Task.WhenAll چه تفاوتی با یک await معمولی دارد؟
4️⃣2️⃣ کلمه کلیدی lock به چه چیزی کامپایل میشود و System.Threading.Lock در C# 13 چه بهبودهایی ارائه میدهد؟
5️⃣2️⃣ کلمه کلیدی volatile چه تضمینهایی ارائه میدهد و در چه شرایطی باید به جای آن از Interlocked استفاده کرد؟
6️⃣2️⃣ چه زمانی باید از <Channel<T به جای <BlockingCollection<T استفاده کرد؟
7️⃣2️⃣ تفاوت <AsyncLocal<T و <ThreadLocal<T چیست؟
🧹 مدیریت حافظه (Memory Management)
8️⃣2️⃣ نسلهای مختلف Garbage Collector (GC Generations) و مفهوم Object Promotion را توضیح دهید. چرا جمعآوری نسل دوم (Gen 2) پرهزینه است؟
9️⃣2️⃣ ءLarge Object Heap (LOH) چیست و چرا برای سرویسهای Long-Running اهمیت دارد؟
0️⃣3️⃣ وجود Finalizer چه تأثیری بر طول عمر یک شیء دارد و چرا در متد Dispose از GC.SuppressFinalize استفاده میشود؟
1️⃣3️⃣ تفاوت Workstation GC و Server GC چیست؟
2️⃣3️⃣ ء<Span<T چه مشکلی را حل میکند و در چه شرایطی باعث حذف Allocationها میشود؟
3️⃣3️⃣ چه زمانی باید از <ArrayPool<T استفاده کرد؟
4️⃣3️⃣ ءClosureها چگونه متغیرها را Capture میکنند؟
5️⃣3️⃣ ءDeferred Execution در LINQ چیست؟
6️⃣3️⃣ ءNative AOT چه مزایایی ارائه میدهد و چه قابلیتهایی را پشتیبانی نمیکند؟