یافتن (و رفع کردن) ریسکهای امنیتی 🔍🛡
ءStatic code analysis میتواند به شما کمک کند آسیبپذیریهای امنیتی احتمالی را در کدتان شناسایی کنید.
در این مثال، یک PasswordHasher فقط از 10,000 iterations برای تولید یک password hash استفاده میکند.
قانون S5344 از SonarAnalyzer.CSharp این مشکل را شناسایی کرده و به شما هشدار میدهد. حداقل تعداد iterations پیشنهادی 100,000 است. ⚠️
میتوانید به توضیحات قانون S5344 مراجعه کنید تا بیشتر یاد بگیرید:
ذخیرهسازی رمز عبور با hashing ضعیف یک ریسک امنیتی جدی برای اپلیکیشنها است. 🔐❗️
وقتی TreatWarningsAsErrors فعال باشد، build شما تا زمانی که مشکل را حل نکنید fail خواهد شد.
این کار احتمال ورود ریسکهای امنیتی به محیط production را به شدت کاهش میدهد. 🚫🔓
نتیجهگیری 📌
ءStatic code analysis یک ابزار قدرتمند است که من همیشه در تمام پروژههای #C استفاده میکنم.
این ابزار کمک میکند مشکلات را زودتر پیدا کنم، کد قابلاعتمادتر و امنتری داشته باشم و در نهایت زمان و انرژی کمتری صرف کنم.
هرچند راهاندازی اولیه و تنظیم دقیق قوانین ممکن است کمی زمانبر باشد، اما مزایای بلندمدت آن غیرقابل انکار است. ⏳✔️
به خاطر داشته باشید که static code analysis یک ابزار کمکی است و جایگزین سایر فعالیتهای توسعه نمیشود.
وقتی static code analysis را با تکنیکهایی مثل code review, unit testing, و continuous integration ترکیب کنید،
میتوانید یک فرایند توسعهی قدرتمند بسازید که همیشه خروجی باکیفیت تولید کند. 💡🚀
ءStatic code analysis را جدی بگیرید.
نسخهی آیندهی شما (و تیمتان) از شما تشکر خواهد کرد. 🙌💙
آنچه بازنویسی یک پروژهی ۴۰ ساله به من دربارهی توسعهی نرمافزار آموخت 🧓💻⚙️
«وظیفهی شما این است که این سیستم را بازنویسی کنید. تمام عملیات ما روی آن اجرا میشود.
اوه، و این سیستم با APL نوشته شده.»
با این جمله، سفر من برای بازنویسی یک سیستم legacy آغاز شد. برای کسانی که با APL آشنا نیستند: این زبان برنامهنویسی مربوط به دههی ۱۹۶۰ است و بهخاطر نشانهگذاری ریاضی خاص و تواناییهای قدرتمند در array manipulation شناخته میشود. پیدا کردن توسعهدهندهای که امروز APL بداند تقریباً به سختی پیدا کردن یک floppy disk drive در یک کامپیوتر مدرن است. 🥲💾
این سیستم طی چهار دهه رشد کرده بود. ابتدا یک ابزار سادهی مدیریت موجودی بود، اما کمکم به یک سیستم ERP جامع تبدیل شد.
بیش از 460+ جدول دیتابیس. بیشمار Business rule در دل کد.Integrationهای پیچیده در هر بخش از فرآیند کسبوکار.
این سیستم ستون فقرات یک عملیات تولیدی بود که بیش از ۱۰ میلیون دلار درآمد سالانه ایجاد میکرد. 🏭💰
ماموریت ما واضح اما ترسناک بود:
مدرنسازی این سیستم با استفاده از dotNET,PostgreSQL, و React. ⚡️
اما یک نکتهی حیاتی وجود داشت:
کسبوکار باید بدون کوچکترین وقفه ادامه پیدا میکرد.
بدون downtime.
بدون data loss.
بدون اختلال در عملیات روزانه. ⛔️🕒
این فقط یک چالش فنی نبود. یک درس بزرگ بود دربارهی مدیریت پیچیدگی، فهم فرآیندهای legacy، و هدایت تعاملات سازمانی.
این داستان و درسهای آموختهشدهی آن است.
وضعیت اولیه: درک Legacy 🏛🧠
اولین چالش این بود که بفهمیم این سیستم عظیم چطور کار میکند. این codebase طی ۴۰ سال بهصورت ارگانیک رشد کرده بود و تنها توسط یک تیم توسعهی ثابت نگهداری میشد. اعضای این تیم حالا در دههی ۶۰ زندگی بودند و قصد بازنشستگی داشتند.
وقتی وارد اولین بازبینی کد شدیم، انگار یک کپسول زمان باز کرده باشیم.
سینتکس بسیار فشردهی APL باعث میشد منطق تجاری پیچیده فقط در چند خط نوشته شود.
زیبا، اگر میتوانستید آن را بخوانید.
وحشتناک، اگر نمیتوانستید.
و اکثر ما نمیتوانستیم. 😅📜
تیم اصلی در مرحلهی انتقال دانش، بیقیمت بود. آنها تمام نکتهها، edge caseها، و business ruleهایی را که طی دههها اضافه شده بود، از حفظ بودند. اما تنها بخش محدودی از این دانش را میتوان از طریق گفتگو منتقل کرد. Documentation کم بود. هر آنچه وجود داشت منسوخ شده بود.
مستندات واقعی در ذهن توسعهدهندگان اصلی بود.
ما هفتهها صرف map کردن کارکردهای سیستم کردیم:
• فرآیند اصلی تولید در بیش از 50+ جدول با وابستگیهای پیچیده پخش شده بود
• مدیریت موجودی تقریباً به همهی بخشهای دیگر سیستم وصل شده بود
• ابزارهای گزارشگیری custom طی دههها ساخته شده بودند تا نیازهای خاص را برآورده کنند
• ءIntegration با سیستمهای خارجی از طریق یک هزارتوی stored procedureها انجام میشد
• جدولهایی که با ساختارهای ساده شروع شده بودند اکنون شامل صدها ستون بودند؛ برخی ستونها دیگر استفاده نمیشدند، اما حذفشان ممکن نبود چون شاید یک گزارش نامعلوم هنوز به آنها وابسته باشد
چالش اصلیتر این بود که بین توصیف سادهی فرآیندهای کسبوکار و پیادهسازی فنی عمیقاً پیچیدهی آنها فاصلهی بزرگی وجود داشت.
کسبوکار یک workflow ساده را توضیح میداد، اما نسخهی فنی آن لایههای متعددی از edge caseها و نیازهای اضافهشده طی سالها را آشکار میکرد.
ما به یک رویکرد سیستماتیک برای فهمیدن این «هیولا» نیاز داشتیم.
شروع کردیم به map کردن business processها و پیادهسازی فنی متناظر آنها.
این کار به ما کمک کرد domainهای اصلی را شناسایی کنیم؛ domainهایی که بعدها معماری ماژولار سیستم جدید را شکل دادند.
مهمتر از آن، به ما کمک کرد مقیاس واقعی کاری که در پیش داشتیم را درک کنیم. 🧩📘
تعارض Product و Engineering ⚔️📊
ءManagement بهدنبال quick wins بود. آنها ما را تحت فشار قرار میدادند که از سادهترین کامپوننتها شروع کنیم. این موضوع میان product management و تیم توسعه تنش ایجاد کرد.
دیدگاه product management روشن بود: باید به کسبوکار پیشرفت قابلمشاهده نشان داد. آنها برای توجیه سرمایهگذاری در بازنویسی سیستم، نیاز به خروجیهای قابلنمایش داشتند. کسبوکار پول قابلتوجهی خرج میکرد و میخواست هرچه سریعتر بازدهی ببیند. 💰📈
اما تیم توسعه واقعیت متفاوتی میدید. ما میدانستیم شروع از بخشهای حاشیهای یعنی ساختوساز روی پایههای سست. هستهی منطق کسبوکار در سیستم legacy باقی میماند و این موضوع هر نقطهی integration را پیچیدهتر میکرد. این بدهی فنی در طول زمان انباشته میشد.
بهعنوان یک technical lead، من بهشدت با این رویکرد مخالفت کردم. استدلالم ساده بود: فرآیند اصلی تولید قلب کل سیستم بود. تمام قابلیتهای جانبی به آن وابسته بودند. با بهتعویقانداختن مهاجرت این core، ما یک شبکهی درهمتنیده از وابستگیها میان سیستم جدید و قدیمی ایجاد میکردیم. هر قابلیت جدیدی که مهاجرت میدادیم نیازمند یک همگامسازی پیچیده با core legacy میشد. ما داشتیم روی ماسههای روان ساختوساز میکردیم. 🏜⚠️
من توصیه کردم ابتدا روی core domain تمرکز کنیم. درست است، نمایش اولین نتایج بیشتر طول میکشید. اما یک پایهی محکم برای ادامهی کار ایجاد میشد. کسبوکار باید دیرتر منتظر «پیشرفت قابلرویت» میبود، اما کل فرآیند مهاجرت سریعتر و قابلاتکاتر انجام میشد.
هیچیک از دو طرف در اهداف خود اشتباه نمیکردند. Product management نگرانیهای موجهی دربارهی نمایش progress داشت.
تیم توسعه نگرانیهای موجهی دربارهی پایداری فنی داشت.
اما این عدمهمراستایی باعث بهوجود آمدن مصالحههایی شد که بر timeline پروژه اثر گذاشت.
تا امروز معتقدم اگر از core business logic شروع کرده بودیم، مهاجرت سریعتر تمام میشد. 🕰🔧
معماری نرمافزار: ساختن برای آینده 🏗🔮
در مرحلهی discovery، ما domainهای کسبوکاری متمایز را در سیستم شناسایی کردیم. این موضوع ما را به سمت پیادهسازی یک modular monolith هدایت کرد. هر ماژول self-contained بود اما میتوانست از طریق یک event bus مشترک با دیگر ماژولها ارتباط برقرار کند.
تصمیمات کلیدی معماری: 🧩
🔸️ءModular monolith:
هر ماژول یک business domain مستقل را نمایش میداد. این کار مسیر روشنی برای حرکت احتمالی آینده به سمت microservices ایجاد میکرد.
🔹️ءAsynchronous communication:
ماژولها از طریق eventها و با استفاده از RabbitMQ با یکدیگر ارتباط برقرار میکردند. این کار coupling را کاهش داده و resiliency را افزایش داد.
🔸️ءShared database با مرزبندی مشخص:
تمام ماژولها از یک دیتابیس PostgreSQL استفاده میکردند، اما هر ماژول جدولها و schemaهای مخصوص خود را داشت. این رویکرد جداسازی منطقی را حفظ میکرد.
🔹️ءCloud-ready design:
سیستم با استفاده از containerization روی AWS مستقر شد. یک Jenkins pipeline امکان deployment به چند environment را در چند دقیقه فراهم میکرد. ☁️🚀
چالش Data Sync 🔄💾
پیادهسازی همگامسازی دوطرفهی داده بسیار پیچیدهتر از چیزی بود که در ابتدا تصور میکردیم. دلیل اینکه نتوانستیم از راهکارهای موجودِ Change Data Capture (CDC) مانند Debezium استفاده کنیم اینها بود:
1️⃣ تبدیلهای پیچیده:
بسیاری از جدولهای legacy برای ساخت رکورد جدید نیازمند داده از چندین جدول در سیستم جدید بودند. این یک mapping سادهی یکبهیک —که ابزارهای CDC در آن عالی هستند— نبود. 🧩
2️⃣ اعمال Business Logic در فرآیند Sync:
فرآیند sync باید business ruleهای متعددی را در مرحلهی transformation اعمال میکرد. این فراتر از قابلیتهای ابزارهای replication معمول بود. ⚙️
3️⃣ نیازمندیهای دوطرفه (Bidirectional):
باید داده را در هر دو جهت همگام میکردیم، بدون آنکه دچار loop بینهایت شویم. سیستم legacy تا زمانی که برخی کامپوننتها مهاجرت نکرده بودند، همچنان source of truth باقی میماند. 🔁❗️
ما یک راهکار custom با استفاده از RabbitMQ برای انتقال پیام ساختیم. این راهکار برای نیازهای ما مناسب بود؛ اما درس بزرگ این است:
پیش از ساخت هر راهکار سفارشی، ابزارهای موجود را بهطور کامل ارزیابی کنید.
حتی اگر نتوانید آنها را تماموکمال استفاده کنید، از معماریها و الگوهای آنها چیزهای ارزشمندی خواهید آموخت. 🛠📘
درسهای فنی کلیدی 🧠💡
• معماری ماژولار بازده دارد:
رویکرد modular monolith باعث شد سیستم قابلدرکتر و قابلنگهداریتر باشد. هر ماژول محدوده و مسئولیتهای روشن داشت.
• سرمایهگذاری روی Deployment Automation:
وجود CI/CD pipeline حیاتی بود. امکان استقرارهای سریع و مطمئن را فراهم کرد و ریسک هر تغییر را کاهش داد. 🚀
• یکپارچهسازی مبتنی بر پیام:
ارتباط async میان ماژولها انعطافپذیری لازم برای مهاجرت تدریجی را فراهم کرد.
• پیچیدگی Data Sync:
هرگز پیچیدگی همگامسازی داده را در مهاجرتهای legacy دستکم نگیرید. چه ابزارهای موجود را استفاده کنید، چه راهکار custom بسازید، این بخش یکی از بزرگترین چالشهاست. ⚠️
عامل انسانی 👥
چالشهای فنی تنها بخشی از ماجرا هستند. موفقیت در بازنویسی سیستمهای legacy شدیداً وابسته به مدیریت ذینفعان است:
🔸️ءProduct Management باید progress ببیند
🔸️تیم توسعه نیاز دارد کار را درست انجام دهد
🔸️کسبوکار باید بدون توقف ادامه پیدا کند
🔸️تیم legacy باید انتقال دانش انجام دهد
🔸️یافتن تعادل میان این نیازهای متعارض، کار آسانی نبود.
ما از چند رویکرد مؤثر استفاده کردیم:
🔹️جلسات منظم ذینفعان برای بیان دغدغهها
🔹️ردیابی شفاف پروژه که برای همه قابل مشاهده بود
🔹️توضیح شفاف تصمیمات فنی و تأثیر آنها بر کسبوکار
🔹️جشنگرفتن milestoneهای فنی و تجاری 🎉
مستندسازی دانش فنی و دانش سازمانی 📚
اهمیت مستندسازی تجربهی ۴۰ سالهی تیم legacy را نمیتوانم دستکم بگیرم.
وقتی تیم اصلی بازنشسته شد، ما مجموعهی کاملی از اسناد داشتیم که تکتک business ruleها و edge caseها را توضیح میداد.
دستاوردهای واقعی 🌟📈
چهار سال بعد، سیستم در وضعیت بسیار خوبی قرار دارد.
زیرساخت cloud، قابلیت اطمینان و مقیاسپذیری را فراهم کرده است.
معماری modular monolith سیستم را قابلنگهداری کرده است.Pipelineهای خودکار امکان استقرار سریع را فراهم میکنند.
اما این مسیر به ما آموخت که باید میان فشارهای کسبوکاری و نیازهای فنی تعادل ایجاد کرد. موفقیت در مهاجرت سیستمهای legacy فقط به برتری فنی وابسته نیست — بلکه نیازمند درک حوزهی کسبوکار، مدیریت انتظارات ذینفعان، و اتخاذ تصمیمات معماری واقعگرایانه است.
معماری نرمافزار مهم است، اما عامل انسانی نیز به همان اندازه اهمیت دارد.
برای هر دو برنامهریزی کنید. 🤝🏗
متشکرم از اینکه خواندید.
و همیشه فوقالعاده بمانید! 🚀
اگر EF Core خودش Repository Pattern را ارائه میدهد، چرا باید یک abstraction دیگر روی آن بسازیم؟ 🤔📦یکی از بحثهای همیشگی در جامعهی .NET این است:
آیا هنوز باید از الگوهای Repository و Unit of Work استفاده کنیم، یا این الگوها در برنامههای مدرن منسوخ شدهاند یا حتی مضر هستند؟
بههرحال، اگر روی DbContext در EF Core hover کنید، مایکروسافت بهوضوح میگوید:
A DbContext instance represents a session with the database and is a combination of the Unit of Work and Repository patterns.
این یعنی EF Core همین حالا این الگوها را پیادهسازی میکند:
• ءDbContext نقش Unit of Work را بازی میکند
• ء<DbSet<TEntity نقش Repository را ایفا میکند
پس سؤال طبیعی این است:
وقتی EF Core همین حالا این abstractions را فراهم میکند، چرا باید یک abstraction دیگر روی آن بسازیم — یعنی یک «abstraction روی abstraction»؟ 🧱🧱
بسیاری از توسعهدهندگان همچنین معتقدند نوشتن repositoryهای سفارشی باعث میشود قابلیتهای قدرتمند EF Core از دید پنهان شود، مانند:
• Eager Loading
• AsNoTracking
• Projections
• Query Composition
• Global Query Filters
• Split Queries
بازنویسی همهی اینها داخل Repositoryهای custom معمولاً به یک نتیجه منتهی میشود:
«ما فقط داریم DbContext را دوباره اختراع میکنیم، فقط با قدمهای اضافی.» 🔁🙃
یک استدلال رایج دیگر testability است.
برخی میگویند برای تستهای واحد باید repository داشته باشیم تا بتوانیم DbContext را mock کنیم؛ اما EF Core همین حالا یک InMemory provider عالی دارد که تستها را واقعیتر میکند و نیاز به mock کردن کل لایهی persistence را از بین میبرد. 🧪🚫
و سپس یک ترس دیگر هم وجود دارد:
«شاید یک روز ORM را عوض کنیم.»
اما در ۹۹٪ پروژههای واقعی این اتفاق نمیافتد.
و حتی اگر هم بیفتد، تغییر فقط repository interfaceها را شامل نمیشود، بلکه بخشهای بسیار بیشتری از سیستم را لمس خواهد کرد — پس این استدلال عمدتاً یک نقض آشکارِ YAGNI است. ⚠️🧠
البته هر Repository اضافی که ایجاد میکنید، سربار نگهداری را افزایش میدهد:
کلاسهای بیشتر 📦
اینترفیسهای بیشتر 🧩
ساختارهای اتصال بیشتر 🔌
پیچیدگی بیشتر 🧠
پس… آیا Repository Pattern مضر است؟ یا ما آن را اشتباه استفاده میکنیم؟
واقعیت، ظریفتر از این حرفهاست.
بسیاری از نظرات منفی ناشی از سوءبرداشت از هدف Repository هستند.Repository هرگز قرار نبود تمام قابلیتهای persistence framework را در معرض دید قرار دهد.
هدف آن این نیست که API مربوط به DbContext را بازتولید کند.
وظیفهٔ اصلی آن این است که کنترل کند دامنه چگونه با داده تعامل میکند.
🎯 Repository واقعاً چه هدفی دارد؟
بهجای ارائهٔ یک سطح دسترسی باز و نامحدود به داده، Repository یک سری عملیات Aggregate-focused، صریح، و intention-revealing تعریف میکند.
این کار چند مزیت مهم به همراه دارد:
منطق کوئری قابلپیشبینی و قابل نگهداری
رفتار کوئری ثابت و در طول زمان بهینهتر میشود.
شفافیت برای domain expertها
نام متدها هدف و مفهوم تجاری را بیان میکنند؛ نیازی نیست SQL یا LINQ بلد باشند.
عملیات دامنهمحور بهجای CRUD عمومی
بهجای اینکه هر نوع دستکاری entity را در اختیار بگذارد، رفتارهایی ارائه میدهد که واقعاً برای دامنه معنادار است.
🛡 هدف واقعی: محافظت از Domain Model
ءRepository pattern در درجهٔ اول دربارهٔ اینها نیست:
• آسانتر کردن unit testing
• امکان تعویض Database
• بستهبندی امکانات EF Core
هدف اصلی آن این است که Domain Model را از نشت نگرانیهای persistence محافظت کند.
در غیاب یک مرز Repository، منطق persistence دیر یا زود وارد دامنه میشود، وضوح را کاهش میدهد، coupling را افزایش میدهد، و مدل را تضعیف میکند.
⛔️ چه زمانی نباید از Repository استفاده کنید؟
• زمانی که دامنهٔ شما ساده است
• زمانی که CRUD کافی است
• زمانی که میخواهید از قابلیتهای EF Core بهصورت مستقیم استفاده کنید
در این موارد، Repository فقط پیچیدگی غیرضروری اضافه میکند.
✅ و چه زمانی باید از Repository استفاده کنید؟
• وقتی با یک دامنهٔ پیچیده و غنی از رفتار سروکار دارید
در چنین سیستمهایی، Repository تبدیل به بخشی از دامنه میشود، قدرت بیان مدل را افزایش میدهد و مرزهای Aggregate را تقویت میکند.
✨ جمعبندی
ءRepository pattern ضدالگو نیست
استفادهٔ اشتباه از آن ضدالگوست.
وقتی آگاهانه و در دامنههای غنی و پیچیده بهکار گرفته شود، به ابزاری قدرتمند برای بیان intent تجاری و حفظ یک معماری تمیز و مقاوم تبدیل میشود.
ساده نگهش دارید.
📌Daniel Jajimi
🔖هشتگها:
#DDD #CleanArchitecture #RepositoryPattern #SoftwareArchitecture #AggregateDesign
معماری Vertical Slice: منطق مشترک دقیقاً کجا باید قرار بگیرد؟ 🚀
معماری Vertical Slice Architecture (VSA) وقتی برای اولین بار با آن روبهرو میشوید شبیه یک نسیم تازه است.
دیگر برای افزودن یک فیلد مجبور نیستید بین هفت لایه جابهجا شوید. پروژههای متعدد را از داخل Solution حذف میکنید. احساس آزادی میکنید.
اما وقتی شروع به پیادهسازی قابلیتهای پیچیدهتر میکنید، ترکها شروع به نمایان شدن میکنند. ⚠️
یک Slice برای CreateOrder میسازید. سپس UpdateOrder. بعد GetOrder.
ناگهان متوجه تکرارها میشوید:
• منطق اعتبارسنجی آدرس در سه مکان تکرار شده است.
• الگوریتم قیمتگذاری هم در Cart نیاز است و هم در Checkout.
• احساس میکنید باید یک Common project یا یک SharedServices folder بسازید.
این لحظه، بحرانیترین نقطه در مسیر پذیرش VSA است.
اگر اشتباه انتخاب کنید، همان coupling که قصد داشتید از آن فرار کنید را دوباره برمیگردانید. 🔄
اگر درست انتخاب کنید، استقلالی را حفظ میکنید که VSA را ارزشمند کرده است.
در ادامه توضیح میدهم که چطور من با shared code در Vertical Slice Architecture برخورد میکنم.
گاردریلها در برابر جادهٔ باز 🛣
برای اینکه بفهمیم چرا این موضوع سخت است، باید به چیزی که پشت سر گذاشتهایم نگاه کنیم.Clean Architecture گاردریلهای سختگیرانه ارائه میکند.
کاملاً مشخص میکند که هر کدی دقیقاً کجا زندگی میکند:
• Entities در Domain
• Interfaces در Application
• Implementations در Infrastructure
امن است. جلوی خطاها را میگیرد.
اما همچنین جلوی میانبرهای ضروری را نیز میگیرد.
در مقابل، Vertical Slice Architecture گاردریلها را حذف میکند.
این معماری میگوید:
"کد را بر اساس قابلیتها سازماندهی کن، نه بر اساس دغدغههای تکنیکی."
این کار سرعت و انعطافپذیری به شما میدهد،
اما بار انضباط معماری را بر دوش خودتان میگذارد.
پس چه باید کرد؟ 🤔
تله: کشوی آشفتگی به نام "Common" 🗃
سادهترین مسیر این است که یک پروژه یا فولدر به نامهای Shared, Common, یا Utils بسازید.
این کار تقریباً همیشه یک اشتباه است. ❌
فرض کنید پروژهای دارید به نام Common.Services همراه با یک کلاس OrderCalculationService.
این کلاس:
• یک متد برای جمع Cart دارد (مورد استفادهی Cart)
• یک متد برای درآمد تاریخی دارد (مورد استفادهی Reporting)
• یک Helper برای فرمتکردن فاکتور دارد (مورد استفادهی Invoices)
سه concern کاملاً بیربط.
سه نرخ تغییر متفاوت.
و یک کلاس که همهٔ اینها را به یکدیگر couple کرده است. 🕸
پروژهٔ Common دیر یا زود تبدیل میشود به یک junk drawer،
محلی برای هر چیزی که حوصلهٔ نامگذاری یا جایگذاری درستش را ندارید.
نتیجه؟
یک شبکهٔ پیچیده از وابستگیها که در آن قابلیتهای مستقل، فقط چون یک Helper مشترک استفاده میکنند، به هم گره میخورند.
در واقع coupling که قصد داشتید از آن فرار کنید دوباره بازمیگردد. 🔁
چارچوب تصمیمگیری 🧭
وقتی به یک موقعیت بالقوهٔ اشتراکگذاری (Sharing) میرسم، سه سؤال از خودم میپرسم:
1️⃣ آیا این موضوع Infrastructural است یا Domain؟
موارد Infrastructure مثل database contexts، logging، HTTP clients تقریباً همیشه باید Shared باشند.
اما مفاهیم Domain نیاز به بررسی دقیقتری دارند.
2️⃣ این کانسپت چقدر پایدار است؟
اگر سالی یک بار تغییر میکند → Shared کردن مناسب است.
اگر همراه با هر Feature Request تغییر میکند → محلی نگهش دارید (Local).
3️⃣ آیا از «Rule of Three» عبور کردهام؟
یک بار Duplicate کردن مشکلی ندارد.
دو بار هم قابل تحمل است.
اما سه بار تکرار باید برای شما زنگ خطر باشد.
تا قبل از رسیدن به سه، Abstraction انجام ندهید.
ما اینها را با Refactor کردن حل میکنیم. بیایید مثالها را ببینیم. 🔍
سه سطح اشتراکگذاری
بهجای اینکه فقط دو گزینهٔ «Shared» یا «Not Shared» داشته باشید، در سه سطح فکر کنید.
Tier 1: Technical Infrastructure (کاملاً قابل اشتراک) ⚙️
کدهای Plumbing که تمام Sliceها به یک اندازه از آن بهره میبرند:
• Logging adapters
• Database connection factories
• Auth middleware
•الگوی Result
•Validation pipelines
این موارد را در یک پروژهٔ Shared.Kernel یا Infrastructure قرار دهید.
همچنین میتوانند فقط یک فولدر باشند.
این بخشها بهندرت بهخاطر نیازهای کسبوکار تغییر میکنند.
نمونهٔ مناسب اشتراکگذاری: Technical Kernel
public readonly record struct Result
{
public bool IsSuccess { get; }
public string Error { get; }
private Result(bool isSuccess, string error)
{
IsSuccess = isSuccess;
Error = error;
}
public static Result Success() => new(true, string.Empty);
public static Result Failure(string error) => new(false, error);
}
Tier 2: Domain Concepts (اشتراکگذاری و انتقال منطق به پایینترین سطح) 🧩
این یکی از بهترین مکانها برای Shared کردن منطق است.
بهجای پخشکردن Business Ruleها در Sliceهای مختلف، منطق را در Entities و Value Objects قرار دهید.
نمونهٔ مناسب: Entity با منطق تجاری
public class Order
{
public Guid Id { get; private set; }
public OrderStatus Status { get; private set; }
public List<OrderLine> Lines { get; private set; }
public bool CanBeCancelled() => Status == OrderStatus.Pending;
public Result Cancel()
{
if (!CanBeCancelled())
{
return Result.Failure("Only pending orders can be cancelled.");
}
Status = OrderStatus.Cancelled;
return Result.Success();
}
}
حالا Sliceهای زیر همگی دقیقاً از همین قوانین استفاده میکنند:
CancelOrder
GetOrder
UpdateOrder
منطق فقط در یک مکان زندگی میکند.
این نکتهٔ مهم را نشان میدهد:
ءSliceهای مختلف میتوانند Domain Model یکسانی را به اشتراک بگذارند.
Tier 3: Feature-Specific Logic (محلی نگه دارید) 📌
منطق مشترک بین Sliceهای مرتبط — مانند CreateOrder و UpdateOrder — نیازی ندارد که Global شود.
میتوانید یک فولدر کوچک Shared در داخل یک Feature ایجاد کنید:
📂 Features
└──📂 Orders
├──📂 CreateOrder
├──📂 UpdateOrder
├──📂 GetOrder
└──📂 Shared
├──📄 OrderValidator.cs
└──📄 OrderPricingService.cs
این یک مزیت پنهان هم دارد:
اگر یک روز Feature مربوط به Orders را حذف کنید، Shared logic مخصوص آن هم حذف میشود.
هیچ کد مردهای (Zombie Code) باقی نمیماند. 🧟♂️❌
اشتراکگذاری بین Featureهای مختلف 🚦
در Vertical Slice Architecture، اشتراکگذاری کد بین Featureهای نامرتبط چطور انجام میشود؟
ءCreateOrder باید بررسی کند آیا Customer وجود دارد یا نه.GenerateInvoice باید Tax را محاسبه کند.Orders و Customers هر دو باید پیامهای Notification را فرمت کنند.
اینها در یک فولدر Shared مخصوص Feature جا نمیگیرند. پس کجا باید بروند؟
اول، بپرسید: آیا واقعاً نیاز به اشتراکگذاری وجود دارد؟
بیشتر اشتراکگذاریهای cross-feature در واقع Data Access در پوشش جدید هستند.
اگر CreateOrder به اطلاعات Customer نیاز دارد، باید مستقیماً دیتابیس را Query کند.
نباید Feature مربوط به Customers را صدا بزند.
هر Slice مالک دسترسی به دادههای خودش است.Entity مربوط به Customer Shared است (در Domain قرار دارد)،
اما Service اشتراکی بین آنها وجود ندارد.
وقتی واقعاً نیاز به اشتراکگذاری منطق دارید، بپرسید ماهیت آن چیست:
اگر Domain Logic است (Business Rules یا Calculations) → Domain/Services
اگر Infrastructure است (APIهای خارجی، Formatting) → Infrastructure/Services
نمونه:
// Domain/Services/TaxCalculator.cs
public class TaxCalculator
{
public decimal CalculateTax(Address address, decimal subtotal)
{
var rate = GetTaxRate(address.State, address.Country);
return subtotal * rate;
}
}
هم CreateOrder و هم GenerateInvoice میتوانند از آن استفاده کنند بدون اینکه به هم Coupled شوند.
قبل از ساخت هر Service برای اشتراکگذاری cross-feature، بپرسید:
آیا این منطق میتواند روی یک Domain Entity قرار بگیرد؟
بیشتر "shared business logic"ها در واقع:
• ءdata access هستند
• ءdomain logicی هستند که باید روی Entity قرار بگیرند
• ءabstractionهای زودهنگاماند
اگر نیاز دارید یک side effect در Feature دیگری ایجاد کنید، پیشنهاد میشود:
از Messaging و Event استفاده کنید
یا Feature مقصد یک Facade (یک API عمومی) برای این عملیات ارائه کند
زمانی که Duplication انتخاب درست است 🔁
گاهی چیزی Shared به نظر میرسد، اما واقعاً Shared نیست.
هر دو یکساناند.
وسوسهٔ ساخت یک SharedOrderDto شدید است.
مقاومت کنید.
هفتهٔ بعد، GetOrder نیاز به tracking URL پیدا میکند.
اما CreateOrder وقتی اجرا میشود هنوز Shipping انجام نشده؛ پس URL وجود ندارد.
یک property nullable اضافه میشد
نیمی از مواقع خالی بود
و باعث ابهام و Coupling میشد.Duplication ارزانتر از Abstraction اشتباه است.
این ساختاری است که یک پروژهٔ بالغ Vertical Slice Architecture معمولاً دارد:
توضیح بخشها:
🔸️ءFeatures → Sliceهای مستقل. هرکدام صاحب Request/Response خودشاناند.
🔸️ءFeatures/[Name]/Shared → اشتراکگذاری محلی بین Sliceهای مرتبط یک Feature.
🔸️ءDomain → Entities، Value Objects، Domain Services.
🔸️ءInfrastructure → کل نگرش فنی سیستم.
🔸️ءShared → فقط Cross-Cutting Behaviors.
بعد از ساخت چند سیستم با این معماری، به این اصول رسیدهام:
1️⃣ ءFeatures صاحب Request/Response خودشان هستند. بدون استثنا.
2️⃣ منطق تجاری را تا حد ممکن وارد Domain کنید.
ءEntities و ValueObjectها بهترین مکان برای اشتراکگذاری واقعی Business Rules هستند.
3️⃣ اشتراکگذاری در سطح Feature-Family را محلی نگه دارید.
فقط اگر کد فقط در Orderها استفاده میشود → همانجا نگه دارید.
4️⃣ ءInfrastructure بهصورت پیشفرض Shared است.
Persistence، Logging، HTTP Clients
5️⃣ ءRule of Three را رعایت کنید.
تا وقتی سه استفادهٔ واقعی و مشابه ندارید → abstraction نکنید.
ءVertical Slice Architecture از شما میپرسد:
«این کد متعلق به کدام Feature است؟»
سؤال اشتراکگذاری در واقع میپرسد:
«اگر جوابش چند Feature است چه کنم؟»
پاسخ:
پذیرفتن اینکه برخی مفاهیم واقعاً cross-feature هستند،
و دادن یک محل مشخص به آنها بر اساس ماهیتشان: Domain، Infrastructure یا Behavior.
هدف، حذف کامل Duplication نیست.
هدف این است که وقتی نیازها تغییر میکند، تغییر کد ارزان و ساده باشد.
و نیازها همیشه تغییر میکنند.
Thanks for reading.
And stay awesome! ✨
آیا این منطق میتواند روی یک Domain Entity قرار بگیرد؟
بیشتر "shared business logic"ها در واقع:
• ءdata access هستند
• ءdomain logicی هستند که باید روی Entity قرار بگیرند
• ءabstractionهای زودهنگاماند
اگر نیاز دارید یک side effect در Feature دیگری ایجاد کنید، پیشنهاد میشود:
از Messaging و Event استفاده کنید
یا Feature مقصد یک Facade (یک API عمومی) برای این عملیات ارائه کند
زمانی که Duplication انتخاب درست است 🔁
گاهی چیزی Shared به نظر میرسد، اما واقعاً Shared نیست.
// Features/Orders/GetOrder
public record GetOrderResponse(Guid Id, decimal Total, string Status);
// Features/Orders/CreateOrder
public record CreateOrderResponse(Guid Id, decimal Total, string Status);
هر دو یکساناند.
وسوسهٔ ساخت یک SharedOrderDto شدید است.
مقاومت کنید.
هفتهٔ بعد، GetOrder نیاز به tracking URL پیدا میکند.
اما CreateOrder وقتی اجرا میشود هنوز Shipping انجام نشده؛ پس URL وجود ندارد.
اگر DTO مشترک ساخته بودید:
یک property nullable اضافه میشد
نیمی از مواقع خالی بود
و باعث ابهام و Coupling میشد.Duplication ارزانتر از Abstraction اشتباه است.
ساختار عملی 🏗
این ساختاری است که یک پروژهٔ بالغ Vertical Slice Architecture معمولاً دارد:
📂 src
└──📂 Features
│ ├──📂 Orders
│ │ ├──📂 CreateOrder
│ │ ├──📂 UpdateOrder
│ │ └──📂 Shared # اشتراکگذاری مخصوص Orders
│ ├──📂 Customers
│ │ ├──📂 GetCustomer
│ │ └──📂 Shared # اشتراکگذاری مخصوص Customers
│ └──📂 Invoices
│ └──📂 GenerateInvoice
└──📂 Domain
│ ├──📂 Entities
│ ├──📂 ValueObjects
│ └──📂 Services # منطق domain مشترک
└──📂 Infrastructure
│ ├──📂 Persistence
│ └──📂 Services
└──📂 Shared
└──📂 Behaviors
توضیح بخشها:
🔸️ءFeatures → Sliceهای مستقل. هرکدام صاحب Request/Response خودشاناند.
🔸️ءFeatures/[Name]/Shared → اشتراکگذاری محلی بین Sliceهای مرتبط یک Feature.
🔸️ءDomain → Entities، Value Objects، Domain Services.
🔸️ءInfrastructure → کل نگرش فنی سیستم.
🔸️ءShared → فقط Cross-Cutting Behaviors.
قوانین 🧠
بعد از ساخت چند سیستم با این معماری، به این اصول رسیدهام:
1️⃣ ءFeatures صاحب Request/Response خودشان هستند. بدون استثنا.
2️⃣ منطق تجاری را تا حد ممکن وارد Domain کنید.
ءEntities و ValueObjectها بهترین مکان برای اشتراکگذاری واقعی Business Rules هستند.
3️⃣ اشتراکگذاری در سطح Feature-Family را محلی نگه دارید.
فقط اگر کد فقط در Orderها استفاده میشود → همانجا نگه دارید.
4️⃣ ءInfrastructure بهصورت پیشفرض Shared است.
Persistence، Logging، HTTP Clients
5️⃣ ءRule of Three را رعایت کنید.
تا وقتی سه استفادهٔ واقعی و مشابه ندارید → abstraction نکنید.
نتیجهگیری 📌
ءVertical Slice Architecture از شما میپرسد:
«این کد متعلق به کدام Feature است؟»
سؤال اشتراکگذاری در واقع میپرسد:
«اگر جوابش چند Feature است چه کنم؟»
پاسخ:
پذیرفتن اینکه برخی مفاهیم واقعاً cross-feature هستند،
و دادن یک محل مشخص به آنها بر اساس ماهیتشان: Domain، Infrastructure یا Behavior.
هدف، حذف کامل Duplication نیست.
هدف این است که وقتی نیازها تغییر میکند، تغییر کد ارزان و ساده باشد.
و نیازها همیشه تغییر میکنند.
Thanks for reading.
And stay awesome! ✨
🔖هشتگها:
#VerticalSliceArchitecture #SoftwareArchitecture #DotNet #CSharp #ArchitecturePatterns
تفاوت IEnumerable و IEnumerator
یادمه در یکی از مصاحبه هایی که داشتم مصاحبه کننده وارد مبحث Collection ها شد و ازم خواست تفاوت IEnumerable و IEnumerator رو توضیح بدم.
منم چون فقط اسمشونو شنیده بودم گفتم هردو اینترفیس های Collection هستن
و وقتی مصاحبه کننده گفت: خب؟! همین...؟؟!
تازه فهمیدم چقدر زیاد راجب این دو و تفاوت هاشون نمیدونم...
تفاوت IEnumerable و IEnumerator
برای اینکه موضوع برام جا بیفته، یک مثال واقعی کمکم کرد.
ببینیم IEnumerable چیه؟
فرض کن وارد یک کتابخانه میشیم.
اولین چیزی که می بینیم لیست موضوعات کتابهاست: رمان، فلسفه، تاریخی، علمی و…
اما هنوز به هیچ کتابی نزدیک نشدیم، چیزی ورق نزدیم و هیچکدومو انتخاب نکردیم.
درواقع IEnumerable دقیقاً همین کار رو میکنه:
فقط میگه این مجموعه قابل پیمایشه و میشه روش iterat کرد.
نه آیتم میاره، نه جلو و عقب میره، نه حرکت میکنه.
و IEnumerator چیه؟
حالا نوبت کتابداره! IEnumerator همون کتابداریه که:
حرکت میکنه
آیتم فعلی رو میاره
و حتی دوباره میتونه به اول لیست برگرده
و سه قابلیت اصلی و کلیدی داره:
حرکت به آیتم بعدی : ()MoveNext
آیتم فعلی : Current
برگشت به ابتدا : ()void Reset
یعنی تنها «عامل حرکت» در تمامی collection ها ، IEnumerator هست.
و حالا یه اتفاق جالب رخ میده وقتی ما روی یک لیست حلقه میزنیم.
وقتی مینویسیم:
foreach (var item in list)
کامپایلر اینو تبدیل میکنه به:
IEnumerator enumerator=list.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
}
یعنی foreach دقیقا این کار رو انجام میده:
از IEnumerable → یک Enumerator جدید میگیره و باهاش پیمایش انجام میده.
خلاصه تفاوت های اصلی:
🔸️IEnumerable :
مشخص میکنه یک مجموعه قابل پیمایشه (مثل فهرست کتابخانه)
🔹️IEnumerator:
یک الگوریتم پیمایشه (مثل کتابداری که حرکت میکنه و کتاب میاره)
این موضوع شاید وقتیکه متوجه ش شدم ساده بهنظر رسید،
اما فهمیدن همین مفاهیم پایه و به ظاهر ساده ست که کیفیت کد و صاحب کد رو مشخص میکنه.
🔗Link
🚫 ءDbContext Thread-Safe نیست: اجرای موازی Queryهای EF Core به روش درست
ما همه یک بار آن endpoint را ساختهایم.
همان Endpoint معروفی که برای Executive Dashboard یا User Summary استفاده میشود. جایی که باید چندین مجموعه دادهٔ کاملاً نامربوط را بگیری تا یک تصویر کامل به کاربر نشان بدهی.
مثلاً:
• ۵۰ سفارش آخر
• لاگهای سلامت سیستم
• تنظیمات پروفایل کاربر
• تعداد اعلانها
و معمولاً کد را اینگونه مینویسیم:
var orders = await GetRecentOrdersAsync(userId);
var logs = await GetSystemLogsAsync();
var stats = await GetUserStatsAsync(userId);
return new DashboardDto(orders, logs, stats);
این کد کار میکند. تمیز است. خواناست. اما یک مشکل جدی وجود دارد. ⚠️
اگر:
🔹️GetRecentOrdersAsync = 300ms
🔹️GetSystemLogsAsync = 400ms
🔹️GetUserStatsAsync = 300ms
کاربر باید ۱ ثانیه کامل منتظر بماند (۳۰۰ + ۴۰۰ + ۳۰۰).
در سیستمهای توزیعشده، Latency تجربهٔ کاربری را نابود میکند.
و چون این دادهها از هم مستقل هستند، باید بتوانیم آنها را به صورت موازی اجرا کنیم.
اگر این کار را بکنیم، طول کل درخواست = کندترین Query (۴۰۰ms)
یعنی ۶۰٪ بهبود عملکرد فقط با تغییر نحوهٔ اجرای Queryها. 🔥
اما اگر با Entity Framework Core به شکل ساده بخواهی آنها را موازی کنی…
اپلیکیشن Crash میکند.
💥 وعدهٔ دروغین Task.WhenAll
رایجترین اشتباه این است که کد را برداری، آن را در چند Task بیندازی و منتظر بمانی:
// ❌ DO NOT DO THIS
public async Task<DashboardData> GetDashboardData(int userId)
{
var ordersTask = _repository.GetOrdersAsync(userId);
var logsTask = _repository.GetLogsAsync();
var statsTask = _repository.GetStatsAsync(userId);
await Task.WhenAll(ordersTask, logsTask, statsTask); // BOOM 💥
return new DashboardData(ordersTask.Result, logsTask.Result, statsTask.Result);
}
اگر این را اجرا کنی، بلافاصله با این Exception روبرو میشوی:
A second operation started on this context before a previous operation completed.
This is usually caused by different threads using the same instance of DbContext...
چرا این اتفاق میافتد؟ 🤔
❗️چرا DbContext Thread-Safe نیست؟
زیرا DbContext در EF Core stateful است و برای "یک Unit of Work" طراحی شده.
دلایل اصلی:
1️⃣ Change Tracker
ءDbContext وضعیت Entityهایی که بارگذاری شدهاند را نگه میدارد.
دو Thread همزمان نمیتوانند این وضعیت را تغییر دهند.
2️⃣ Single Database Connection
هر DbContext معمولاً یک Connection دارد.
پروتکل پایگاهداده (TCP Stream برای PostgreSQL یا SQL Server) اجازهٔ اجرای همزمان چند Query روی یک Connection را نمیدهد.
3️⃣ ءRace Condition و خراب شدن State داخلی
اگر دو Query همزمان اجرا شوند، DbContext نمیداند کدام نتیجه مربوط به کدام Query است.
بنابراین EF Core برای جلوگیری از فساد داده، Exception میاندازد.
🧩 پس یک تناقض داریم:
میخواهیم Queryها را موازی اجرا کنیم
اما EF Core ما را مجبور میکند پیاپی اجرا کنیم.
در ادامهٔ مقاله راهحلهای درست معرفی میشود…
✅ راهحل
از NET 5. به بعد، EF Core دقیقاً برای همین سناریو یک راهحل درجهیک ارائه داده:
IDbContextFactory<T> 🎉
بهجای تزریق یک DbContext با طول عمر Scoped (که برای کل Request زنده میماند)، یک Factory تزریق میکنیم که هر زمان لازم شد یک DbContext سبک، مستقل و بدون تداخل بسازد.
این یعنی:
هر Task → یک DbContext مستقل → یک Connection مستقل → بدون هیچ Conflict👌
💡نکته:
اگر به DbContextOptions دسترسی داری، حتی میتوانی دستی هم Context بسازی:
using var context = new AppDbContext(options);
🛠 ثبت Factory در Program.cs
ء<IDbContextFactory<AppDbContext به صورت Singleton رجیستر میشود و خود AppDbContext همچنان به صورت Scoped قابل استفاده است
builder.Services.AddDbContextFactory<AppDbContext>(options =>
{
options.UseNpgsql(builder.Configuration.GetConnectionString("db"));
});
⚡️ ءRefactor کردن Dashboard برای اجرای موازی
بهجای اینکه AppDbContext را تزریق کنیم، <IDbContextFactory<AppDbContext را تزریق میکنیم.
در هر Task:
1️⃣ یک DbContext جدید میسازیم
2️⃣ ءQuery را اجرا میکنیم
3️⃣ آن را Dispose میکنیم
بدون هیچ تداخلی 🔥
using Microsoft.EntityFrameworkCore;
public class DashboardService(IDbContextFactory<AppDbContext> contextFactory)
{
public async Task<DashboardDto> GetDashboardAsync(int userId)
{
// 1. اجرای موازی تمام Query ها
var ordersTask = GetOrdersAsync(userId);
var logsTask = GetSystemLogsAsync();
var statsTask = GetUserStatsAsync(userId);
// 2. صبر برای اتمام همه
await Task.WhenAll(ordersTask, logsTask, statsTask);
// 3. برگرداندن نتایج
return new DashboardDto(
await ordersTask,
await logsTask,
await statsTask
);
}
private async Task<List<Order>> GetOrdersAsync(int userId)
{
await using var context = await contextFactory.CreateDbContextAsync();
return await context.Orders
.AsNoTracking()
.Where(o => o.UserId == userId)
.OrderByDescending(o => o.CreatedAt)
.ThenByDescending(o => o.Amount)
.Take(50)
.ToListAsync();
}
private async Task<List<SystemLog>> GetSystemLogsAsync()
{
await using var context = await contextFactory.CreateDbContextAsync();
return await context.SystemLogs
.AsNoTracking()
.OrderByDescending(l => l.Timestamp)
.Take(50)
.ToListAsync();
}
private async Task<UserStats?> GetUserStatsAsync(int userId)
{
await using var context = await contextFactory.CreateDbContextAsync();
return await context.Users
.Where(u => u.Id == userId)
.Select(u => new UserStats { OrderCount = u.Orders.Count })
.FirstOrDefaultAsync();
}
}
🧠 مفاهیم کلیدی
1️⃣ جداسازی کامل Contextها 🧩
هر Task → یک DbContext مستقل
یعنی:
• یک Connection جدا
• بدون رقابت روی Change Tracker
• بدون Race Condition
• بدون Exception خطرناک EF Core
2️⃣ ءDispose شدن سریع Context 🧹
با await using، Context فوراً Dispose میشود و Connection به Connection Pool برمیگردد.
این نکته حیاتی است، مخصوصاً زیر بار بالا.
📊 Benchmark
برای اثبات اینکه این روش واقعاً کار میکند، یک برنامه کوچک NET 10. با Aspire و PostgreSQL ساختم.
چون همهچیز روی سیستم محلی اجرا میشد، زمانها خیلی پاییناند؛
اما اگر دیتابیس ریموت بود، اعداد بزرگتر میشدند — نسبتِ سرعت (Speedup Ratio) تقریباً همین میمانَد.
🐢 اجرای ترتیبی (Sequential Execution): حدود ۳۶ms
در حالت ترتیبی، دقیقاً یک Waterfall داریم:
هر Query منتظر تمامشدن Query قبلی میمانَد.
یعنی:
Query 1 → تمام شود → Query 2 → تمام شود → Query 3
این ساختار از لحاظ تجربهٔ کاربری، فوقالعاده کند است. ⏳
⚡️ اجرای موازی (Parallel Execution): حدود ۱۳ms
با رویکرد موازیسازی:
تمام Query ها همزمان شروع میشوند و تقریباً همزمان به پایان میرسند.
نتیجه:
۳ برابر سریعتر
(در این مثال: ۳۶ms → ۱۳ms)
این همان جایی است که تاخیرهای شبکه، I/O و latency دیتابیس را بهصورت مؤثری کاهش میدهیم. 🚀
⚖️ ملاحظات، Trade-off ها و نتیجهگیری
استفاده از IDbContextFactory پلی است بین:
معماری EF Core که بر واحد کار (Unit of Work) و Context واحد طراحی شده و نیازهای مدرن که اجرای موازی حقیقی را میطلبد.
این الگو به شما اجازه میدهد از جعبهٔ
"یک درخواست → یک Thread → یک DbContext"
خارج شوید — بدون اینکه Thread-Safety یا Data Integrity قربانی شود. ✔️
اما باید با احتیاط استفاده شود:
⚠️ 1️⃣ احتمال اتمام Connection Pool
وقتی درخواست قبلاً فقط ۱ Connection مصرف میکرد،
در حال حاضر همان درخواست ۳ Connection میگیرد.
اگر ترافیک بالایی داشته باشید و connection pool کوچک باشد:
ممکن است سریعاً Connection Exhaustion رخ دهد.
این یعنی درخواستها در صف میمانند یا Timeout میدهند. ❗️
⚠️ 2️⃣ سربار Context
اگر Query های شما بسیار سریع هستند (lookup ساده بر اساس ID)،
ساختن چندین Task و DbContext جدید
ممکن است حتی کندتر از حالت ترتیبی شود!
این روش مناسب Query های سنگینتر و پنجرههای بزرگ داده است—not simple queries. ⚙️
🧠 نکتهٔ پایانی
وقتی با یک Dashboard کند مواجه شدید،
قبل از اینکه سراغ SQL دستی یا Stored Procedure بروید…
نگاهی به await های خود بیندازید.
اگر پشتسرهم صف شدهاند،
وقت آن است که کمی فکر موازی انجام دهید. ⚡️
🔖هشتگها:
#EFCore #ParallelProgramming
#AsyncAwait #SoftwareEngineering #IDbContextFactory
Forwarded from Sonora.Dev
🚀 بهینهسازی پروژههای .NET با Directory.Build.props
اگر روی پروژههای چندپروژهای یا بزرگ در .NET کار میکنید، احتمالاً با مشکلاتی مثل:
ناسازگاری نسخهها
تعریف تنظیمات تکراری برای هر پروژه
مدیریت مسیرهای خروجی و تنظیمات کامپایلر
روبرو شدهاید. اینجاست که Directory.Build.props میتواند ناجی شما باشد!
چرا Directory.Build.props مهم است؟
پیکربندی مرکزی پروژهها: میتوانید تنظیمات عمومی مانند نسخه C# (LangVersion)، مسیر خروجی (OutputPath)، Company و Version را یکجا تعریف کنید.
صرفهجویی در زمان: دیگر نیازی به تغییر دستی تنظیمات در هر پروژه نیست.
ثبات و هماهنگی: تمام پروژههای زیرشاخه از این تنظیمات به ارث میبرند، بنابراین رفتار یکسانی خواهند داشت.
کنترل Warningها و Build Options: میتوانید به راحتی فعال یا غیرفعال کردن Warningها و گزینههای کامپایلر را مدیریت کنید.
نکته عملی:
کافیست یک فایل Directory.Build.props در ریشه Solution ایجاد کنید و Propertyهای مشترک را در آن تعریف کنید. این فایل به صورت خودکار روی تمام پروژههای زیرشاخه اعمال میشود و از تکرار کد و ناسازگاری جلوگیری میکند.
💡 استفاده از Directory.Build.props مخصوصاً در تیمهای بزرگ، باعث سادگی، امنیت و ثبات پروژهها میشود و توسعهدهندگان میتوانند روی نوشتن کد تمرکز کنند، نه تنظیمات پروژه.
#DotNet #CSharp #MSBuild #DirectoryBuildProps #SoftwareDevelopment #BestPractices
اگر روی پروژههای چندپروژهای یا بزرگ در .NET کار میکنید، احتمالاً با مشکلاتی مثل:
ناسازگاری نسخهها
تعریف تنظیمات تکراری برای هر پروژه
مدیریت مسیرهای خروجی و تنظیمات کامپایلر
روبرو شدهاید. اینجاست که Directory.Build.props میتواند ناجی شما باشد!
چرا Directory.Build.props مهم است؟
پیکربندی مرکزی پروژهها: میتوانید تنظیمات عمومی مانند نسخه C# (LangVersion)، مسیر خروجی (OutputPath)، Company و Version را یکجا تعریف کنید.
صرفهجویی در زمان: دیگر نیازی به تغییر دستی تنظیمات در هر پروژه نیست.
ثبات و هماهنگی: تمام پروژههای زیرشاخه از این تنظیمات به ارث میبرند، بنابراین رفتار یکسانی خواهند داشت.
کنترل Warningها و Build Options: میتوانید به راحتی فعال یا غیرفعال کردن Warningها و گزینههای کامپایلر را مدیریت کنید.
نکته عملی:
کافیست یک فایل Directory.Build.props در ریشه Solution ایجاد کنید و Propertyهای مشترک را در آن تعریف کنید. این فایل به صورت خودکار روی تمام پروژههای زیرشاخه اعمال میشود و از تکرار کد و ناسازگاری جلوگیری میکند.
💡 استفاده از Directory.Build.props مخصوصاً در تیمهای بزرگ، باعث سادگی، امنیت و ثبات پروژهها میشود و توسعهدهندگان میتوانند روی نوشتن کد تمرکز کنند، نه تنظیمات پروژه.
#DotNet #CSharp #MSBuild #DirectoryBuildProps #SoftwareDevelopment #BestPractices