C# Geeks (.NET)
413 subscribers
152 photos
4 videos
152 links
Download Telegram
در یکی از پروژه‌ ها، قرار بود یک قابلیت جدید به سیستم اضافه کنیم.
یکی از اعضای تیم گفت:
«اگر از همین الان این بخش را Generic طراحی کنیم، بعدا برای چند سناریوی دیگر هم قابل استفاده است.»
پیشنهاد جذابی بود. شروع کردیم به طراحی. Interface اضافه شد. چند لایه Abstraction ایجاد شد. تنظیمات مختلف اضافه شدند.
قابلیتی که قرار بود در دو روز آماده شود، نزدیک دو هفته زمان برد.
چند ماه بعد، برگشتیم و نگاهی به آن بخش انداختیم.
از تمام سناریو هایی که برای آینده پیش‌ بینی کرده بودیم، فقط همان سناریوی اول استفاده شده بود.
بقیه هیچ‌ وقت اتفاق نیفتادند.آن روز یک درس مهم گرفتم.
بزرگ‌ ترین خطر در طراحی نرم‌ افزار، همیشه «طراحی ضعیف» نیست.
گاهی «طراحی برای آینده‌ ای است که هنوز وجود ندارد.»
از آن به بعد، هر وقت وسوسه میشوم چیزی را بیش از نیاز امروز Generalize کنم، از خودم یک سوال میپرسم:
«اگر این نیاز هیچ‌ وقت به وجود نیاید، آیا باز هم حاضر بودم هزینه امروز را پرداخت کنم؟»
خیلی وقت‌ها جواب، «نه» است.
و همان «نه»، باعث میشود سیستم ساده‌ تر بماند.
چون تجربه به من یاد داد که آینده، همیشه آن‌طور که ما طراحی میکنیم پیش نمیرود.
اما پیچیدگی‌ ای که امروز وارد سیستم میکنیم، همین امروز شروع به گرفتن هزینه میکند.
📚 مسیر یادگیری یک برنامه‌نویس NET. در هر سطح

بیشتر توسعه‌دهنده‌ها نمی‌دانند در هر مرحله از مسیر حرفه‌ای خود باید روی چه موضوعاتی تمرکز کنند. به همین دلیل ممکن است سال‌ها تجربه داشته باشند، اما هنوز برای موقعیت‌های Senior یا Architect آماده نباشند.

اگر بخواهم مسیر یادگیری یک برنامه‌نویس NET. را به چهار مرحله تقسیم کنم، این نقشه راه را پیشنهاد می‌دهم:
1️⃣ Junior Developer

در این مرحله هدف، یادگیری اصول برنامه‌نویسی و ساخت پروژه‌های واقعی است.

C# Syntax, Types & OOP
Collections & Basic LINQ
Async/Await Basics
Exception Handling
Building Basic APIs with Controllers & EF Core
Configuration with appsettings.json
Git Basics & IDE Debugging

2️⃣ Middle Developer

در این مرحله دیگر فقط کدنویسی مهم نیست؛ باید یاد بگیرید چرا یک راه‌حل از راه‌حل دیگر بهتر است.

Advanced LINQ, IQueryable vs IEnumerable
Async/Await, CancellationToken & Deadlocks
EF Core Migrations, Relationships & Query Performance
SOLID Principles & Common Design Patterns
Clean Architecture & Layered Architecture
Authentication & Authorization
Middleware, Filters & Model Binding
Validation, Mapping & Dependency Injection
Scheduling & Caching
Structured Logging
Integration Testing
Docker & Containerization

3️⃣ Senior Developer

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

Performance, Memory & Allocation Profiling
Concurrency, Threading & Thread Safety
Distributed Systems Fundamentals
RabbitMQ, Kafka & Azure Service Bus
Resilience with Polly (Retry & Circuit Breaker)
OpenTelemetry (Metrics, Tracing & Observability)
CQRS & Event-Driven Architecture
Database Transactions & Concurrency Control
Security Best Practices (OWASP & Secret Management)
CI/CD Pipelines & Cloud Deployment
Domain Modeling & DDD Tactical Patterns
Code Review & Mentoring
Trade-off Analysis & Technology Selection
Modular Monolith & Service Boundaries

4️⃣ Software Architect

در این مرحله دیگر مسئله فقط نوشتن کد نیست؛ بلکه طراحی سیستم‌هایی است که سال‌ها قابل توسعه و نگهداری باشند.
System Design (Scalability, Availability & Reliability)
Monolith vs Modular Monolith vs Microservices vs Serverless
Strategic DDD (Bounded Context & Context Mapping)
Consistency, Idempotency & Saga Pattern
Data Architecture, Event Sourcing & Polyglot Persistence
CAP Theorem & PACELC Trade-offs
Zero Trust Security & Identity
Non-Functional Requirements (NFRs) & Quality Attributes
ADRs (Architecture Decision Records) & C4 Model
Migration & Modernization Strategy
Standards, Guardrails & Governance
Business-Driven Architecture
Conway's Law & Team Topologies
Stakeholder Communication & Trade-off Analysis
Cloud Cost Optimization
Platform Engineering & Developer Experience (DevEx)
Disaster Recovery & Business Continuity
Multi-Tenancy & Data Isolation Patterns

🎯 نکته مهم

این مسیر یک نردبان نیست که بعد از رسیدن به مرحله بعدی، مطالب قبلی را کنار بگذارید.
هرچه جلوتر می‌روید، عمق دانشتان در موضوعات قبلی بیشتر می‌شود و مفاهیم جدیدی به آن اضافه می‌کنید.
یک Architect خوب، هنوز هم #C را عمیق می‌شناسد؛ فقط نگاهش از «نوشتن کد» به «طراحی سیستم» تغییر کرده است.
🛡 اگر تیمت از اشتباه کردن جلوی تو می‌ترسد، از قبل سقف رشدش را مشخص کرده‌ای.
من به Psychological Safety به چشم یک واژه‌ی مد روز نگاه نمی‌کنم. وقتی مسئول هدایت یک تیم هستی، این یکی از ملموس‌ترین وظایفی است که بر عهده داری.
افراد باید بتوانند ایده‌ای را امتحان کنند، اشتباه کنند و دوباره بلند شوند؛ بدون اینکه نتیجه‌اش تحقیر شدن باشد. 🤝
نکته‌ای که همیشه سعی می‌کنم فراموش نکنم این است:
ساختن اعتمادبه‌نفس یک نفر، به زمان، انرژی و تلاش زیادی نیاز دارد؛ اما نابود کردنش، گاهی فقط به یک واکنش اشتباه نیاز دارد.
یک برخورد نامناسب جلوی اعضای تیم، می‌تواند اعتمادی را که ماه‌ها برای ساختنش زحمت کشیده‌ای، در چند ثانیه از بین ببرد. 💔
به‌عنوان یک رهبر، چه بخواهی و چه نخواهی، تو فرهنگ تیم را شکل می‌دهی.
واکنش تو به یک اشتباه، مخصوصاً زمانی که در مقابل دیگران اتفاق می‌افتد، به تمام اعضای تیم این پیام را می‌دهد که:
«آیا اینجا واقعاً جای امنی برای ریسک کردن و یاد گرفتن هست یا نه؟»
این به معنی نبود مسئولیت‌پذیری (Accountability) نیست.
اشتباه کردن با تحقیر شدن، دو مفهوم کاملاً متفاوت هستند.
وقتی افراد بدانند که می‌توانند بدون ترس از سرزنش یا تحقیر شکست بخورند، حاضر می‌شوند ریسک‌های هوشمندانه‌ای انجام دهند که باعث پیشرفت تیم و محصول می‌شود. 🚀
اما اگر احساس امنیت نداشته باشند، همیشه محافظه‌کارانه عمل می‌کنند، از ایده‌های جدید فاصله می‌گیرند و فقط کاری را انجام می‌دهند که مطمئن هستند بابتش مورد قضاوت قرار نمی‌گیرند.
در نهایت، کیفیت یک تیم را فقط مهارت‌های فنی اعضایش تعیین نمی‌کند؛ بلکه محیطی که رهبر آن تیم می‌سازد نیز به همان اندازه مهم است.
اگر مسئول هدایت یک تیم هستید، وقتی یکی از اعضای تیمتان اشتباه می‌کند، اولین واکنش شما چیست؟
#مهندس_فکر_کن
قسمت: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 مشهوری ندارد اما به‌راحتی قابل فهم، تغییر و نگهداری است،
از کدی که فقط برای نمایش دانش الگوهای طراحی نوشته شده،ارزش مهندسی بیشتری دارد.چون در مهندسی نرم‌افزار،سادگی یک مزیت است؛ نه نشانه کم‌تجربگی.