فصل 6 از 8 درس 3 از 4

Repository و Unit of Work

Generic و Non-Generic Repository با هم

بخشی از آموزش جامع Design Patterns در C# و .NET

محتوای درس Generic و Non-Generic Repository با هم

در این درس یک جنبه مهم از طراحی شیءگرا را با نگاه کاربردی بررسی می‌کنیم.

هدف یادگیری

  • بتوانید مفهوم «Generic و Non-Generic Repository با هم» را با زبان خودتان توضیح دهید.
  • بتوانید مسئله‌ای را که این تکنیک حل می‌کند از روی کد تشخیص دهید.
  • بتوانید تشخیص دهید چه زمانی استفاده از آن اشتباه یا اضافه‌کاری است.

مسئله اصلی چیست؟

قرار دادن قرارداد Query/Persistence دامنه در مرز داده مسئله اصلی معمولاً زمانی شروع می‌شود که یک تصمیم طراحی در چند نقطه از سیستم تکرار می‌شود و هر تغییر، چند فایل و چند کلاس را درگیر می‌کند.

مدل ذهنی ساده

سناریوی ذهنی این درس: OrderRepository با Queryهای دامنه‌محور. به جای اینکه Client همه جزئیات را بداند، یک مرز طراحی ایجاد می‌کنیم تا بخش متغیر پشت یک قرارداد یا ساختار مشخص قرار بگیرد.

پیاده‌سازی نمونه در C#

نمونه کد آموزشی

نمونه کد این درس در بخش کد درس قرار داده شده است.

کد قبل از Refactoring چه بویی می‌دهد؟

  • if/switch بزرگ که با هر نوع جدید رشد می‌کند.
  • ساخت مستقیم وابستگی‌ها با new در لایه Business/Application.
  • تکرار منطق cross-cutting مثل Logging، Retry یا Validation.

مراحل Refactoring

1. نقطه تغییر را مشخص کنید و دقیقاً بگویید کدام تصمیم ناپایدار است.

2. یک قرارداد کوچک و معنادار برای بخش متغیر تعریف کنید.

3. Concrete implementationها را پشت قرارداد منتقل کنید.

4. Client را فقط به قرارداد وابسته کنید.

5. با تست، رفتار قبل و بعد را یکسان نگه دارید و سپس پیچیدگی اضافی را حذف کنید.

چه زمانی استفاده کنیم؟

وقتی abstraction دامنه‌ای ارزش دارد؛ نه صرفاً برای Wrap کردن تمام متدهای DbSet. اگر تنها یک پیاده‌سازی ساده دارید و احتمال تغییر واقعی کم است، Pattern ممکن است صرفاً تعداد فایل‌ها را زیاد کند.

نکته تخصصی این درس

Repository عمومی را برای عملیات تکراری محدود نگه دارید و Queryهای مهم دامنه را در Repository تخصصی تعریف کنید.

خطاهای رایج

  • ساخت Generic Repository با ده‌ها متد CRUD که DbSet را دوباره اختراع می‌کند.
  • برگرداندن IQueryable از مرزی که قرار بود persistence را پنهان کند.
  • قرار دادن Business Rule در Repository.

Best Practice

  • قرارداد را کوچک و بر اساس نیاز Client طراحی کنید.
  • نام کلاس‌ها را بر اساس نقش دامنه/کاربرد انتخاب کنید، نه صرفاً نام Pattern.
  • قبل از اضافه کردن abstraction یک تغییر واقعی یا تست‌پذیری لازم را نشان دهید.
  • برای سناریوهای async، cancellation و exception handling را بخشی از قرارداد در نظر بگیرید.

تمرین

  • یک نمونه کوچک از Generic و Non-Generic Repository با هم برای سامانه فروش آنلاین طراحی کنید.
  • کد خود را طوری بازنویسی کنید که یک implementation جدید بدون تغییر Client اضافه شود.
  • یک حالت پیدا کنید که استفاده از این Pattern overengineering باشد و دلیلش را بنویسید.

جمع‌بندی

مهم‌ترین نکته این درس این است که «Generic و Non-Generic Repository با هم» یک اسم برای کلاس‌بندی کد نیست؛ یک راه شناخته‌شده برای مدیریت یک نوع مشخص از تغییر است. اگر مسئله وجود دارد، Pattern کمک می‌کند نقطه تغییر محدود و قابل تست شود؛ اگر مسئله وجود ندارد، ساده‌ترین طراحی معمولاً بهتر است.

درس بعدی

درس 77 از 89 در آموزش جامع Design Patterns در C# و .NET

نمونه کد

public interface IOrderRepository
{
 Task<Order?> GetForCheckoutAsync(Guid id, CancellationToken ct);
 void Add(Order order);
}
public sealed class EfOrderRepository(AppDbContext db) : IOrderRepository
{
 public Task<Order?> GetForCheckoutAsync(Guid id, CancellationToken ct) =>
 db.Orders.Include(x => x.Items).SingleOrDefaultAsync(x => x.Id == id, ct);
 public void Add(Order order) => db.Orders.Add(order);
}

خروجی مورد انتظار

خروجی این درس شامل اجرای نمونه مرتبط با «Generic و Non-Generic Repository با هم» و درک کاربرد آن در سناریوهای واقعی C# و .NET است.