Developer Advocate
الذهاب إلى القناة على Telegram
1 035
المشتركون
لا توجد بيانات24 ساعات
لا توجد بيانات7 أيام
-130 أيام
أرشيف المشاركات
1 035
راهحلهای LeetCode با استفاده از الگوریتمها و ساختارهای داده در سیشارپ
https://www.udemy.com/course/leetcode-solutions-using-algorithms-and-data-structure-in-c/?srsltid=AfmBOorkoe8S_XiC7mDV1KkvpbbWN19z8TOUFtPLze06foXJ-ClZIwyI
@DeveloperAdvocate 🥑
1 035
آیا واقعاً CAP را زندگی میکنید؟
در دنیای سیستمهای توزیعشده، معمولاً مجبورید بین Consistency (همخوانی) و Availability (دسترسپذیری) انتخاب کنید—مخصوصاً وقتی Partition (تکهتکهشدن شبکه) رخ میدهد. انتخاب CP (همخوانی + تحمل پارتیشن) یعنی از دسترسپذیری موقتاً بگذرید تا دادهها همیشه معتبر بمانند. مثال کلاسیک: سرویس بانکداری آنلاین.
فرض کنید بانک شما از یک پایگاه داده توزیعشده (مانند Cosmos DB با strong consistency) استفاده میکند:
- اگر ارتباط بین دیتاسنترها قطع شود، و کاربری از دیتاسنتر A درخواست برداشت وجه کند، دیتاسنتر B درخواست موجودی را بررسی میکند اما بهدلیل شبکه قطع، نمیتواند آخرین state را چک کند. سیستم CP چه میکند؟ درخواست را reject یا timeout میدهد تا از double-spending جلوگیری شود.
- این یعنی در دوران Partition ترجیح داده شده پاسخ اشتباه ندهد (مثلاً کسی را بیش از موجودی برداشت کند)، اما ممکن است بخشهایی از سرویس موقتاً غیردسترس شوند—نگران عصبانیشدن کاربران نباشید، اما نگران bankruptcy بانک باشید!
نکته اجرایی: در معماری چنین سیستمهایی، مکانیزمهایی مثل
throw new ConsistencyException("Cannot serve request consistently during partition.");
در لایه Application، و Active-Passive Replication برای اطمینان از atomic commit به کار میرود.
CAP فقط یک گزاره تئوریک نیست؛ مهارتی است که باید بسته به domain آن را بهدرستی اعمال کنید. انتخابتان در معماری، جنس رفتار users و حتی آینده شغلی شما را تعیین میکند!
@DeveloperAdvocate 🥑1 035
در .NET 8، کلاس جدیدی به نام
TimeProvider معرفی شده که تستنویسی برای منطقهای وابسته به زمان را به طرز چشمگیری ساده میکند. قبلا هنگام تست واحد، با نبردهایی مثل Mock کردن DateTime.Now یا استفاده از abstrationهای Custom مواجه بودیم. اما حالا میتوانید به راحتی زمان را کنترل و شبیهسازی کنید. مثال زیر را در نظر بگیرید:
فرض کنید متدی داریم که هرگاه یک ساعت از آخرین فعالیت گذشته باشد، Session را Expire میکند:
public class SessionManager
{
private readonly TimeProvider _timeProvider;
private DateTimeOffset _lastActivity;
public SessionManager(TimeProvider timeProvider)
{
_timeProvider = timeProvider;
_lastActivity = _timeProvider.GetUtcNow();
}
public void UpdateActivity() =>
_lastActivity = _timeProvider.GetUtcNow();
public bool IsSessionExpired()
=> _timeProvider.GetUtcNow() - _lastActivity > TimeSpan.FromHours(1);
}
در تست واحد، کافیست از FakeTimeProvider استفاده کنید و زمان را به دلخواه جابجا کنید:
using Microsoft.Extensions.Time.Testing;
var fakeTimeProvider = new FakeTimeProvider();
fakeTimeProvider.SetUtcNow(DateTimeOffset.Parse("2024-06-01T12:00:00Z"));
var sessionManager = new SessionManager(fakeTimeProvider);
sessionManager.UpdateActivity();
// زمان را یک ساعت و یک دقیقه جلو میبریم:
fakeTimeProvider.Advance(TimeSpan.FromMinutes(61));
Assert.True(sessionManager.IsSessionExpired());
حالا تست شما کاملاً دترمینیستیک، سریع و مستقل از زمان واقعی اجرا خواهد بود. با این رویکرد، بدون پیچیدگی و Dependencyهای اضافه، هر منطق وابسته به زمان را Behavior-driven تست کنید.
نکته: TimeProvider در اکثر سرویسهای جدید .NET قابل تزریق است! دینامیک بودن زمان در تستهای شما، حالا فقط چند خط کد فاصله دارد.
@DeveloperAdvocate 🥑1 035
✳️ درک Event Loop جاوااسکریپت برای معماران بکاند
اگر از داتنت یا پلتفرمهایی مثل جاوا آمدهاید، شاید انتظار دارید که هر درخواستی یک Thread مخصوص خود داشته باشد؛ اما در Node.js، مدل event-driven و event loop جایگزین آن شده است.
در Node.js فقط یک Thread برای اجرای جاوااسکریپت وجود دارد. هر بار که عملیاتی async (مثل I/O یا timer) شروع میکنید، کنترل به event loop بازمیگردد. عملیات async توسط worker threads یا libuv هندل میشود و پس از اتمام، callback در صف عملیات یا همان "callback queue" قرار میگیرد.
event loop مسئول بررسی این صف است و هرگاه stack جاوااسکریپت خالی باشد، یک callback را اجرا میکند.
این مدل دارای چند ویژگی مهم برای بکاند است:
- مقیاسپذیری بالا: چون برای هزاران اتصال فقط یک Thread دارید، هزینه context switch و memory کاهش مییابد.
- عدم بلوکاژ: تا زمانی که عملیات سنگین CPU انجام نمیدهید، کل اپلیکیشن پاسخگو است.
- پیچیدگی مدیریت concurency: برخلاف مدل thread-based، race condition فقط در دیتابیس یا resourceهای shared رخ میدهد، نه در خود جاوااسکریپت؛ چون همه کد JS روی یک stack اجرا میشود.
یک مثال معادل بین C# و جاوااسکریپت را ببینید:
// C# - هر درخواست به طور معمول یک Thread میگیرد
public async Task<IActionResult> Index()
{
var result = await dbContext.Users.ToListAsync();
return View(result);
}
و معادل Node.js:
// Node.js - تمام درخواستها توسط event loop با غیرهمزمانی اجرا میشود
app.get('/users', async (req, res) => {
const result = await db.users.find().toArray();
res.json(result);
});
نکته معمارانه: اگر نیاز به محاسبات سنگین دارید، آن را به یک process جدا یا Worker Thread بیندازید تا event loop block نشود. اما برای کارهای I/O محور (شبکه، فایل، DB)، Node.js یک رویکرد مینیمال و مقیاسپذیر ارائه میدهد.
پس: event loop در Node.js تنها یک Thread دارد که با اطمینان از عدم بلوکه شدن، قابلیت سرویسدهی به هزاران connection همزمان را فراهم میکند—کاملاً متفاوت با مدلهای multi-threaded کلاسیک!
@DeveloperAdvocate 🥑1 035
💎 معماری هگزاگونال در سرویسهای .NET: ساختار صحیح، انفصال واقعی
معماری Hexagonal (یا Ports & Adapters) مُدرنی برای جداسازی هسته دامنه (Core Domain) از جهان بیرون است. در .NET، به جای لایهبندی سنتی (مثل n-tier)، هستهی دامنه را در مرکز قرار دهید و کل وابستگیهای زیرساخت (درگاهها، مخازن، سرویسهای خارجی و ورودی/خروجی) را از طریق اینترفیسها کنترل کنید.
📁 ساختار پیشنهادی پروژه:
-
/Domain: مدلها، اینترفیسهای سرویسها و منطق هسته
- /Application: سرویسهای کاربردی، DTOها، منطق use-case
- /Infrastructure: پیادهسازی پورتها (مثلاً EF, Http, FileSystem)
- /Api: ورودی سمت وب (Controllers)
مثال پایه برای تعریف پورت (Port) و آداپتر (Adapter):
// پورت (اینترفیس استخراجشده در هسته)
namespace MyApp.Domain.Ports
{
public interface ICustomerRepository
{
Customer? GetById(Guid id);
void Save(Customer customer);
}
}
پیادهسازی آداپتر زیرساختی
// آداپتر Entity Framework در لایه Infrastructure
namespace MyApp.Infrastructure.Repositories
{
public class EfCustomerRepository : ICustomerRepository
{
private readonly MyDbContext _context;
public EfCustomerRepository(MyDbContext context) =>
_context = context;
public Customer? GetById(Guid id) =>
_context.Customers.Find(id);
public void Save(Customer customer)
{
_context.Update(customer);
_context.SaveChanges();
}
}
}
ورود از لایه خارجی (Application Service):
// سرویس کاربردی که فقط پورت میشناسد
namespace MyApp.Application.Services
{
public class CustomerService
{
private readonly ICustomerRepository _repo;
public CustomerService(ICustomerRepository repo) =>
_repo = repo;
public CustomerDto? GetCustomer(Guid id)
{
var customer = _repo.GetById(id);
return customer == null
? null
: new CustomerDto { Id = customer.Id, Name = customer.Name };
}
}
}
✳️ تمام وابستگیها وارونه هستند: هسته Domain حتی اسم EntityFramework یا وب یا هر تکنولوژی بیرونی را نمیداند.
🔑 نکته مهم: هنگام رجیستر کردن DI در startup فقط آداپترهای زیرساخت را bind کنید:
services.AddScoped<ICustomerRepository, EfCustomerRepository>();
services.AddScoped<CustomerService>();
این ساختار هکزاگونال قابلیت تست، نگهداری و انفصال تکاملی کد را به اوج میرساند. برای تخصصیتر شدن، حتی Controllerها را Adapter درنظر بگیرید تا API و پیامرسانها فقط از طریق پورت با هسته حرف بزنند.
@DeveloperAdvocate 🥑1 035
🌱 توسعه مبتنی بر تست (TDD): چرا و چگونه؟
برای مهندسانی که دنبال چرخه توسعه سالم و کدنویسی اطمینانبخش هستند، TDD فقط یک سبک توسعه نیست، بلکه یک قرارداد ذهنی برای طراحی سیستمهای انعطافپذیر و بدون بدهی فنی است. بیایید با یک workflow ساده و کاربردی، قدرت TDD را احساس کنیم:
۱. نوشتن تست شکستخورده (Red):
مسئله را خرد کنید و اولین تست را بنویسید که منطق موردنیاز هنوز پیادهسازی نشده است؛ این تست باید شکست بخورد و دلیلی روشن داشته باشد.
public class CalculatorTests
{
[Fact]
public void Add_ShouldReturnSumOfTwoNumbers()
{
var calculator = new Calculator();
Assert.Equal(5, calculator.Add(2, 3));
}
}
۲. پیادهسازی حداقلی برای قبولی تست (Green):
کوچکترین پیادهسازی ممکن که فقط تست را سبز کند را ایجاد کنید؛ نه کمتر، نه بیشتر.
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
۳. بازسازی و پاکسازی (Refactor):
حال، ساختار و خوانایی کد را بهتر کنید؛ بدون اینکه رفتار کد تغییر کند یا تستی قرمز شود. به Dependency Injection، SOLID، و DRY فکر کنید.
۴. تکرار:
سیکل Red-Green-Refactor را برای هر فیچر یا گام کوچک تکرار کنید. هر تست، یک پیشنیاز برای گسترش و اعتمادپذیری بالا است.
🌟 نکته:
با اجرای جدی TDD، معماری شما ناخواسته ماژولار و قابل تست میشود. Dependencyها شفافتر و سطح coupling به حداقل میرسد.
در پروژههای بزرگ، صرفهجویی زمان در رفع regression و طراحی emergent، واقعاً محسوس است.
🔗 اگر تجربهای از اجرای موفق یا چالشهای خاص با TDD دارید، همینجا کامنت بگذارید تا گفتوگو را عمیقتر کنیم.
@DeveloperAdvocate 🥑1 035
🎯 Feature Flags: راز موفقیت در دیپلویمنت بیریسک و Canary Release
در سامانههای مقیاسپذیر و پراهمیت، فعالسازی فیچرهای جدید بدون ریسک نیاز به رویکردی ساختاریافتهتر از "دیپلوی و دعا" دارد! Feature Flagها (یا Feature Toggles) دقیقاً ابزاری هستند که امکان rollout تدریجی، ریسکزدایی و حتی rollback بدون redeploy را مهیا میکنند.
### 🌱 ساختار پیشنهادی: الگوی Enum-Based Feature Flag
فرض کنید یک سیستم API توسعه دادهاید و میخواهید بخشی از ترافیک را به ویژگی جدید هدایت کنید. اول، Featureهای خود را تعریف کنید:
public enum Feature
{
NewCheckoutFlow,
AdvancedDiscountEngine,
RealtimeNotification
}
یک سرویس مرکز برای مدیریت Feature Flagها طراحی کنید:
public interface IFeatureFlagService
{
bool IsEnabled(Feature feature, string userId = null);
}
یک پیادهسازی ساده که مبتنی بر توزیع کاربران برای canary release است:
public class CanaryFeatureFlagService : IFeatureFlagService
{
private readonly Dictionary<Feature, int> _canaryPercentage = new()
{
{ Feature.NewCheckoutFlow, 10 }, // ۱۰٪ از کاربران feature جدید را میبینند
};
public bool IsEnabled(Feature feature, string userId = null)
{
if (!_canaryPercentage.TryGetValue(feature, out var percentage) || userId == null)
return false;
// Hash کردن userId برای توزیع برابر
int bucket = Math.Abs(userId.GetHashCode()) % 100;
return bucket < percentage;
}
}
### 🚦 استفاده موثر در منطق اپلیکیشن
بهسادگی فیچر را شرطی کنید و rollout تدریجی داشته باشید:
if (_featureFlagService.IsEnabled(Feature.NewCheckoutFlow, user.Id))
{
// منطق جدید checkout
}
else
{
// مسیر قدیمی
}
### 🧠 نکات معماری
- اگر قابلیت rollback فوری میخواهید، feature flag را از storage سریع قابل تغییر مثل Redis یا حتی فایل config قابل hot reload بخوانید.
- با ابزارهایی همچون LaunchDarkly، Azure App Configuration و Unleash میتوانید feature flagهای enterprise-grade با تحلیل audit trail، targeting پیشرفته و dashboardهای real-time داشته باشید.
- بهتر است هر feature flag برای maximum یک یا دو release زنده بماند تا کد منسوخ نشده و complexity کنترل شود.
🔍 تجربهٔ شما چیست؟ کدام ابزار یا الگو برای feature flags و canary rollout به نظرتان بهترین است؟
@DeveloperAdvocate 🥑1 035
🎯 یک مشکل رایج دسترسپذیری (a11y): دکمههایی که فقط با آیکون نمایش داده میشوند و برای کاربران نابینا یا کمبینا فاقد توضیح معنایی هستند. فرض کنید یک دکمه حذف با آیکون سطل آشغال داریم:
<button onClick={handleDelete}>
<TrashIcon />
</button>
❌ این دکمه برای کاربران screen reader بیمعناست، چون برچسب متنی ندارد. راهحل اصولی: استفاده از ARIA attributes (مثلاً aria-label) برای توصیف عملکرد دکمه. به علاوه، برای حفظ فوکوس مناسب باید type را هم مشخص کنیم:
<button
type="button"
aria-label="حذف آیتم"
onClick={handleDelete}
>
<TrashIcon aria-hidden="true" />
</button>
🔥 توضیحات:
- خاصیت aria-label معنی دکمه را به صورت متنی برای فناوریهای کمکی مشخص میکند.
- وقتی تنها یک آیکون داخل دکمه داریم، حتماً روی آیکون aria-hidden="true" بگذارید تا تکرار نشود.
- همواره نوع دکمه را با type="button" مشخص کنید تا داخل فرم باعث ارسال ناخواسته نشود.
💡 نکته تکمیلی: اگر دکمه حالتی دارد (مثلاً فعال/غیرفعال بودن یا سوییچ)، از خصیصههایی مثل aria-pressed یا aria-checked هم کمک بگیرید تا وضعیت به کاربر اعلام شود. این نوع ریزهکاریها تفاوت بین یک UI معمولی و یک UI واقعاً حرفهای و قابلدسترس را رقم میزنند.
@DeveloperAdvocate 🥑1 035
🔹 چگونه یک Tech Talk پربازده ۱۵ دقیقهای برگزار کنیم؟
۱. سه دقیقه اول: معضل و دامنه
در همان ابتدا، صورت مسأله را شفاف و مختصر بیان کنید (مثلاً "چطور dependency injection را بدون sacrificial architecture پیاده کنیم؟"). حتماً اهمیت آن برای پروژههای واقعی و کلیدیترین مثالها را ذکر کنید. استفاده از یک سناریوی واقعی تیم خودتان، شنونده را سریعاً درگیر میکند.
۲. پنج دقیقه میانی: راهکار فنی
اصل صحبت را با یک چارچوب شفاف جلو ببرید:
- راهحلهای مرسوم را نام ببرید و ناکارآمدیهایشان را به زبان دقیق فنی توضیح دهید (مثلاً، anti-patternهای معمول در DI).
- سپس رویکرد اصلی خود را مرحله به مرحله پیچ کنید. در هر گام، reference به مواردی مانند SOLID، DDD یا حتی الگوهای پیشرفته مثل Composite Root بدهید.
- یک نمونه کد خلاصه، ولی عمیق نمایش دهید تا مزیت راهکار ملموس شود:
public interface IService { void Execute(); }
public class LoggingService : IService
{
private readonly ILogger _logger;
public LoggingService(ILogger logger) => _logger = logger;
public void Execute()
{
_logger.Log("Executing service...");
// Logic here
}
}
// In Startup.cs
services.AddSingleton<IService, LoggingService>();
اشاره کنید چرا این Construction Root امنتر و قابل تستتر از معرفی Dependencies به صورت پراکنده است.
۳. سه دقیقه جمعبندی و صیقل
یافتهها، ریسکها و نتایج را مرور کنید (مانند trade-off بین testability و complexity).
اگر تجربیات failure یا success در پروژههای کلان داشتهاید، همینجا اشاره کوتاه کنید.
توصیه عملی و شاخص (Actionable Takeaway) بدهید: مثلاً "همیشه Composition Root را متمرکز و قابل ارزیابی نگه دارید؛ نه فقط به خاطر Clean Code، بلکه برای کنترل هزینه تغییرات معماری."
۴. چهار دقیقه برای Q&A
حتماً یک سؤال چالشی مطرح کنید (مثلاً "اگر کاربران نیازمند dynamic resolution شوند، چطور Extension Point بسازیم؟") تا تعامل و عمیقترشدن بحث را تضمین کنید.
نکته حرفهای: از Slides مینیمال با Diagram و کد حداکثر استفاده کنید. کل talk با سه اسلاید هم اثربخشتر است!
یادتان باشد: تکنیک، تمرکز و تعامل؛ سه پایه یک Tech Talk فراموشنشدنی.
@DeveloperAdvocate 🥑1 035
📚 مقایسه مدیریت State در React: Redux Toolkit در برابر Zustand
در دنیای React، انتخاب معماری مدیریت state، تأثیر عمیقی بر خوانایی، مقیاسپذیری و تجربه کار توسعهدهنده دارد. بیایید نگاهی عمیق بیندازیم به دو رویکرد متفاوت: Redux Toolkit (یک چارچوب کلاسیک اما بهبود یافته) و Zustand (یک کتابخانه مدرن با API ساده و مدل mental متفاوت).
⬅️ Redux Toolkit
یک فروشگاه مرکزی و معماری اکوسیستمی بر پایه reducer/dispatch:
import { configureStore, createSlice } from '@reduxjs/toolkit';
// تعریف اسلایس به صورت مدرن
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1 },
decrement: state => { state.value -= 1 },
},
});
export const { increment, decrement } = counterSlice.actions;
export const store = configureStore({ reducer: { counter: counterSlice.reducer } });
// در کامپوننت
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
const value = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
✅ مزایا:
- اینرشیپ معماری قوی، اکوسیستم بالغ، ابزارهای دیباگینگ عالی (Redux Devtools)، پشتیبانی از Middleware.
- مناسب برای اپلیکیشنهای بزرگ و تیمهای بزرگ با نیاز به قابلیت ردگیری state و history.
⚡ Zustand
نگاهی متفاوت؛ بدون provider، State-های آبجکتیو بدون boilerplate:
import { create } from 'zustand';
// تعریف Store ساده
const useCounterStore = create(set => ({
value: 0,
increment: () => set(state => ({ value: state.value + 1 })),
decrement: () => set(state => ({ value: state.value - 1 })),
}));
// در کامپوننت
function Counter() {
const { value, increment, decrement } = useCounterStore();
return (
<div>
<span>{value}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
✨ مزایا:
- بدون نیاز به Provider؛ کاملاً hooks-based؛ کد بسیار کم و قابل قیاس با useState.
- قابلیت استفاده برای اشتراک state بین بخشهای مختلف اپلیکیشن با حداقل ceremony.
- مناسب برای پروژههای سریع یا مواقعی که به پیچیدگی Redux نیازی ندارید.
🔬 جمعبندی تکنیکی:
- اگر مقیاس، قابلیت دیباگ و ساختار تیمی بزرگ دارید یا نیاز به اکوسیستم Middleware، سراغ Redux Toolkit بروید.
- اگر سادگی، انعطاف و سبک بودن و کمترین Boilerplate اولویت است، Zustand میتواند paradigm جذابتری ارائه کند.
👑 در نهایت، معماری State شما باید بازتابدهنده بافت تیم و الزامات بیزینسی باشد—نه صرفاً مد روز!
@DeveloperAdvocate 🥑1 035
✨ کاربردهای پیشرفته Tuple در #CSharp — از Deconstruction تا کلیدگذاری در Dictionary
در داتنت، تاپلها نه فقط برای برگرداندن چند مقدار از متدها، بلکه برای تحلیل و مدلسازی دادههای ترکیبی پیچیده کاربردی هستند. بیایید چند سناریوی پیشرفته را بررسی کنیم:
🔹 Deconstruction پیشرفته
تاپلها از قابلیت deconstruction به صورت قدرتمند پشتیبانی میکنند؛ حتی در ترکیب با pattern matching و variable discards:
(string Name, int Score, DateTime When) GetUserScore() => ("Ali", 92, DateTime.Now);
// فقط دو مقدار اول را میگیریم، بقیه را دور میاندازیم
var (name, score, _) = GetUserScore();
Console.WriteLine($"{name} scored {score}");
همچنین میتوانید deconstruction را مستقیم بر روی مقادیر dictionary اجرا کنید:
var dict = new Dictionary<(int Id, string Locale), string>();
dict[(42, "fa-IR")] = "علی";
foreach (var ((id, locale), value) in dict)
{
Console.WriteLine($"{id} | {locale} => {value}");
}
🔸 تاپل به عنوان کلید Dictionary
استفاده از تاپل به عنوان کلید، راهی تمیز، type-safe و سریع برای ایجاد mappingهای چندبعدی است:
var cache = new Dictionary<(int UserId, int Year, int Month), double>();
cache[(12, 2024, 6)] = 893.50;
// جستجوی سریع
var spent = cache.TryGetValue((12, 2024, 6), out var v) ? v : 0;
با توجه به این که ValueTuple به صورت خودکار Equality/HashCode را در خود دارد، پیادهسازی equality پیچیده لازم نیست، و mappingها بسیار کاراتر از anonymous type (یا کلاس شخصی) خواهند بود.
📎 نکته پیشرفته: اگر به کلیدگذاری مرکب بیشتر نیاز دارید (مثلاً با بیش از چهار مقدار کلیدی)، میتوانید تاپلهای تودرتو بسازید یا مستقیماً از (…, …, …, …, …) تا هفتتایی استاندارد استفاده کنید.
✨ تاپلها، در سطح پیشرفته، ابزار modeling داده کمنظیر برای معماری مدرن سیستمهای #dotnet هستند.
@DeveloperAdvocate 🥑1 035
✨ وقتی مدیریت منابع Native با Asynchrony تلاقی میکند: استفاده پیشرفته از
IAsyncDisposable در #CSharp
در .NET، اینترفیس IAsyncDisposable برای حذف منابع بهصورت غیرهمزمان (asynchronous) طراحی شده است. رویکرد فوقالعادهای برای وقتی که Dispose منابع سنگین (مثل هندلهای File یا Database connection) طولانیمدت یا نیازمند عملیات async باشند.
در این مثال، فرض کنید کلاس شما با یک Native resource کار میکند که باید با P/Invoke آزاد شود و قبل از آزادسازی نیازمند عملیات async (مثل فلش کردن دادهها به دیسک) است:
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
public class AdvancedUnmanagedResource : IAsyncDisposable
{
private IntPtr _nativeHandle;
private FileStream? _logStream;
private bool _disposed;
public AdvancedUnmanagedResource(string logFilePath)
{
// Native allocation (فرضی)
_nativeHandle = UnsafeNativeMethods.OpenNativeResource();
// یک stream برای لاگ گیری فعالیتهای resource
_logStream = new FileStream(logFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 4096, useAsync: true);
}
public async ValueTask DisposeAsync()
{
if (_disposed) return;
// پیش از آزادسازی resource، لاگ را flush کن (op async)
if (_logStream != null)
{
await _logStream.FlushAsync();
await _logStream.DisposeAsync();
}
// آزاد سازی resource native (فرضی، sync چون پشتیبانی async ندارد)
if (_nativeHandle != IntPtr.Zero)
{
UnsafeNativeMethods.CloseNativeResource(_nativeHandle);
_nativeHandle = IntPtr.Zero;
}
_disposed = true;
GC.SuppressFinalize(this);
}
~AdvancedUnmanagedResource()
{
// fallback به Dispose sync برای مواقعی که DisposeAsync فراموش شده باشد
if (!_disposed && _nativeHandle != IntPtr.Zero)
{
UnsafeNativeMethods.CloseNativeResource(_nativeHandle);
}
}
private static class UnsafeNativeMethods
{
[DllImport("MyNativeLib.dll")]
public static extern IntPtr OpenNativeResource();
[DllImport("MyNativeLib.dll")]
public static extern void CloseNativeResource(IntPtr handle);
}
}
🔥 نکات مهم:
- حتما state _disposed را بررسی و مدیریت کنید تا resource دوبار آزاد نشود.
- اگر resource کاملا async نیست (مثل Native handle)، بخش sync را همچنان در DisposeAsync بگذارید.
- ~AdvancedUnmanagedResource() باید همیشه راه آخر باشد؛ rely بر Finalizer نکنید!
- استفاده از await using تضمین میکند DisposeAsync در پایان scope فرابخوانی شود:
await using var resource = new AdvancedUnmanagedResource("log.txt");
این الگو را میتوانید برای تعامل صحیح و تمیز با unmanaged resourceها (بهویژه وقتی نیاز به عملیات async دارید) در سرویسهای high-throughput یا microservices بهکار ببرید.
@DeveloperAdvocate 🥑1 035
فرق ETL و ELT در معماری دادهای:
اگر با معماری دادههای بزرگ یا Data Warehouse کار میکنید، حتماً با دو رویکرد ETL (Extract, Transform, Load) و ELT (Extract, Load, Transform) مواجه شدهاید. تفاوت کلیدی آنها به محل و ترتیب انجام Transform برمیگردد:
🔹 ETL:
ابتدا دادهها از منبع استخراج (Extract) میشوند، سپس خارج از دیتابیس مقصد (معمولاً توسط SSIS، Spark، یا یک برنامه C# اختصاصی) تبدیل (Transform) میشوند و در نهایت بارگذاری (Load) به دیتابیس یا Data Warehouse مقصد صورت میگیرد.
سناریوی مناسب ETL:
- زمانی که منبع داده، دادههایی با کیفیت پایین یا با ساختاری ناسازگار دارد.
- نیاز به تغییرات پیچیده یا Cleansing بیرون از دیتابیس مقصد.
- Data Warehouse بر روی دیتابیسی با قدرت پردازش پایین (مثلاً SQL Server معمولی) اجرا میشود.
🔹 ELT:
در این روش، پس از استخراج، دادهها مستقیماً و به سرعت به Data Warehouse یا دیتابیس مقصد (مانند Azure Synapse, Snowflake یا Google BigQuery) Load میشوند و عملیات Transform توسط همان دیتابیس و با قدرت پردازشی آن (distributed processing) انجام میشود.
سناریوی مناسب ELT:
- دیتابیس مقصد یک محاسباتی Columnar قدرتمند دارد و حجم دادهها بسیار بالاست.
- نیاز به پردازش همزمان/انبوه و اجرای Queryهای پیچیده نزدیک به داده وجود دارد.
- فاز Transform شامل Aggregation/Joinهای سنگین است که در دیتابیس مقصد بسیار سریعتر انجام میشوند.
🔹 یک مثال عملی با C# (Abstracted Sample on Azure):
public async Task RunEtlAsync()
{
// ETL Sample: Transform قبل از Load
var sourceData = await ExtractFromApiAsync();
var cleansed = CleanAndConvert(sourceData); // Transform in App
await LoadToSqlAsync(cleansed);
}
public async Task RunEltAsync()
{
// ELT Sample: Load خام به Data Warehouse، سپس Transform با SQL
var rawData = await ExtractFromApiAsync();
await LoadToSynapseRawTableAsync(rawData); // Fast bulk load
// حالا Transformation با یک دستور SQL
await SynapseDb.ExecuteAsync(@"
INSERT INTO curatedData([fields...])
SELECT TRANSFORM_LOGIC([fields...]) FROM rawData
");
}
✅ جمعبندی معماری:
تا زمانی که Data Warehouse شما قدرت پردازش بالا نداشته باشد یا Quality/Cleansing داده حیاتی باشد، ETL انتخاب بهتری است. اما وقتی با دنیای کلانداده و دیتابیسهای column-store مدرن کار میکنید، رویکرد ELT (و نه ETL) را انتخاب کنید تا از توزیع بار پردازش و انعطاف دیتابیسهای تحلیلی بهرهمند شوید.
@DeveloperAdvocate 🥑1 035
الگوی Cache-Aside (یا Lazy-Loading) یکی از پراستفادهترین استراتژیهای کشینگ است، مخصوصاً در معماریهای distributed. در این الگو، اپلیکیشن کنترلگر اصلی داده است: ابتدا درخواست داده را مستقیماً از کش میدهد. اگر داده در کش وجود نداشت (cache miss)، از دیتابیس خوانده، به کش اضافه میکند و سپس به کلاینت بازمیگرداند. برتری اصلی این الگو سادگی و انعطافپذیری آن است: کش و استوریج اصلی loosely-coupled باقی میمانند، و احتمال staleness نسبتاً پایین است.
یک پیادهسازی کلاسیک Cache-Aside با Redis و EF Core را به سبک idiomatic در .NET مشاهده کنید:
public class ProductService
{
private readonly IDatabase _redis;
private readonly DbContext _db;
public ProductService(IDatabase redis, DbContext db)
{
_redis = redis;
_db = db;
}
public async Task<Product> GetProductAsync(int id, CancellationToken ct = default)
{
string cacheKey = $"product:{id}";
string cached = await _redis.StringGetAsync(cacheKey);
if (cached != null)
{
// کش hit
return JsonSerializer.Deserialize<Product>(cached);
}
// کش miss
var product = await _db.Set<Product>().FindAsync(new object[] { id }, ct);
if (product is null) return null;
// کش را populate میکنیم (مثلاً ۵ دقیقه TTL)
await _redis.StringSetAsync(
cacheKey,
JsonSerializer.Serialize(product),
TimeSpan.FromMinutes(5)
);
return product;
}
}
🔹 نکات پیشرفته:
- برای جلوگیری از Cache Stampede، اگر انتظار دارید که دادهی موردنظر به شدت درخواست خواهد شد، میتوانید lockهای distributed (مثل RedLock) یا الگوی request coalescing پیادهسازی کنید.
- افزودن کانال invalidation (مثلاً با Pub/Sub Redis) تضمین میکند که کش در صورت تغییر داده در دیتابیس invalidate شود.
این الگو برای سناریوهای read-heavy به شدت توصیه میشود و پیادهسازی آن با .NET و Redis فوقالعاده ساده و خوشساختار است.
@DeveloperAdvocate 🥑1 035
در معماری سرویسهای مدرن، مدیریت نرخ درخواستها (Rate Limiting) حیاتی است تا از سوء استفاده و بارگیری بیشازحد سرور جلوگیری شود. در .NET 7 و بالاتر، Rate Limiting به صورت داخلی در ASP.NET Core ارائه شده، اما اگر مایلید یک Middleware سفارشی و قابلگسترش برای سناریوهای خاص خود پیاده کنید، معماری پیشنهادی زیر ارزش بررسی دارد:
فرض کنیم میخواهیم برای هر IP، اجازه بدهیم حداکثر ۱۰۰ درخواست در هر بازه ۱۵ دقیقهای ارسال شود. یک پیادهسازی ساده مبتنی بر Sliding Window Counter:
using System.Collections.Concurrent;
public class RateLimitingMiddleware
{
private readonly RequestDelegate _next;
private readonly int _maxRequests;
private readonly TimeSpan _window;
private static readonly ConcurrentDictionary<string, SlidingWindowCounter> _counters = new();
public RateLimitingMiddleware(RequestDelegate next, int maxRequests, TimeSpan window)
{
_next = next;
_maxRequests = maxRequests;
_window = window;
}
public async Task InvokeAsync(HttpContext context)
{
var key = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
var counter = _counters.GetOrAdd(key, _ => new SlidingWindowCounter(_window));
if (!counter.GrantAccess(_maxRequests))
{
context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
await context.Response.WriteAsync("Rate limit exceeded. Try again later.");
return;
}
await _next(context);
}
}
public class SlidingWindowCounter
{
private readonly TimeSpan _window;
private readonly Queue<DateTime> _accessTimestamps = new();
private readonly object _lock = new();
public SlidingWindowCounter(TimeSpan window)
{
_window = window;
}
// اجازه/عدم اجازه برای یک درخواست جدید
public bool GrantAccess(int maxRequests)
{
lock (_lock)
{
var now = DateTime.UtcNow;
while (_accessTimestamps.Count > 0 && now - _accessTimestamps.Peek() > _window)
_accessTimestamps.Dequeue();
if (_accessTimestamps.Count >= maxRequests)
return false;
_accessTimestamps.Enqueue(now);
return true;
}
}
}
استفاده در Startup.cs یا Program:
app.UseMiddleware<RateLimitingMiddleware>(100, TimeSpan.FromMinutes(15));
✅ این معماری کاملاً قابلگسترش است (مثلاً میتوانید وابستگیهای وزنی، سیاستهای مختلف، یا توزیع دادهها روی cacheهای توزیعشده مثل Redis را اضافه کنید).
✅ مناسب سناریوهایی است که مایل باشید کنترل کامل روی رفتار Rate Limiting و storage آن داشته باشید.
نکتهٔ حرفهای: اگر نیاز به همسانسازی بین چند instance دارید، sliding window را روی Redis با Sorted Set پیادهسازی کنید تا consistency تضمین شود.
#RateLimiting #DotNet #Middleware #Architecture
@DeveloperAdvocate 🥑1 035
🔹 Database Sharding عمیق: مقایسه Range-Based و Hash-Based
در معماری سیستمهای مقیاسپذیر، شاردینگ دیتابیس راهکاری حیاتی برای مدیریت حجم عظیم داده است. انتخاب صحیح استراتژی شاردینگ، تاثیر مستقیمی بر کارایی، تعادل بار و پیچیدگی پیادهسازی دارد.
👈 مثال: فرض کنید کاربران یک سرویس با دهها میلیون رکورد داریم و قصد داریم دیتابیس SQL را شارد کنیم.
◽ شاردینگ Range-Based (شاردینگ بازهای):
در این روش هر شارد، محدودهای از مقادیر کلید اصلی (مثلاً UserId) را نگهداری میکند.
- مثال: شارد ۱ کاربران ۱۰۰۰۰۰۰-۰، شارد ۲ کاربران ۲۰۰۰۰۰۰-۱۰۰۰۰۰۰ و ...
- مناسب برای پرسوجوی Range (مثلاً بین UserId X تا Y)
🔑 چالش: اگر توزیع داده نابرابر باشد (مثلاً UserId به صورت افزایشی تولید میشود)، برخی شاردها بیش از بقیه بار خواهند گرفت (Hot Shard).
◽ شاردینگ Hash-Based:
در این تکنیک، با یک تابع hash (مثلاً mod تعداد شاردها)، رکوردها به صورت یکنواخت روی شاردها پخش میشوند.
public int GetShardForUser(Guid userId, int shardCount)
{
return Math.Abs(userId.GetHashCode()) % shardCount;
}
- مزیت: توزیع یکنواخت داده و بار بین شاردها.
- عیب: پرسوجوهای بازهای عملاً باید روی تمام شاردها اجرا شوند (Scatter-Gather).
💡 نکتههای حرفهای:
- اگر Workload شما عمدتاً مبتنی بر بازه (Range Queries) است، Range-Based مناسبتر است؛ با مراقبت از Hot Spot و معماری Rebalancing پویای شاردها.
- اگر حجم داده بسیار متغیر است و توزیع جغرافیایی یا کسبوکاری ندارید، Hash-Based به طور محسوسی مقیاسپذیرتر و Balancing-Friendly است.
⌛ تصمیمگیری درست شاردینگ، نیازمند تحلیل دقیق الگوی دسترسی و رشد دادهها است — و در صورت نیاز، پیادهسازی مسیریابی هوشمند یا Abstraction Layer برای جداسازی لایه اپلیکیشن از تنظیمات شاردینگ.
@DeveloperAdvocate 🥑1 035
🔒 مقایسه عمیق دو فلو کلیدی OAuth 2.0: Authorization Code و Client Credentials
در معماریهای مدرن مبتنی بر API، انتخاب درست فلو احراز هویت، تضمینگر امنیت و تجربه توسعه بهتر است. تمرکز بر دو فلو پرکاربرد:
۱️⃣ Authorization Code Flow
- سناریو: زمانی که یک کاربر انسانی میخواهد به منابع خود در سرویسهای ثالث دسترسی پیدا کند (مثلاً برنامه وب یا موبایل).
- بازیگران: Client (برنامه)، Authorization Server، Resource Owner (کاربر انسانی).
- امنیت: تبادل کد authorization به جای توکن مستقیماً، ریسک افشای توکن را در کلاینت افزایش میدهد. استفاده از PKCE حیاتی است، مخصوصاً در SPA و موبایل.
- Tokenها: قابلیت دریافت Refresh Token برای تمدید سشن، مناسب برای کلاینتهایی که به تعامل طولانی با کاربر نیاز دارند.
// استفاده با OpenIddict یا IdentityServer در ASP.NET Core
services.AddAuthentication(options => { ... })
.AddOAuth("MyOAuth", options =>
{
options.ClientId = "...";
options.ClientSecret = "...";
options.AuthorizationEndpoint = ".../authorize";
options.TokenEndpoint = ".../token";
options.CallbackPath = "/signin-oauth";
options.SaveTokens = true;
// PKCE فعالسازی شود
});
۲️⃣ Client Credentials Flow
- سناریو: سرویس-به-سرویس، بدون دخالت مستقیم کاربر انسانی؛ مثلاً ماکروسرویسها یا jobهای background.
- بازیگران: فقط Client و Authorization Server.
- امنیت: چون هیچ کاربری وجود ندارد، token همیشه با هویت کلاینت (application identity) صادر میشود، پس کلمات عبور کلاینت (Client Secret) باید بهخوبی حفاظت شوند (مثلاً در Azure Key Vault یا HashiCorp Vault).
- Tokenها: تنها Access Token گرفته میشود؛ Refresh Token صادر نمیشود. مناسب برای عملیات کوتاهمدت.
// درخواست توکن با HttpClient
var client = new HttpClient();
var dict = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", "service-api"},
{"client_secret", "your-secret"},
{"scope", "orders.read"}
};
var response = await client.PostAsync("https://auth.server/connect/token", new FormUrlEncodedContent(dict));
var result = await response.Content.ReadFromJsonAsync<TokenResponse>();
🔑 خلاصه حکمرانی:
- اگر کاربر انسانی دارید و تجربه کاربری، امنیت و قابلیت refresh مهم است، Authorization Code Flow را با PKCE و اصول امنیتی توصیهشده انتخاب کنید.
- اگر تعامل بین سرویسها با هویت مستقل از کاربر دارید، Client Credentials Flow کمریسکتر، ساده و بهینه است.
در هر دو حالت، اصول Least Privilege و رمزنگاری Secrets را جدی بگیرید. تجربه شما در انتخاب این فلوها چیست؟
@DeveloperAdvocate 🥑1 035
وقتی صحبت از Dependency Injection در .NET میشود، تزریق توابع (Func-injection) راهکاری توانمند برای lazy resolution یا forward dependencies است. این الگو به ویژه در تستپذیری و کاهش coupling موثر است. مثالی تمیز برای استفاده از Func<T>:
public interface ITransactionService
{
void Execute();
}
public class TransactionService : ITransactionService
{
public void Execute()
{
// عملیات تراکنش
}
}
public class PaymentProcessor
{
private readonly Func<ITransactionService> _transactionServiceFactory;
public PaymentProcessor(Func<ITransactionService> transactionServiceFactory)
{
_transactionServiceFactory = transactionServiceFactory;
}
public void ProcessPayment()
{
// فقط هنگام نیاز نمونهسازی میشود (lazy)
var transactionService = _transactionServiceFactory();
transactionService.Execute();
}
}
// ثبت در DI
services.AddTransient<ITransactionService, TransactionService>();
services.AddTransient<PaymentProcessor>();
مزیت: بسته به لایفسایکل و dependencies، نمونه سرویس در زمان نیاز resolve میشود؛ بنابراین مشکلاتی مثل circular dependency یا هزینه نمونهسازی زودهنگام برطرف میشود. این تکنیک برای سرویسهای سنگین یا وابسته به context کاربردی است.
@DeveloperAdvocate 🥑1 035
در پروژههای Enterprise سطح بالا، اغلب نیاز به جداسازی concerns از طریق استفاده از الگوهایی مانند Clean Architecture داریم. اما بسیاری از توسعهدهندگان درک درستی از نحوه ارتباط بین لایه Application و Domain ندارند. به طور خاص، پرسش این است: آیا Application Service باید منطق دامین را مستقیماً فراخوانی کند یا فقط نقش orchestration را بازی کند؟
یک راهحل ظریف، استفاده از Domain Service است؛ یعنی تمام منطق پیچیده دامین را در Domain Serviceها قرار دهیم و Application Service فقط orchestration و handling را انجام دهد. به این صورت، با تست واحد Domain Serviceها، از صحت منطق کسبوکار مطمئن میشویم و Application Service فقط به تست ترکیبی نیاز دارد.
مثلاً:
// Domain
public class Order {
public void AddProduct(Product product) { /* ... */ }
}
// Domain Service
public class OrderService {
public void PlaceOrder(Order order) {
// Complex business rules
}
}
// Application Service
public class OrderAppService {
private readonly OrderService _orderService;
public void HandlePlaceOrder(int orderId) {
var order = /* fetch order */;
_orderService.PlaceOrder(order);
// Other orchestration (e.g., publishing events)
}
}
آیا شما معماریها، راهکارها یا مثالهایی دیدهاید که مرز بین لایه Application و Domain را بهتر مدیریت کنند، یا شاید نقاط ضعفی در این تفکیک مشاهده کردهاید؟ تجربیات ارزشمند خود را به اشتراک بگذارید.
@DeveloperAdvocate 🥑