آیا استفاده از مقادیر اختیاری Optional در Interface صحیح است؟
نویسنده: هادی مزارعی
تاریخ: ۱۴۰۴/۰۸/۳۰ ۱۹:۲۱
آدرس: www.dntips.ir
Interface کار درستی است یا آن را به کلاسهای پیادهسازی کننده واگذار کنیم؟ در این نمونه مشخصا پارامتر CancellationToken برای متدهای Async مد نظر میباشد. فرض کنیم از دریافت درخواست در API تا لایههایی که متدهای Interface پیاده سازی شدهاند بطور فرضی در چند کلاس متدهای Async اجرا میشوند:CancellationToken دریافت خواهد کرد.MediatR پیاده سازی شده است و قرار است متدهای Repository را صدا بزند. ممکن است بدلیل پیچیدگی منطق برنامه، عملکردهای مختلف در یک Handler به کلاسهای کوچکتری تقسیم شده باشند که در هر یک از آنها ممکن است یک متد Async دیگری اجرا شود.Interface را پیاده سازی کرده است.CancellationToken را بصورت اختیاری مقدار دهی کند. حال در صورت استفاده از مقدار default آیا ضرورتا باید برای تمام متدهایی که لایه به لایه فراخوانی میشوند پارامتر CancellationToken را با مقدار پیشفرض default تعریف کنیم یا فقط در ActionMethod در لایه Api کافیست؟AsyncLocal<CancellationToken> و IHttpContextAccessor.HttpContext.RequestAborted برخوردم ولی کارکرد این دو را متوجه نشدم. البته در خصوص HttpContext.RequestAborted مادامی که در لایه Service دسترسی به سرویس IHttpContextAccessor میسر باشد امکان بررسی درخواست لغو شده وجود دارد اما کاربرد AsyncLocal را متوجه نشدم. ممنون میشم چگونگی استفاده از AsyncLocal را توضیح بدید.CancellationToken را به عنوان آخرین پارامتر و با مقدار پیشفرض در نظر بگیرید.public interface IUserRepository
{
Task<User> GetByIdAsync(int id, CancellationToken cancellationToken = default);
}None باشد) و میتواند تصمیم بگیرد که آیا از آن در EF Core یا سایر عملیات I/O استفاده کند یا خیر.AsyncLocalبرای حذف پارامتر (Implicit Passing)AsyncLocal اشاره کردید. این روشی است برای ایجاد یک "محیط" یا Context که در طول جریان اجرای کدهای Async (از بالا به پایین) در دسترس است، بدون اینکه نیاز باشد آن را به عنوان پارامتر پاس دهید.AsyncLocalچیست و چگونه کار میکند؟AsyncLocal مانند یک متغیر Static است، اما با این تفاوت که مقدار آن برای هر "Request" یا هر "Thread منطقی" جداگانه است. وقتی شما یک متد async را صدا میزنید (await میکنید)، مقدار AsyncLocal به آن متد و متدهای زیرمجموعهی آن "جریان" (Flow) پیدا میکند.// Core/Application Layer
public interface ICancellationContext
{
CancellationToken CurrentToken { get; }
}// Infrastructure Layer
public class AsyncLocalCancellationContext : ICancellationContext
{
// این متغیر استاتیک است اما مقدارش برای هر درخواست ایزوله است
private static readonly AsyncLocal<CancellationToken> _token = new AsyncLocal<CancellationToken>();
public CancellationToken CurrentToken => _token.Value;
// متدی برای ست کردن توکن در ابتدای درخواست
public void SetToken(CancellationToken token)
{
_token.Value = token;
}
}// API Layer
public class CancellationMiddleware
{
private readonly RequestDelegate _next;
public CancellationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, AsyncLocalCancellationContext cancellationContext)
{
// توکن مربوط به این درخواست را درون کانتینر سراسری میریزیم
cancellationContext.SetToken(context.RequestAborted);
await _next(context);
}
}// Infrastructure / Repository Layer
public class UserRepository : IUserRepository
{
private readonly DbContext _db;
private readonly ICancellationContext _canContext; // تزریق وابستگی
public UserRepository(DbContext db, ICancellationContext canContext)
{
_db = db;
_canContext = canContext;
}
public async Task<User> GetByIdAsync(int id) // دیگر پارامتر توکن ندارد
{
// توکن را از کانتکست میخوانیم
return await _db.Users.FindAsync(new object[] { id }, _canContext.CurrentToken);
}
}IHttpContextAccessorIHttpContextAccessor استفاده نکنیم؟ASP.NET Core وابسته است. اگر فردا بخواهید از کدهای لایه Business یا Repository خود در یک Console App یا Background Service استفاده کنید، کد شما خطا میدهد (چون HttpContext وجود ندارد).SetToken را صدا بزنید و همه چیز کار میکند.| روش | شفافیت کد | راحتی استفاده | وابستگی | توصیه |
| Explicit (لایه به لایه) | عالی | کم (کد زیاد) | ندارد | توصیه مایکروسافت |
| AsyncLocal | متوسط | عالی | ندارد | گزینه دوم |
| IHttpContextAccessor | بد | خوب | شدید به Web | اصلاً استفاده نکنید |
AsyncLocal (با همان ساختار ICancellationContext که نوشتم) یک الگوی بسیار حرفهای و تمیز برای حل این مشکل است.