استفاده از Repository Pattern در ASP.NET Core

الگوی Repository در ASP.NET Core: یک لایه انتزاعی برای مدیریت داده
کینگتو - آموزش برنامه نویسی تخصصصی - دات نت - سی شارپ - بانک اطلاعاتی و امنیت

استفاده از Repository Pattern در ASP.NET Core

33 بازدید 0 نظر ۱۴۰۴/۰۸/۲۲

مقدمه: چرا به Repository Pattern نیاز داریم؟

در توسعه نرم‌افزارهای سازمانی (Enterprise) با ASP.NET Core، یکی از چالش‌های اصلی، مدیریت ارتباط بین لایه‌های مختلف برنامه، به ویژه جداسازی منطق کسب‌وکار (Business Logic) از منطق دسترسی به داده (Data Access Logic) است. اگرچه ابزارهای مدرن نگاشت رابطه‌ای شیء (ORM) مانند Entity Framework Core (EF Core) کار تعامل با پایگاه داده را آسان کرده‌اند، اما استفاده مستقیم از DbContext در لایه‌های سرویس یا کنترلرها می‌تواند منجر به وابستگی شدید (Tight Coupling)، کاهش قابلیت تست‌پذیری (Testability) و کد نویسی تکراری شود.

الگوی مخزن (Repository Pattern) به عنوان یک لایه واسط، این مشکل را حل می‌کند. این الگو یک مجموعه از اشیاء در حافظه را شبیه‌سازی می‌کند که می‌تواند برای عملیات CRUD (ایجاد، خواندن، به‌روزرسانی و حذف) روی مدل‌های دامنه (Domain Models) استفاده شود، بدون اینکه جزئیات نحوه ذخیره‌سازی یا بازیابی داده‌ها مشخص باشد.

 

۱. Repository Pattern چیست؟

الگوی Repository یک میانجی بین لایه‌های دامنه (Business/Service) و لایه‌های نگهداری داده (Data Persistence) عمل می‌کند . این الگو دسترسی به داده‌ها را از طریق متدهایی مانند GetById, GetAll, Add, Update, و Delete انتزاعی می‌کند.

اجزای اصلی Repository Pattern:

  1. رابط (Interface) مخزن (IRepository<T>): وظایف اصلی و قراردادهای دسترسی به داده‌ها را تعریف می‌کند. این کار به ما امکان می‌دهد که لایه سرویس/کسب‌وکار فقط به این قرارداد متکی باشد و نه به پیاده‌سازی خاص.

  2. کلاس پیاده‌سازی مخزن (Repository<T>): منطق دسترسی به داده‌ها، اغلب با استفاده از EF Core یا Dapper، را پیاده‌سازی می‌کند. این کلاس مسئول برقراری ارتباط با DbContext یا اجرای کوئری‌های SQL است.

  3. مدل‌های دامنه (Domain Entities): اشیائی هستند که داده‌ها و همچنین منطق کسب‌وکار مرتبط را نگهداری می‌کنند.

 

۲. مزایای استفاده از Repository Pattern در ASP.NET Core

استفاده از این الگو به ویژه در پروژه‌های بزرگ و پیچیده مزایای قابل توجهی به همراه دارد:

  • جداسازی نگرانی‌ها (Separation of Concerns): این مهم‌ترین مزیت است. منطق کسب‌وکار کاملاً از منطق دسترسی به داده جدا می‌شود. این جداسازی، خوانایی و سازمان‌دهی کد را به شدت افزایش می‌دهد.

  • افزایش قابلیت تست‌پذیری (Testability): برای انجام تست‌های واحد (Unit Tests) بر روی منطق کسب‌وکار، دیگر نیازی به اتصال واقعی به پایگاه داده نداریم. می‌توانیم با استفاده از Mocking یک نسخه تقلبی (Mock) از Repository Interface ایجاد کرده و رفتار مورد انتظار را شبیه‌سازی کنیم. این امر فرآیند تست را سریع‌تر، مطمئن‌تر و ارزان‌تر می‌سازد.

  • انعطاف‌پذیری در تغییر فناوری داده: اگر در آینده تصمیم بگیرید که ORM خود را از EF Core به Dapper یا حتی یک پایگاه داده NoSQL تغییر دهید، کافی است فقط کلاس‌های پیاده‌سازی Repository را تغییر دهید؛ لایه سرویس و کنترلرها بدون تغییر باقی می‌مانند.

  • کاهش کدهای تکراری (Code Duplication): کوئری‌های متداول (مانند دریافت همه، جستجو بر اساس شناسه) در یک مکان (Repository) متمرکز شده و از تکرار نوشتن کد دسترسی به داده در چندین سرویس یا کنترلر جلوگیری می‌شود.

  • اعمال قوانین متمرکز دسترسی به داده: می‌توان قوانین خاصی مانند کشینگ (Caching) یا مجوزدهی (Authorization) در سطح دسترسی به داده را به صورت متمرکز در لایه Repository اعمال کرد.

 

 

۳. پیاده‌سازی الگوی Generic Repository

برای کاهش کدهای تکراری، اغلب از الگوی Generic Repository استفاده می‌شود. به جای تعریف یک Repository مجزا برای هر Entity (مثلاً IUserRepository، IProductRepository)، یک رابط و کلاس Repository عمومی تعریف می‌شود که برای هر نوع Entity قابل استفاده است.

 

۳.۱. تعریف رابط Generic Repository

public interface IRepository<TEntity> where TEntity : class
{
    Task<TEntity> GetByIdAsync(int id);
    Task<IEnumerable<TEntity>> GetAllAsync();
    Task AddAsync(TEntity entity);
    void Update(TEntity entity);
    void Delete(TEntity entity);
    // متدهای پیشرفته‌تر مانند FindByCondition با IQueryable یا Expression
}

 

۳.۲. پیاده‌سازی با Entity Framework Core

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

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
    protected readonly DbContext _context;
    protected readonly DbSet<TEntity> _dbSet;

    public Repository(DbContext context)
    {
        _context = context;
        _dbSet = context.Set<TEntity>();
    }

    public async Task<TEntity> GetByIdAsync(int id)
    {
        return await _dbSet.FindAsync(id);
    }

    public async Task AddAsync(TEntity entity)
    {
        await _dbSet.AddAsync(entity);
    }
    // ... پیاده‌سازی سایر متدها
}

 

۴. الگوی Unit of Work (واحد کار)

الگوی Repository اغلب با الگوی Unit of Work ترکیب می‌شود. Unit of Work یک کلاس است که تراکنش‌های پایگاه داده را مدیریت می‌کند و تضمین می‌کند که چندین عملیات Repository (مانند افزودن کاربر و حذف محصول) به صورت اتمی (Atomic) انجام شوند؛ یعنی یا همه عملیات با موفقیت انجام شوند و در پایگاه داده ذخیره شوند، یا هیچ‌کدام اعمال نشوند (Rollback).

در ASP.NET Core، Unit of Work معمولاً با مدیریت DbContext ارتباط دارد.

public interface IUnitOfWork : IDisposable
{
    IProductRepository Products { get; }
    IUserRepository Users { get; }
    Task<int> CompleteAsync(); // متد SaveChanges
}

با این کار، بجای اینکه هر Repository خود دارای متد SaveChanges باشد، عملیات ذخیره تغییرات به Unit of Work سپرده می‌شود.

 

۵. تزریق وابستگی (Dependency Injection)

ASP.NET Core از ابتدا با مفهوم تزریق وابستگی طراحی شده است. این موضوع به طور طبیعی با Repository Pattern هم‌خوانی دارد. شما باید رابط‌های Repository و Unit of Work خود را در فایل Program.cs یا Startup.cs به عنوان یک سرویس ثبت کنید (معمولاً با طول عمر Scoped یا Transient):

// در متد ConfigureServices
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); // برای Generic Repository

سپس، این سرویس‌ها را می‌توان به راحتی در سازنده (Constructor) کنترلرها یا سرویس‌های کسب‌وکار تزریق کرد:

public class ProductService
{
    private readonly IUnitOfWork _unitOfWork;

    public ProductService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public async Task AddProduct(Product product)
    {
        // اعمال منطق کسب‌وکار
        _unitOfWork.Products.AddAsync(product);
        await _unitOfWork.CompleteAsync();
    }
}

 

۶. بحث‌های انتقادی و جایگزین‌ها: آیا EF Core خود یک Repository نیست؟

برخی از توسعه‌دهندگان معتقدند که با ظهور Entity Framework Core، که خود شامل مفاهیمی مانند DbSet است (که شبیه به یک Repository عمل می‌کند)، استفاده از یک لایه Repository اضافی در پروژه‌های کوچک و متوسط می‌تواند بیش از حد پیچیده و غیرضروری باشد.

پاسخ مایکروسافت و بهترین شیوه‌ها:

  • پروژه‌های ساده: در برنامه‌های CRUD ساده، استفاده مستقیم از DbContext در لایه‌های سرویس ممکن است کافی باشد و پیچیدگی کمتری داشته باشد.

  • پروژه‌های سازمانی (Enterprise) و پیچیده: مایکروسافت توصیه می‌کند در سناریوهای پیچیده، به ویژه زمانی که نیاز به جداسازی شدید منطق کسب‌وکار، قابلیت تست پذیری بالا و مدیریت تراکنش‌های پیچیده دارید، از Repository Pattern استفاده کنید. این الگو برای موارد زیر ضروری است:

    • نیاز به استفاده از چندین ORM یا فناوری داده در یک پروژه.

    • نیاز به پیاده‌سازی منطق‌های پیچیده‌تر بازیابی داده در یک مکان متمرکز.

    • نیاز به معماری تمیز (Clean Architecture) یا معماری مبتنی بر دامنه (Domain-Driven Design - DDD).

  • روش جایگزین: برخی از توسعه‌دهندگان از رویکردهای دیگری مانند CQRS (Command Query Responsibility Segregation) استفاده می‌کنند که در آن منطق خواندن (Queries) و منطق نوشتن (Commands) کاملاً از هم جدا می‌شوند، و این امر نیاز به Repository سنتی را کاهش می‌دهد.

 

۷. نکات و بهترین شیوه‌های پیاده‌سازی

  • عدم افشای IQueryable: از برگرداندن مستقیم اشیاء IQueryable<T> از متدهای Repository خودداری کنید. این کار باعث می‌شود لایه کسب‌وکار به قابلیت‌های کوئری‌سازی EF Core وابسته شود و هدف انتزاعی‌سازی نقض گردد. بهتر است یک لیست یا مجموعه (مانند IEnumerable<T> یا List<T>) برگردانید تا کوئری در لایه Repository بسته شود.

  • تمرکز بر Aggregate Root: Repositoryها باید برای کار با ریشه تجمیع (Aggregate Root) در مدل دامنه طراحی شوند. این بدان معنی است که یک Repository نباید به Repositoryهای دیگر وابستگی داشته باشد.

  • Repositoryهای تخصصی: علاوه بر Generic Repository، برای کوئری‌های پیچیده و خاص، Repositoryهای تخصصی (Specific) ایجاد کنید.

  • استفاده از Cancellation Tokens: در متدهای ناهمگام (Async) حتماً از CancellationToken استفاده کنید تا عملیات پایگاه داده در سناریوهای زمان‌بر قابل لغو شدن باشند.

 

نتیجه‌گیری

الگوی Repository در ASP.NET Core یک ابزار معماری قدرتمند است که در پروژه‌های پیچیده، مقیاس‌پذیر و نیازمند تست‌پذیری بالا، ارزش خود را به اثبات می‌رساند. با ایجاد یک لایه انتزاعی بین منطق کسب‌وکار و لایه دسترسی به داده (معمولاً EF Core)، توسعه‌دهنده قادر است کدی تمیزتر، قابل نگهداری‌تر و انعطاف‌پذیرتر بنویسد. انتخاب استفاده از این الگو به اندازه و پیچیدگی پروژه شما بستگی دارد، اما در معماری‌های بزرگ، یک انتخاب هوشمندانه و اصولی به شمار می‌آید.

 

لینک استاندارد شده: xbaUIQd

0 نظر

    هنوز نظری برای این مقاله ثبت نشده است.
جستجوی مقاله و آموزش
دوره‌ها با تخفیفات ویژه