نقش معماری Decorator Pattern در ASP.NET Core Middleware
Middleware و الگوی دکوراتور
در ASP.NET Core، سیستم پردازش درخواستهای HTTP بر اساس یک خط لوله (Pipeline) از اجزای نرمافزاری به نام Middleware طراحی شده است. هر Middleware وظیفهی خاصی (مانند احراز هویت، لاگگیری، یا مسیریابی) را انجام داده و سپس درخواست را به Middleware بعدی (با فراخوانی next.Invoke(context)) ارسال میکند. این ساختار، نمونهای از الگوی زنجیره مسئولیت (Chain of Responsibility) است و نه بهطور مستقیم الگوی دکوراتور، اما شباهتهای عملکردی زیادی در بستهبندی و توسعهپذیری دارد.
الگوی دکوراتور (Decorator Pattern): این یک الگوی طراحی ساختاری (Structural Design Pattern) است که به شما اجازه میدهد بهصورت دینامیک (Dynamic) و در زمان اجرا، رفتارهای جدیدی را بدون تغییر کد اصلی کلاس، به یک شیء اضافه کنید. این کار با بستهبندی (Wrapping) شیء اصلی درون یک یا چند "دکوراتور" انجام میشود که هرکدام قابلیت اضافی خود را اعمال میکنند.
اهداف اصلی الگوی دکوراتور
-
رعایت اصل باز/بسته (Open/Closed Principle): کلاسها برای توسعه (افزودن قابلیت) باز و برای تغییر (تغییر کد موجود) بسته باشند.
-
جداسازی دغدغهها (Separation of Concerns): هر قابلیت اضافی (مانند کشینگ یا لاگگیری) در کلاس دکوراتور مجزای خود پیادهسازی میشود.
-
اجتناب از انفجار زیرکلاسها: به جای ایجاد زیرکلاسهای متعدد برای هر ترکیب احتمالی از قابلیتها (ماندویت، لاگینگ، کشینگ)، از ترکیب دکوراتورها استفاده میشود.
پیادهسازی الگوی دکوراتور در ASP.NET Core
اگرچه Middleware خود یک نوع دکوراتور برای RequestDelegate است، اما کاربرد اصلی الگوی دکوراتور در ASP.NET Core برای سرویسها (Services) و ریپازیتوریها (Repositories) است که توسط سیستم تزریق وابستگی (Dependency Injection - DI) مدیریت میشوند.
این الگو زمانی بسیار مفید است که بخواهید دغدغههای جانبی (Cross-Cutting Concerns) را به منطق تجاری (Business Logic) یا لایه دسترسی به داده اضافه کنید، بدون اینکه منطق اصلی را آلوده کنید.
۱. تعریف قرارداد (Interface)
اولین قدم، تعریف یک اینترفیس است که هم سرویس اصلی و هم دکوراتورها از آن پیروی خواهند کرد. این اینترفیس، قرارداد اصلی برای عملکرد را مشخص میکند.
public interface IProductService
{
Task GetProductAsync(int id);
}
۲. پیادهسازی سرویس اصلی (Component)
این کلاس شامل منطق اصلی کار است. به عنوان مثال، خواندن داده از پایگاه داده.
public class ProductService : IProductService
{
// ... وابستگیها
public async Task GetProductAsync(int id)
{
// منطق اصلی: فراخوانی دیتابیس
return new Product { Id = id, Name = "Laptop" };
}
}
۳. ایجاد دکوراتورها (Decorators)
دکوراتورها نیز باید اینترفیس IProductService را پیادهسازی کنند. هر دکوراتور در سازنده خود، یک نمونه از همان اینترفیس (IProductService) را دریافت میکند. این نمونه یا سرویس اصلی است یا دکوراتور دیگری که قبلاً اعمال شده است.
مثال: دکوراتور لاگگیری (Logging Decorator)
این دکوراتور، قابلیت لاگگیری را قبل و بعد از فراخوانی سرویس اصلی اضافه میکند:
public class LoggingProductServiceDecorator : IProductService
{
private readonly IProductService _productService;
private readonly ILogger _logger;
public LoggingProductServiceDecorator(IProductService productService, ILogger logger)
{
_productService = productService;
_logger = logger;
}
public async Task GetProductAsync(int id)
{
_logger.LogInformation($"درخواست محصول با شناسه: {id}"); // منطق اضافی
var product = await _productService.GetProductAsync(id); // فراخوانی سرویس بستهبندیشده
_logger.LogInformation($"محصول با موفقیت دریافت شد: {product.Name}"); // منطق اضافی
return product;
}
}
مثال: دکوراتور کشینگ (Caching Decorator)
این دکوراتور، قبل از فراخوانی سرویس اصلی، بررسی میکند که آیا داده در حافظه کش موجود است یا خیر:
public class CachingProductServiceDecorator : IProductService
{
private readonly IProductService _productService;
private readonly IDistributedCache _cache;
public CachingProductServiceDecorator(IProductService productService, IDistributedCache cache)
{
_productService = productService;
_cache = cache;
}
public async Task GetProductAsync(int id)
{
var cacheKey = $"product-{id}";
var cachedProduct = await _cache.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(cachedProduct))
{
// اگر در کش موجود بود، از آن استفاده کن
return JsonSerializer.Deserialize(cachedProduct);
}
// اگر نبود، سرویس اصلی را فراخوانی کن
var product = await _productService.GetProductAsync(id);
// نتیجه را کش کن
await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(product), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) });
return product;
}
}

۴. ثبت در تزریق وابستگی (DI Registration)
پیچیدهترین بخش، تنظیم صحیح زنجیره دکوراتورها در سیستم DI است. در اینجا، باید مطمئن شویم که هنگامی که کلاسی IProductService را درخواست میکند، آخرین دکوراتور (Caching) را دریافت کند که خود آن، دکوراتور قبلی (Logging) را فراخوانی کند و در نهایت به سرویس اصلی برسیم.
ثبت دستی (Manual Registration)
// 1. ثبت سرویس اصلی به عنوان پیادهسازی واقعی (مثلاً ProductService)
services.AddScoped();
// 2. ثبت دکوراتورها به ترتیب معکوس (از خارج به داخل)
services.AddScoped(sp =>
{
// دریافت نمونه سرویس اصلی از DI
var actualService = sp.GetRequiredService();
// تزریق به دکوراتور لاگگیری
var loggingDecorator = new LoggingProductServiceDecorator(actualService, sp.GetRequiredService>());
// تزریق به دکوراتور کشینگ
var cachingDecorator = new CachingProductServiceDecorator(loggingDecorator, sp.GetRequiredService());
// بازگرداندن آخرین دکوراتور به عنوان IProductService
return cachingDecorator;
});
استفاده از کتابخانه Scrutor (راهکار پیشنهادی)
کتابخانه Scrutor با متد توسعهای Decorate این فرآیند را بهشدت ساده میکند و دیگر نیازی به ثبت دستی نیست:
services.AddScoped(); // 1. ثبت سرویس اصلی
// 2. ثبت دکوراتورها به ترتیب اعمال (از داخل به خارج، چون Scrutor به صورت پشتهای عمل میکند)
// اولین دکوراتور (Logging) سرویس اصلی را Decorate میکند.
services.Decorate();
// دومین دکوراتور (Caching) خروجی دکوراتور قبلی (Logging) را Decorate میکند.
services.Decorate();
// زنجیره نهایی: Caching -> Logging -> ProductService
💡 تفاوتهای کلیدی با ASP.NET Core Middleware
همانطور که ذکر شد، Middleware یک Pipeline از اجزای متوالی است، در حالی که Decorator یک ساختار تو در تو (Nested/Wrapped) است.
|
ویژگی |
الگوی دکوراتور (برای سرویسها) |
ASP.NET Core Middleware |
|
هدف اصلی |
افزودن رفتار به یک شیء سرویس (Service Object) خاص. |
پردازش یک درخواست HTTP (HTTP Request) در یک خط لوله سراسری. |
|
نوع الگو |
دکوراتور (Decorator) |
زنجیره مسئولیت (Chain of Responsibility) |
|
محل اجرا |
درون منطق تجاری (Business Logic) برنامه. |
در لایه زیرساخت (Infrastructure Layer) در ابتدای پردازش درخواست. |
|
پایان فرآیند |
با رسیدن به سرویس اصلی (Component). |
با رسیدن به یک Middleware خاتمهدهنده (Terminal) (مانند Endpoint Middleware) یا بازگشت به سمت عقب. |
|
مدیریت وابستگی |
از طریق DI container و تزریق نمونه داخلی. |
از طریق متد RequestDelegate next و متدهای توسعهای IApplicationBuilder. |
نتیجهگیری
الگوی دکوراتور در ASP.NET Core یک ابزار قدرتمند برای افزایش توسعهپذیری و خوانایی کد شما در لایههای سرویس و ریپازیتوری است. این الگو امکان میدهد تا دغدغههای جانبی را از منطق اصلی کسبوکار جدا کرده و به طور مستقل و ماژولار آنها را اضافه یا حذف کنید، که در نهایت منجر به یک معماری تمیز (Clean Architecture) و آسان برای نگهداری و تست میشود. استفاده از کتابخانههایی مانند Scrutor این فرآیند را بهویژه در برنامههای بزرگ، بسیار ساده و کارآمد میسازد.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.