C# Geeks (.NET)
413 subscribers
152 photos
4 videos
153 links
Download Telegram
#مهندس_فکر_کن
قسمت:5️⃣

🎯مصاحبه‌کننده:
فرض کنید مسئول طراحی یک Notification Platform هستید.
این سیستم از یک Provider برای ارسال SMS استفاده می‌کند و تمام پیامک‌ها از طریق یک Message Broker پردازش می‌شوند.
سناریو
هر روز ساعت 17:00 باید برای حدود 7 میلیون کاربر پیامک واریز سود ارسال شود.
معماری فعلی به شکل زیر است:
                Producer


SMS Queue (FIFO)


SMS Consumers


SMS Provider

هم‌زمان با شروع ارسال این 7 میلیون پیامک، کاربران نیز وارد اپلیکیشن می‌شوند و درخواست OTP Login می‌دهند.
اما کاربران متوجه می‌شوند که کد OTP بعد از 20 تا 40 دقیقه به دستشان می‌رسد، چون پیام OTP پشت صف 7 میلیون پیامک قرار گرفته است.
سؤال
اگر معمار این سیستم باشید، چگونه آن را طراحی می‌کنید تا:
ءOTP حداکثر در چند ثانیه ارسال شود.
ارسال 7 میلیون پیامک نیز متوقف نشود.
هیچ دسته‌ای از پیام‌ها باعث Starvation دسته دیگر نشود.
سیستم در آینده بتواند انواع Notificationهای جدید را نیز پشتیبانی کند.
#تصمیم‌های_مهندسی (Engineering Decisions)
یکی از تصمیم‌هایی که تقریباً هر تیمی دیر یا زود با آن روبه‌رو می‌شود، این است:
«آیا برای این قابلیت، از Cache استفاده کنیم یا نه؟»
جالب است که در بسیاری از تیم‌ها، این سؤال زمانی مطرح می‌شود که Performance افت کرده است.
اما سؤال درست، چیز دیگری است.
«آیا اصلاً مشکل ما، نداشتن Cache است؟»
بارها دیده‌ام اولین راه‌حل پیشنهادی برای کاهش زمان پاسخ، اضافه کردن Redis یا Memory Cache بوده است.
در حالی که بعد از اندازه‌گیری مشخص شده:
ءQuery اشتباه نوشته شده است.
ءN+1 Query رخ می‌دهد.
یک API خارجی گلوگاه سیستم است.
یا حتی بخش زیادی از زمان صرف Serialization می‌شود.
در چنین شرایطی، Cache فقط صورت مسئله را پنهان می‌کند.
نه اینکه آن را حل کند.
به همین دلیل، تصمیم استفاده از Cache نباید یک تصمیم Performance باشد.
باید یک تصمیم معماری باشد.
یعنی قبل از هر چیز باید بدانیم:
آیا داده به اندازه کافی پایدار است؟
آیا Consistency لحظه‌ای برای Business اهمیت دارد؟
هزینه Invalidating Cache چقدر است؟
اگر Cache از دسترس خارج شود، سیستم چه رفتاری خواهد داشت؟
اگر جواب این سؤال‌ها روشن نباشد،
اضافه کردن Cache، بیشتر شبیه قرض گرفتن از آینده است تا بهینه‌سازی.
چون در مهندسی نرم‌افزار،
بهترین تصمیم همیشه این نیست که سیستم را سریع‌تر کنیم.
گاهی بهترین تصمیم این است که اول دلیل کند بودن آن را بفهمیم.
🔍 چرا شرکت‌های بزرگ یک Search Service جداگانه می‌سازند؟

اوایل پروژه همه چیز ساده است.
کاربر عبارتی را جستجو می‌کند و با یک LIKE یا ()Contains روی دیتابیس، نتیجه برمی‌گردد.
اما وقتی حجم داده‌ها زیاد می‌شود، ناگهان هر سرویس شروع می‌کند به پیاده‌سازی Search، Pagination، Ranking، Filter، Autocomplete و Full-Text Search.
همین‌جاست که مفهوم Search Service وارد می‌شود.
به جای اینکه هر سرویس خودش مسئول جستجو باشد، یک سرویس مرکزی فقط روی Search و Indexing تمرکز می‌کند.
هر سرویس منطق Search خودش را دارد.
🏗 معماری با Search Service
                    Search Query

Search Service

Elasticsearch / OpenSearch

Search Index

┌──────────┼──────────┐
↓ ↓ ↓
Service A Service B Service C
│ │ │
└──── Publish Events ─────┘

تمام سرویس‌ها فقط داده‌های خود را به Search Service ارسال می‌کنند و کاربران نیز تمام جستجوهای خود را از طریق همین سرویس انجام می‌دهند.
مزایا
1️⃣ سرعت بسیار بالا

ءSearch Engineها برای جستجو طراحی شده‌اند، نه Databaseهای رابطه‌ای.
حتی روی میلیون‌ها رکورد نیز پاسخ در چند میلی‌ثانیه برمی‌گردد.
2️⃣ Full-Text Search

امکاناتی مانند:
• Fuzzy Search
• Typo Tolerance
• Stemming
• Synonym
• Ranking
• Relevance Score
به صورت پیش‌فرض در اختیار شما قرار می‌گیرد.
3️⃣ حذف بار از Database

دیگر Queryهای سنگین Search روی دیتابیس اصلی اجرا نمی‌شوند.
در نتیجه:
• Load کمتر
• Response Time بهتر
• Performance بیشتر
4️⃣ جستجوی یکپارچه

فرض کنید سیستم شما شامل:
• Articles
• Products
• Users
• Orders
باشد.
کاربر فقط یک بار Search انجام می‌دهد و نتایج از تمام سرویس‌ها برمی‌گردد.
5️⃣ قابلیت توسعه بالا

به راحتی می‌توانید اضافه کنید:
• Autocomplete
• Suggestion
• Highlight
• Faceted Search
• Geo Search
• Semantic Search
بدون اینکه Business Serviceها تغییری کنند.
6️⃣ قابلیت Ranking
می‌توانید تعیین کنید:
• محبوب‌ترین نتایج
• جدیدترین
• مرتبط‌ترین
• پربازدیدترین
در ابتدای لیست نمایش داده شوند.
معایب
1️⃣
Eventual Consistency

نتایج Search همیشه لحظه‌ای به‌روز نیستند.
ممکن است چند ثانیه طول بکشد تا Indexها بروزرسانی شوند.
2️⃣ پیچیدگی بیشتر

باید مفاهیمی مانند:
• Index
• Mapping
• Analyzer
• Shard
• Replica
• Reindex
را مدیریت کنید.
3️⃣ نیاز به همگام‌سازی داده‌ها

هر تغییری در دیتابیس باید وارد Search Index نیز شود.
4️⃣ هزینه بیشتر

نگهداری Elasticsearch یا OpenSearch منابع سخت‌افزاری قابل توجهی نیاز دارد.

🚀 ءFlow اصولی پیاده‌سازی
مرحله 1️⃣
کاربر اطلاعات جدید ثبت می‌کند.
POST /articles

مرحله 2️⃣
ءBusiness Service اطلاعات را داخل Database ذخیره می‌کند.
مرحله 3️⃣
یک Domain Event منتشر می‌شود.
ArticleCreated

مرحله 4️⃣
ءSearch Service این Event را دریافت می‌کند.
مرحله 5️⃣
اطلاعات داخل Search Index ذخیره می‌شود.
Elasticsearch
OpenSearch

مرحله 6️⃣
کاربر درخواست Search ارسال می‌کند.
GET /search?q=DDD

مرحله 7️⃣
ءSearch Service نتایج را از Index خوانده و به Client برمی‌گرداند.

📋 نکاتی که حتماً باید رعایت شوند
🔸 ءSearch Database را جایگزین Database اصلی نکنید.
🔸 از Event-Driven Architecture برای بروزرسانی Index استفاده کنید.
🔸 ءQueryهای Search مستقیماً روی Database اجرا نشوند.
🔸 ءIndexها Version داشته باشند.
🔸 برای عملیات Reindex برنامه مشخصی داشته باشید.
🔸 ءMappingها را از ابتدا با دقت طراحی کنید.
🔸 از Analyzer مناسب برای زبان‌های مختلف استفاده کنید.
🔸 ءShard و Replicaها بر اساس حجم داده تنظیم شوند.
🔸 ءMonitoring برای وضعیت Indexها فعال باشد.
🔸 در صورت خطا، عملیات Indexing قابلیت Retry داشته باشد.

⭐️ برای بهتر شدن Search Service چه کارهایی انجام دهیم؟
Incremental Indexing
Background Indexing
Event-Driven Synchronization
Autocomplete
Search Suggestions
Highlight Search Results
Faceted Search
Geo Search
Semantic Search با AI
Synonym Dictionary
Spell Correction
Cache نتایج پرتکرار
Distributed Search Cluster
Zero-Downtime Reindex

🔖هشتگ‌ها:
#search #elasticsearch #opensearch #eventdriven #fulltextsearch
یک متنی خواندم که خیلی نظرمو جلب کرد.
تا حالا دقت کرده‌ای چرا پل‌ها را برای روزهای عادی طراحی نمی‌کنند؟
در بیشتر روزهای سال، پل فقط وزن چند خودرو را تحمل می‌کند.
اما مهندس سازه، پل را برای همان روز طراحی نمی‌کند. او به روزی فکر می‌کند که ترافیک سنگین باشد.باران شدید ببارد.باد شدید بوزد.یا یک کامیون سنگین از روی آن عبور کند.

اگر پل فقط برای شرایط عادی طراحی شود،
در اولین شرایط غیرعادی، ارزشش را از دست می‌دهد.

آیا نرم‌افزار هم دقیقاً همین‌طور است؟
یعنی مهندس نرم افزار از اول تمام نیاز هارو شناسایی و همشون رو پیاده سازی میکنه؟
برداشتتون از این موضوع چیه؟
نظراتتون واقعا برام ارزشمنده🫶🏻
#اشتباهات_مهندسی (Engineering Mistakes)
یکی از اشتباهاتی که خیلی آرام وارد پروژه می‌شود، این است که به جای حذف کردن، فقط اضافه می‌کنیم.
یک Configuration جدید...
یک Feature Flag جدید...
یک Service جدید...
یک جدول جدید...
یک Endpoint جدید...
کم‌کم سیستم بزرگ‌تر می‌شود.
اما سؤال اینجاست:
آخرین باری که چیزی را از سیستم حذف کردیم، کی بود؟
جالب است که اضافه کردن، همیشه حس پیشرفت می‌دهد.
اما حذف کردن، شجاعت می‌خواهد.
چون باید مطمئن باشی چیزی را که سال‌ها وجود داشته، دیگر کسی نیاز ندارد.
تیم‌های باتجربه فقط قابلیت جدید توسعه نمی‌دهند.
به همان اندازه، روی حذف چیزهایی که دیگر ارزش ایجاد نمی‌کنند هم وقت می‌گذارند.
کدی که دیگر استفاده نمی‌شود.APIهایی که هیچ Client فعالی ندارند.Featureهایی که سال‌هاست کسی از آن‌ها استفاده نکرده است.
هر چیزی که حذف نمی‌شود، هزینه نگهداری دارد.
شاید امروز دیده نشود.
اما در هر Refactoring، هر Deploy و هر تغییر، خودش را نشان می‌دهد.
در مهندسی نرم‌افزار،
رشد یک سیستم فقط به چیزهایی که به آن اضافه می‌کنیم وابسته نیست.
گاهی کیفیت واقعی یک سیستم را،
چیزهایی تعیین می‌کنند که جرئت حذف کردنشان را داشته‌ایم.
🧵 یکی از چالش‌های همیشگی و آزاردهنده برای توسعه‌دهندگان در هنگام پردازش متون، مدیریت کاراکترهای پایان خط یا همان خط جدید (Newline) بوده است.
😓 تفاوت در نحوه ذخیره‌سازی خطوط جدید در سیستم‌عامل‌های مختلف و همچنین استانداردهای گوناگون انکودینگ، همواره باعث بروز باگ‌های پنهان در پردازش متن و عبارات باقاعده (Regex) می‌شود.
🎉 خوشبختانه مایکروسافت در NET 11. با معرفی یک قابلیت جدید و کاربردی به نام RegexOptions.AnyNewLine، این گره کور را باز کرده‌است.
در این مقاله، به بررسی این چالش قدیمی، مشکلات راهکارهای سنتی و نحوه حل اصولی آن در دات‌نت ۱۱ می‌پردازیم.
📌 چالش تاریخی: تنوع استانداردهای خط جدید

از گذشته تا به امروز، سیستم‌عامل‌های مختلف روش‌های متفاوتی برای تعریف پایان یک خط داشته‌اند.
به طور سنتی، سه فرمت اصلی وجود داشت:

🪟 ویندوز (Windows): استفاده از ترکیب \r\n (CRLF)
🐧 یونیکس و لینوکس (Unix & Linux): استفاده از \n (LF)
🍎 مک‌اواس قدیمی (Older MacOS): استفاده از \r (CR)

🌍 با ظهور استاندارد Unicode، این تنوع باز هم فراتر رفت و سه کاراکتر دیگر نیز به این جمع اضافه شدند:
🔹 Next Line (\u0085 - NEL)
🔹 Line Separator (\u2028 - LS)
🔹 Paragraph Separator (\u2029 - PS)

💡 حال تصور کنید متنی ترکیبی دارید که از منابع مختلف (وب‌سرویس‌ها، فایل‌های آپلود شده توسط کاربران با سیستم‌عامل‌های گوناگون و دیتابیس‌ها) جمع‌آوری شده و شامل انواع این کاراکترهاست:
var text = "The Quick\r\nBrown Fox\u0085Jumped Over\u2028The Lazy Dog\nBigly";

🎯 هدف ما این است که خطوط این متن چندخطی (Multiline) را به صورت تفکیک‌شده استخراج کنیم.
🔍 بررسی عملکرد در نسخه‌های قدیمی دات‌نت
در NET 10. و نسخه‌های پیش از آن، برای استخراج خطوط معمولاً از الگوی زیر استفاده می‌شد:
var oldLines = Regex.Matches(text, @"^.*$", RegexOptions.Multiline)
.Select(r => r.Value)
.ToArray();

در این عبارت باقاعده:
📍 علامت ^ نشان‌دهنده ابتدای خط است.
📍 الگوی .* هر کاراکتری به جز \n را به تعداد دلخواه جستجو می‌کند.
📍 علامت $ نشان‌دهنده انتهای خط است.
📍 گزینه RegexOptions.Multiline به موتور پردازش اعلام می‌کند که متن ورودی ساختار چندخطی دارد.
مشکل کجاست؟
⚠️ موتور Regex دات‌نت به طور سنتی فقط کاراکتر \n (LF) را به عنوان جداکننده خط در حالت Multiline می‌شناسد.
در نتیجه اجرای کد بالا روی متن نمونه، خروجی مطلوبی نخواهد داشت:
کاراکترهای یونیکد مانند \u0085 یا \u2028 اصلاً شناسایی نمی‌شوند و خطوط متصل به آن‌ها جدا نخواهند شد.
در مورد ویندوز (\r\n) نیز، از آنجا که فقط \n به عنوان پایان خط در نظر گرفته می‌شود، کاراکتر \r (Carriage Return) به عنوان بخشی از متن خط اول استخراج می‌شود و معمولاً باید با متدهایی مانند Trim() حذف شود.

🚀 راهکار دات‌نت ۱۱: پرچم RegexOptions.AnyNewLine

مایکروسافت در نسخه NET 11.، پرچم جدیدی به نام RegexOptions.AnyNewLine به کلاس Regex اضافه کرده است.
با فعال‌سازی این گزینه، لنگرهای سر خط (^)، ته خط ($)، پایان رشته (\Z) و همچنین کاراکتر نقطه (.)، به صورت هوشمند تمامی استانداردهای خط جدید (اعم از ویندوز، لینوکس و انواع یونیکدها) را به رسمیت می‌شناسند.
کد بهینه‌شده در NET 11. به شکل زیر تغییر می‌کند:
var text = "The Quick\r\nBrown Fox\u0085Jumped Over\u2028The Lazy Dog\nBigly";

// استفاده همزمان از حالت چندخطی و پرچم جدید AnyNewLine
var newLines = Regex.Matches(
text,
@"^.*$",
RegexOptions.Multiline | RegexOptions.AnyNewLine)
.Select(r => r.Value)
.ToArray();

foreach (var line in newLines)
{
Console.WriteLine($"Line: {line}");
}

چرا این راهکار بسیار کارآمدتر است؟
🌍 1. شناسایی کامل Unicode

تمام کاراکترهای پایان خط یونیکد، بدون نیاز به نوشتن Patternهای پیچیده و طولانی، به طور خودکار پردازش می‌شوند.
⚡️ 2. یکپارچه‌سازی اتمیک \r\n

برخلاف ترفندهای قدیمی (مانند استفاده از \r?$) که کاراکتر \r را به عنوان بخشی از تطابق (Match) برمی‌گرداندند،
گزینه AnyNewLine با ترکیب \r\n به عنوان یک موجودیت واحد (Atomic) برخورد می‌کند و از ورود کاراکتر مزاحم \r به خروجی نهایی جلوگیری می‌کند.
🎯 جمع‌بندی
معرفی قابلیت RegexOptions.AnyNewLine در NET. 11 گام مهمی در جهت بهبود پایداری و سادگی پردازش متون چندزبانه و بین‌المللی است.
با به‌کارگیری این پرچم، دیگر نیازی به نوشتن متدهای کمکیِ سنگین برای نرمال‌سازی خطوط پیش از اعمال Regex یا استفاده از Patternهای پیچیده و غیراستاندارد نخواهید داشت.
#تحلیل_و_طرز_تفکر (Engineering Mindset)
یکی از عادت‌هایی که سعی کردم در خودم از بین ببرم، این بود که هر وقت یک راه‌حل خوب پیدا می‌کنم، سریع عاشقش نشوم.
چون بهترین راه‌حل امروز، ممکن است بدترین تصمیم شش ماه بعد باشد.
بارها دیده‌ام تیمی که با اطمینان از یک معماری دفاع می‌کرد، چند ماه بعد برای حذف همان معماری جلسه گذاشت.
نه چون اشتباه بود.
چون شرایط عوض شده بود.
تعداد کاربران تغییر کرده بود.
نیازهای بیزینس تغییر کرده بود.
تیم بزرگ‌تر شده بود.
یا محدودیت‌های جدیدی به پروژه اضافه شده بود.
این تجربه یک چیز را به من یاد داد.
در مهندسی نرم‌افزار، خیلی از تصمیم‌ها «درست» یا «غلط» نیستند.آن‌ها وابسته به شرایط هستند.
به همین دلیل، هر وقت از یک تصمیم دفاع می‌کنم، سعی می‌کنم به جای گفتن: «این بهترین راه‌حل است.» بگویم:
«با دانشی که امروز داریم و محدودیت‌های فعلی، این بهترین انتخاب است.»
این جمله یک تفاوت مهم ایجاد می‌کند.
چون اجازه می‌دهد اگر فردا واقعیت تغییر کرد، تصمیممان را هم تغییر دهیم.
بدون اینکه احساس کنیم شکست خورده‌ایم.
در مهندسی نرم‌افزار، بلوغ یعنی به تصمیم‌هایت متعهد باشی... اما به آن‌ها وابسته نشوی.
چرا شرکت‌های بزرگ یک 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 این کد را نوشته‌ای:
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 پیش ببرید.

این تغییر نشونه بیکارشدن ما نیست ولی بیزینس چی براش کمتر هزینه داشته باشه میره به اون سمت، تا بوده همین بوده.
شما سعی کنید مسبب این کاهش هزینه باشید.
(#باور_غلط_یا_واقعیت؟)
باور غلط

«هرچه تعداد 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 نیست.
یک طرز فکر است.
طرز فکری که می‌گوید:
«انسان‌ها اشتباه می‌کنند؛ وظیفه سیستم این است که اجازه ندهد یک اشتباه کوچک، به یک فاجعه بزرگ تبدیل شود.»
فرهنگ مهندسی از جایی شروع می‌شود که اعضای تیم،
برای گفتن حقیقت، احساس امنیت کنند.
چون تیمی که از اشتباهاتش یاد می‌گیرد،
خیلی سریع‌تر از تیمی رشد می‌کند که فقط سعی می‌کند اشتباهاتش را پنهان کند.
🚨 قابلیت جدید 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