دکوراتور اعتبارسنجی با FluentValidation: ✅
internal sealed class ValidationCommandHandler<TCommand, TResponse>(...)
: ICommandHandler<TCommand, TResponse>
where TCommand : ICommand<TResponse>
{
public async Task<Result<TResponse>> Handle(TCommand command, CancellationToken cancellationToken)
{
// ... (منطق اعتبارسنجی قبل از اجرای handler اصلی) ...
}
}
⚠️ مهم: از آنجایی که ما با اینترفیسهای جنریک کار میکنیم، هر دکوراتور باید صراحتاً همان قرارداد جنریک را هدف قرار دهد.
در بخش بعدی، این را با استفاده از Scrutor به هم متصل خواهیم کرد.
راهاندازی DI ⚙️
با handlerها و دکوراتورهایمان، میتوانیم همه چیز را با استفاده از Scrutor ثبت کنیم.
services.Scan(scan => scan.FromAssembliesOf(typeof(DependencyInjection))
.AddClasses(classes => classes.AssignableTo(typeof(IQueryHandler<,>)), publicOnly: false)
.AsImplementedInterfaces()
.WithScopedLifetime()
// ... ثبت بقیه handlerها ...
);
🔹این کد اسمبلی اپلیکیشن را اسکن کرده و تمام command و query handlerها را ثبت میکند.
🔹سپس، دکوراتورها را برای اعتبارسنجی و لاگینگ اعمال میکنیم:
services.Decorate(typeof(ICommandHandler<,>), typeof(ValidationDecorator.CommandHandler<,>));
services.Decorate(typeof(ICommandHandler<,>), typeof(LoggingDecorator.CommandHandler<,>));
💡ترتیب مهم است. آخرین دکوراتور اعمال شده، بیرونیترین دکوراتور در زمان اجرا خواهد بود. بنابراین در این مثال، دکوراتور لاگینگ ابتدا اجرا میشود، سپس اعتبارسنجی و بعد handler اصلی.
استفاده از Minimal API 🎯
هنگامی که همه چیز متصل شد، استفاده از یک command handler از یک endpoint Minimal API ساده است:
app.MapPut("todos/{id:guid}/complete", async (
Guid id,
ICommandHandler<CompleteTodoCommand> handler,
CancellationToken cancellationToken) =>
{
var command = new CompleteTodoCommand(id);
Result result = await handler.Handle(command, cancellationToken);
return result.Match(Results.NoContent, CustomResults.Problem);
})ما ICommandHandler مناسب را مستقیماً به endpoint تزریق میکنیم. نیازی به ISender، لایه mediator یا جستجوی زمان اجرا نیست.
نتیجهگیری 👍
CQRS
به یک فریمورک پیچیده نیاز ندارد.
با چند اینترفیس کوچک، چند کلاس دکوراتور و یک راهاندازی تمیز DI، میتوانید یک پایپلاین ساده و انعطافپذیر برای مدیریت کامندها و کوئریها بسازید. درک، تست و توسعه آن آسان است.
امیدوارم این مطلب مفید بوده باشد.
🔖 هشتگها:
#CSharp #DotNet #CQRS #SoftwareArchitecture #CleanArchitecture #DesignPatterns #BestPractices #MediatR
🧠 جمعبندی
Domain Event
ها به شما کمک میکنند تا سیستمی loosely coupled بسازید.
میتوانید از آنها برای جدا کردن منطق اصلی دامنه از اثرات جانبی (side effects) استفاده کنید،
اثراتی که میتوانند به صورت asynchronous مدیریت شوند.
لازم نیست برای پیادهسازی Domain Eventها از صفر شروع کنید؛
میتوانید از ترکیب EF Core و MediatR استفاده کنید تا این قابلیت را بهسادگی بسازید.
باید تصمیم بگیرید که چه زمانی میخواهید Domain Eventها را منتشر کنید:
قبل یا بعد از ذخیره شدن تغییرات در پایگاه داده — هرکدام مزایا و معایب خاص خود را دارند.
من شخصاً ترجیح میدهم بعد از ذخیرهسازی تغییرات Domain Eventها را منتشر کنم
و برای اطمینان از تراکنش اتمیک، از الگوی Outbox استفاده میکنم.
این رویکرد باعث ایجاد eventual consistency میشود،
اما در عین حال قابل اعتمادتر است.
امیدوارم این مطلب برایتان مفید بوده باشد 🙌
🔖هشتگها:
#DomainEvents #EFCore #OutboxPattern #EventDriven #DesignPatterns #LooselyCoupled