استفاده از MediatR

همانطور که اطلاع دارید، الگوی Mediator برای کاهش پیچیدگی ارتباطات بین اشیاء یا کلاسهای مختلف استفاده می شود.یکی از مشکلات در سیستم ها، وجود سرویس ها یا کلاس هایی هستند که اغلب چندین وابستگی به کلاس های دیگر دارند و سیستم را با هرج و مرج بزرگی از وابستگی ها مواجه می کنند. در الگوی mediator هیچ سرویسی به سرویس دیگر وابسته نیست و تنها به mediator به عنوان یک سازمان دهنده وابسته می باشند.mediator در همین راستا، کتابخانه ای برای دات نت به نام MediatR نوشته شده است.یکی از روش های کار در این الگو به این صورت است که ما یک Request را به سمت MediatR ارسال می کنیم، MediatR آن را به سمت handler مربوطه ارسال می کند، handler یک response را به MediatR برمی گرداند و MediatR نیز آن را در اختیار کنترلر قرار می دهد. هر چند که می تواند خروجی هم نداشته باشد. نکته مهم اینجاست که به ازای هر message، فقط یک handler خواهیم داشت. در روش دیگر ما یک event را ارسال می کنیم که خروجی ندارند و صرفا چیزی را notify می کنند. این بار MediatR ممکن است آن را به یک یا چند handler ارسال کند. برای مثال می خواهیم اعلام کنیم که یک تراکنش بانکی صورت گرفته است و آن را به MediatR می دهیم. برنامه نویس های مختلف در یک شرکت ممکن است برای این موضوع، handlerهای مختلفی بنویسند. یک برنامه نویس قصد دارد آن را لاگ کند، یک برنامه نویس قصد دارد آن را sms یا ایمیل کند.بنابراین در روش اول ممکن است خروجی داشته باشیم و یا نداشته باشیم و تناظر یک به یک می باشد. یعنی به ازای هر message، یک handler وجود دارد و اصلاحا مفهومی به نام send داریم. اما در روش دوم مطلقا خروجی نداریم و تناظر ممکن است یک به یک و یا یک به چند باشد. اصلاحا مفهومی به نام push داریم.برای شروع، یک پروژه از نوع API .NET 5 ایجاد می کنم و ابزار MediatR را از طریق لینک زیر نصب می کنم.https://www.nuget.org/packages/MediatR.Extensions.Microsoft.DependencyInjection/و پس از آن در متد ConfigureServices کلاس Startup، این دستور را اضافه می کنم که بر اساس آن تمام کلاس های مرتبط به MediatR را Register می کند.services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly);یک کلاس به نام CreatePersonCommand ایجاد می کنم که یک کلاس ساده می باشد و صرفا تعدادی پروپرتی دارد. قصد دارم با استفاده از MediatR یک Person را ایجاد کنم و پاسخی هم به من برگردانده نخواهد شد. برای استفاده از MediatR، این کلاس را از MediatR.IRequest ارث بری می کنم.public class CreatePersonCommand : MediatR.IRequest { public string FullName { get; set; } public byte Age { get; set; } }زمانی که MediatR این Command را دریافت می کند، باید آن را به یک Handler ارسال کند و وظیفه آن Handler در واقع handle کردن این Command یا Message می باشد.بنابراین یک کلاس به نام CreatePersonCommandHandler ایجاد می کنم که وظیفه handle کردن Command من را خواهد داشت. این کلاس را از MediatR.IRequestHandler<CreatePersonCommand> ارث بری می کنم. ملاحظه می کنید که به صورت Generic، کلاس CreatePersonCommand را وارد کرده ام. MediatR زمانی که این کلاس را ببیند، متوجه می شود که اگر یک Message از این جنس به آن ارسال شد، باید آن را به این Handler ارسال کند.public class CreatePersonCommandHandler : MediatR.IRequestHandler<CreatePersonCommand> { private readonly IPersonRepository _personRepository; public CreatePersonCommandHandler(IPersonRepository personRepository) { _personRepository = personRepository; } public async Task<MediatR.Unit> Handle (CreatePersonCommand request, CancellationToken cancellationToken) { var person = new Person { FullName = request.FullName, Age = request.Age, }; await _personRepository.CreatePersonAsync(person); return new MediatR.Unit(); } }در این کلاس متد Handle را override کرده ام که از طریق PersonRepository، رکورد من را ذخیره می کند.همانطور که ملاحظه می کنید متد ما Async می باشد و چون متد ما خروجی ندارد بنابراین از MediatR.Unit استفاده می کنم.بعد از آن در PeopleController عملیات inject ابزار MedateR خود را انجام می دهم و اکشن مربوطه را اضافه می کنم.[ApiController] [Route(&quot[controller]&quot)] public class PeopleController : ControllerBase { private readonly IMediator _mediator; public PeopleController(IMediator mediator) { _mediator = mediator; } [HttpPost] public async Task<IActionResult> Create(CreatePersonCommand command) { await _mediator.Send(command); return Ok(); } }در مرحله بعد قصد دارم یک Query ایجاد کنم که قاعدتا دارای یک خروجی هم می باشد.یک کلاس به نام GetPersonByIdQuery ایجاد می کنم که یک Person را به من برگرداند. public class GetPersonByIdQuery : MediatR.IRequest<Person> { public int Id { get; set; } }ملاحظه می کنید که به صورت Generic خروجی خود را به آن معرفی کردم. و بعد از آن Hanlder مربوطه را اضافه می کنم.public class GetPersonByIdQueryHandler : MediatR.IRequestHandler<GetPersonByIdQuery, Person> { private readonly IPersonRepository _personRepository; public GetPersonByIdQueryHandler(IPersonRepository personRepository) { _personRepository = personRepository; } public async Task<Person> Handle (GetPersonByIdQuery request, CancellationToken cancellationToken) { var person = await _personRepository.GetPersonByIdAsync(request.Id); return person; } }و در نهایت اکشن مربوطه:[HttpGet(&quot{personId}&quot)] public async Task<IActionResult> Get(int personId) { var person = await _mediator.Send(new GetPersonByIdQuery { Id = personId, }); return Ok(person); }اما روش دومی که تحت عنوان Event تعریف کردم و می توانیم یک یا چند Handler داشته باشیم را قصد دارم پیاده سازی کنم.کلاسی که ایجاد می کنم این بار از MediatR.INotification ارث بری می کند.public class SendMessageNotification : MediatR.INotification { public string Message { get; set; } }و کلاس Hanlder مربوطه نیز از MediatR.INotificationHandler<SendMessageNotification>ارث بری می کنم.public class SendMessageCommandHanlder : MediatR.INotificationHandler<SendMessageNotification> { private readonly INotificationService _notificationService; public SendMessageCommandHanlder(INotificationService notificationService) { _notificationService = notificationService; } public async Task Handle (SendMessageNotification notification, CancellationToken cancellationToken) { await _notificationService.SendAsync(notification.Message); } }هر تعداد کلاس Hanlder برای این Command ایجاد کنم اجرا خواهند شد. در نهایت در اکشن مربوطه این بار به جای متد Send از Publish استفاده می کنم.[HttpPost(&quotSendMessage/{message}&quot)] public async Task<IActionResult> SendMessage(string message) { await _mediator.Publish(new SendMessageNotification { Message = message, }); return Ok(); }پایان