پیاده سازی CQRS با MediatR در Asp.Net Core

الگوی CQRS مخفف Command and Query Responsibility Segregation است، الگویی که عملیات خواندن و به روز رسانی را برای ذخیره داده جدا می کند. پیاده سازی CQRS در برنامه شما می تواند عملکرد، مقیاس پذیری و امنیت آن را به حداکثر برساند. انعطاف‌پذیری ایجاد شده با مهاجرت به CQRS به سیستم اجازه می‌دهد در طول زمان بهتر تکامل یابد و از ایجاد تداخل merge conflicts در سطح دامنه توسط دستورات به‌روزرسانی جلوگیری می‌کند.در اینجا Command به یک فرمان پایگاه داده اشاره دارد که می تواند یک عملیات Insert / Update یا Delete باشد، در حالی که Query مخفف Querying data از یک منبع است. اساساً نگرانی ها را از نظر خواندن و نوشتن از هم جدا می کند، که بسیار منطقی است. این الگو از اصل جداسازی command و query ابداع شده توسط برتراند مایر نشات گرفته است. که بیان می‌کند که هر متد یا باید command ی باشد که یک عمل را انجام می‌دهد، یا کوئری است که داده‌ها را به تماس‌گیرنده برمی‌گرداند، اما نه هر دو. مشکل روش های سنتی چیست ؟مشکل الگوهای معماری سنتی این است که از همان مدل داده یا DTO برای پرس و جو و همچنین به روز رسانی Data source استفاده می شود. زمانی که برنامه شما فقط به عملیات CRUD مربوط می شود و نه چیزی بیشتر، این می تواند روشی مناسب باشد. اما زمانی که الزامات شما به طور ناگهانی شروع به پیچیده شدن می کند، این رویکرد اساسی می تواند فاجعه باشد.حجم کار خواندن و نوشتن اغلب نامتقارن است، با عملکرد و مقیاس بسیار متفاوت.در برنامه های کاربردی، همیشه یک عدم تطابق بین فرم های خواندن و نوشتن داده ها وجود دارد، مانند ویژگی های اضافی که ممکن است برای به روز رسانی نیاز داشته باشید. عملیات موازی حتی ممکن است در بدترین موارد منجر به از دست دادن اطلاعات شود. این بدان معناست که شما در تمام طول عمر برنامه فقط با یک DTO گیر خواهید داشت مگر اینکه DTO دیگری را معرفی کنید که به نوبه خود ممکن است معماری برنامه شما را خراب کند.مدیریت امنیت و مجوزها می‌تواند پیچیده شود، زیرا هر Entity تابع عملیات خواندن و نوشتن است، که ممکن است داده‌ها را در context نادرست نمایش دهد.زمانی که عملیات به صورت موازی روی یک مجموعه از داده ها انجام شود، اختلاف داده ها ممکن است رخ دهد.راه حل چیست؟ایده CQRS این است که به برنامه اجازه می دهد با مدل های مختلف کار کند. به طور خلاصه، شما یک مدل دارید که داده های مورد نیاز برای به روز رسانی یک رکورد، مدل دیگری برای درج یک رکورد، و مدلی دیگر برای پرس و جو یک رکورد را دارد. این به شما انعطاف پذیری با سناریوهای مختلف و پیچیده می دهد. با اجرای CQRS لازم نیست فقط به یک DTO برای کل عملیات CRUD تکیه کنید.الگوی CQRS خواندن و نوشتن را در مدل‌های مختلف جدا می‌کند و از commandها برای به‌روزرسانی داده‌ها و از query برای خواندن داده‌ها استفاده می‌کند.دستورات/commandها باید مبتنی بر وظیفهtask-based) باشند، نه داده محور(data centric) ("رزرو اتاق هتل"، نه "تنظیم وضعیت رزرو روی رزرو شده").دستورات ممکن است در یک صف برای پردازش ناهمزمان(asynchronous) قرار گیرند، نه اینکه به صورت همزمان(synchronously) پردازش شوند.کوئری ها هرگز پایگاه داده را تغییر نمی دهند. یک پرس و جو یک DTO را برمی گرداند که هیچ چیز از دامنه را کپسوله(encapsulate) نمی کند.سپس می‌توان مدل‌ها را جدا کرد، همانطور که در نمودار زیر نشان داده شده است، اگرچه این یک الزام مطلق نیست.داشتن مدل های پرس و جو و به روز رسانی جداگانه، طراحی و پیاده سازی را ساده می کند. با این حال، یک نقطه ضعف این است که کد CQRS نمی تواند به طور خودکار از یک database schema با استفاده از مکانیسم های scaffolding مانند ابزارهای O/RM تولید شود.برای جداسازی بیشتر، می توانید داده های خوانده شده را از داده های نوشتن به صورت فیزیکی جدا کنید. در آن صورت، پایگاه داده خوانده شده می تواند از طرح داده های خود استفاده کند که برای پرس و جوها بهینه شده است. حتی ممکن است از نوع دیگری از ذخیره داده استفاده کند. به عنوان مثال، پایگاه داده write ممکن است رابطه ای باشد، در حالی که پایگاه داده read یک document database است.اگر از پایگاه‌های اطلاعاتی جداگانه برای read و write استفاده می‌شود، باید آنها را هماهنگ(sync) نگه داشت. این معمولاً با انتشار یک رویداد توسط write model هر زمان که پایگاه داده را به روز می کند، انجام می شود. به روز رسانی پایگاه داده و انتشار رویداد باید در یک تراکنش انجام شود.حجم کار خواندن و نوشتن اغلب نامتقارن است، با عملکرد و مقیاس بسیار متفاوت.معمولاً حجم کار خواندن با بار بسیار بالاتری نسبت به نوشتن مواجه می‌شوند.برخی از پیاده سازی های CQRS از الگوی Event Sourcing استفاده می کنند. با این الگو، وضعیت برنامه به عنوان یک توالی از رویدادها ذخیره می شود. هر رویداد نشان دهنده مجموعه ای از تغییرات در داده ها است. وضعیت فعلی با پخش مجدد رویدادها ساخته می شود. در زمینه CQRS، یکی از مزایای Event Sourcing این است که از همان رویدادها می توان برای اطلاع رسانی به اجزای دیگر استفاده کرد - به ویژه برای اطلاع رسانی به مدل خوانده شده. Read Model از رویدادها برای ایجاد یک snapshot از وضعیت فعلی استفاده می کند که برای پرس و جوها کارآمدتر است. با این حال، Event Sourcing به پیچیدگی می‌افزاید.مزایای CQRS عبارتند از:مقیاس بندی مستقل CQRS اجازه می دهد تا حجم کار خواندن و نوشتن به طور مستقل مقیاس شود و ممکن است منجر به اختلافات قفل کمتر شود.(از نظر عملی، همیشه 10 برابر بیشتر عملیات خواندن در مقایسه با عملیات نوشتن وجود دارد. با استفاده از این الگو، می توانید با معرفی یک حافظه پنهان یا NOSQL Db مانند Redis یا Mongo، عملکرد عملیات خواندن خود را افزایش دهید.)دیتا schemas بهینه شده سمت Read می تواند از طرحی استفاده کند که برای پرس و جوها بهینه شده است، در حالی که سمت Write از طرحی استفاده می کند که برای به روز رسانی بهینه شده است.امنیت. اطمینان از اینکه فقط موجودیت های دامنه مناسب روی داده ها Write را انجام می دهند آسان تر است.تفکیک نگرانی ها(Soc) جداسازی دو طرف خواندن و نوشتن می‌تواند منجر به مدل‌هایی شود که قابلیت نگهداری و انعطاف‌پذیری بیشتری دارند. بیشتر منطق پیچیده تجاری وارد مدل نوشتن می شود. مدل خواندن می تواند نسبتا ساده باشد.(به لطف رویکرد تفکیک شده این الگو، دیگر نیازی به کلاس های مدل پیچیده در برنامه خود نخواهیم داشت. در عوض، ما یک مدل در هر عملیات داده داریم که برای ما انعطاف‌پذیری را به ارمغان می آورد.)در حین انجام عملیات موازی امکان از دست رفتن داده وجود ندارد(از آنجایی که ما در هر گزینه مدل های اختصاصی داریم)داشتن کنترل بر روی مدل ها مطابق با نوع عملیات داده، برنامه شما را در دراز مدت بسیار مقیاس پذیر می کند.مسائل و ملاحظات اجرایی، برخی از چالش های اجرای الگوی CQRS عبارتند از:پیچیدگی(Complexity). ایده اصلی CQRS ساده است. اما می تواند منجر به طراحی برنامه پیچیده تری شود، به خصوص اگر شامل الگوی Event Sourcing باشد.(حداقل 3 یا 4 برابر بیشتر از آنچه که معمولاً هست، با خطوط کد مواجه خواهید شد. اما هر چیزی بهایی دارد. این بهای ناچیزی است که باید هنگام دریافت ویژگی ها و امکانات عالی با این الگو پرداخت.)ثبات نهایی(Eventual consistency) اگر پایگاه داده خواندن و نوشتن را از هم جدا کنید، داده های خوانده شده ممکن است قدیمی شده باشند. مدل خوانده شده باید به‌روزرسانی شود تا تغییرات را در Write Model منعکس کند، و تشخیص زمانی که کاربر براساس داده‌های خوانده شده قدیمی درخواستی صادر کرده است دشوار است.پیام رسانی(Messaging). اگرچه CQRS به پیام نیازی ندارد، استفاده از پیام برای پردازش دستورات و انتشار رویدادهای به روز رسانی معمول است. در این صورت، برنامه باید با شکست پیام ها یا پیام های تکراری مقابله کند.الگوی CQRS را برای سناریوهای زیر در نظر بگیرید:دامنه های مشارکتی(Collaborative domains) که در آن بسیاری از کاربران به طور موازی به داده های مشابه دسترسی دارند.رابط های کاربری مبتنی بر وظیفه(Task-based user interfaces) که در آن کاربران از طریق یک فرآیند پیچیده به عنوان یک سری مراحل یا با مدل های دامنه پیچیده هدایت می شوند. مدل نوشتن ممکن است مجموعه‌ای از اشیاء مرتبط را به‌عنوان یک واحد برای تغییرات داده‌ها (یک Aggregate، در اصطلاح DDD) در نظر بگیرد و اطمینان حاصل کند که این اشیاء همیشه در یک حالت ثابت هستند. مدل خوانده شده هیچ منطق تجاری یا اعتبار سنجی ندارد و فقط یک DTO را برای استفاده در یک view model برمی گرداند. مدل خواندن در نهایت با مدل نوشتن سازگار است.سناریوهایی که عملکرد خواندن داده ها باید جدا از عملکرد نوشتن داده ها تنظیم شود، به خصوص زمانی که تعداد خواندن ها بسیار بیشتر از تعداد نوشتن ها باشد. سناریوهایی که در آن یک تیم از توسعه دهندگان می توانند بر روی مدل دامنه پیچیده که بخشی از مدل نوشتن است تمرکز کنند و تیم دیگری می توانند بر روی مدل خواندن و رابط های کاربر تمرکز کنند.سناریوهایی که در آن انتظار می‌رود سیستم در طول زمان تکامل یابد و ممکن است چندین نسخه از مدل را شامل شود، یا قوانین تجاری به طور منظم تغییر می‌کنند.سناریوهایی که درآن خرابی موقت یک زیرسیستم نباید بر در دسترس بودن سایر سیستم ها تأثیر بگذارد.این الگو زمانی توصیه نمی شود:دامنه یا قوانین business ساده هستند.یک رابط کاربری ساده به سبک CRUD و عملیات دسترسی به داده کافی است.استفاده از CQRS را در بخش های محدودی از سیستم خود در نظر بگیرید که در آن بیشترین ارزش را دارد.پیاده سازی الگوی CQRS در ASP.NET Core 6 WebApiویژوال استودیو را باز کنید و یک ASP.NET Core WebApi ایجاد کنید. بسته‌های زیر را از طریق Package Manager Console روی پروژه API خود نصب کنید. فقط خطوط زیر را در Package Manager Console خود کپی کنید. تمام بسته های مورد نیاز نصب می شوند. Install-Package Microsoft.EntityFrameworkCore Install-Package Microsoft.EntityFrameworkCore.Relational Install-Package Microsoft.EntityFrameworkCore.SqlServer Install-Package MediatR Install-Package MediatR.Extensions.Microsoft.DependencyInjection Install-Package Swashbuckle.AspNetCore Install-Package Swashbuckle.AspNetCore.Swagger Install-Package Microsoft.EntityFrameworkCore.Toolsاضافه کردن Modelاز آنجایی که ما از یک رویکرد code first پیروی می کنیم، بیایید مدل های داده خود را طراحی کنیم. یک پوشه Models اضافه کنید و یک کلاس جدید به نام Person با propertyهای زیر ایجاد کنید.public class Person { public Guid Id { get; set; } public string Name { get; set; } public string Family { get; set; } public string NationalCode { get; set; } public string MobileNumber { get; set; } public string Email { get; set; } public string Password { get; set; } }لطفا همینجا صبر کنید ....گفتیم ایده CQRS این است که به برنامه اجازه می دهد با مدل های مختلف کار کند. به طور خلاصه، شما یک مدل دارید که داده های مورد نیاز برای به روز رسانی یک رکورد، مدل دیگری برای درج یک رکورد، و مدلی دیگر برای پرس و جو یک رکورد را دارد. این به شما انعطاف پذیری با سناریوهای مختلف و پیچیده می دهد. با اجرای CQRS لازم نیست فقط به یک DTO برای کل عملیات CRUD تکیه کنید.بنابراین هر عملیات مدل داده خود را خواهد داشت و استفاده از یک مدل مشترک برای تمام عملیات CRUD برخلاف ایده CQRS است در نتیجه در ادامه ما برای هر عملیات مدل دیگری را نیز ایجاد خواهیم کرد.اضافه کردن Context Class و Interfaceیک پوشه جدید به نام Context بسازید و یک کلاس به نام ApplicationContext اضافه کنید. این کلاس خاص به ما کمک می کند تا با استفاده از Entity Framework Core ORM به داده ها دسترسی پیدا کنیم. public class ApplicationContext : DbContext { public DbSet<Person> Persons { get; set; } public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options) { } public async Task<int> SaveChangesAsync() { return await base.SaveChangesAsync(); } }نکته - چگونه یک interface را از یک کلاس استخراج کنیم؟اکنون که کلاس را کامل کردیم، اجازه دهید یک راه آسان برای ایجاد یک interface برای هر کلاس مشخص به شما نشان دهم. ویژوال استودیو بسیار قدرتمند از آن چیزی است که ما فکر می کنیم. بنابراین در اینجا نحوه کار به این صورت است.به کلاسی بروید که برای آن به interface نیاز داریم. در اینجا، به کلاس ApplicationContext.cs بروید.کل کلاس را انتخاب کنید.پس از انتخاب، به Edit -> Refactor -> Extract Interface بروید.ویژوال استودیو برای تایید، درخواست خواهد کرد. نام اینترفیسی که باید تولید شود را وارد کنید و بعد Ok.ما interface خود را آماده کرده ایم. تصور کنید وقتی کلاس های نسبتا طولانی داریم، این ویژگی چقدر مفید خواهد بود.خروجی آن به این صورت خواهد بود :public interface IApplicationContext { DbSet<Person> Persons { get; set; } Task<int> SaveChangesAsync(); }پیکربندی سرویس‌های API برای پشتیبانی از Entity Framework Coreبه کلاس Program.cs پروژه API خود بروید. بیایید پشتیبانی از EntityFrameworkCore را اضافه کنیم. فقط این خطوط را اضافه کنید. با این کار EF Core در برنامه Register می شود.builder.Services.AddDbContext<ApplicationContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString(&quotDefaultConnection&quot), b => b.MigrationsAssembly(typeof(ApplicationContext).Assembly.FullName)); });خط 3 در مورد رشته اتصال که به عنوان DefaultConnection نامگذاری شده است می گوید. اما ما چنین ارتباطی را تعریف نکرده ایم، بیایید آن را نیز انجام دهیمتعریف رشته اتصال در appsettings.jsonما باید یک منبع داده را به API متصل کنیم. برای این کار، باید یک رشته اتصال در appsettings.json موجود در پروژه API تعریف کنیم. &quotConnectionStrings&quot: { &quotDefaultConnection&quot: &quotServer=.;Database=SampleCQRSwithMediatR;Trusted_Connection=True;MultipleActiveResultSets=true&quot },ایجاد پایگاه دادهاکنون مدل های خود و رشته اتصال را آماده کرده ایم، تنها کاری که باید انجام دهیم این است که از مدل های تعریف شده یک پایگاه داده تولید کنیم. برای این کار باید از کنسول Package Manager ویژوال استودیو استفاده کنیم. می‌توانید این را با رفتن به Tools -> Nuget Package Manager -> Package Manager Console قبل از ادامه، بیایید بررسی کنیم که آیا مشکلاتی در Build وجود دارد یا خیر. برنامه را یک بار Build کنید تا مطمئن شوید خطایی وجود ندارد، زیرا ممکن است در مراحل بعدی اخطار مناسبی نشان داده نشود. و بعد :add-migration initialupdate-databaseاکنون، ما این پایگاه داده را به API خود متصل می کنیم تا عملیات CRUD را انجام دهیم.الگوی Mediatorهنگام ساخت برنامه‌ها، به‌ویژه برنامه‌های ASP.NET Core، بسیار مهم است که به خاطر داشته باشید که همیشه باید کدهای داخل controllerها را تا حد امکان به حداقل برسانیم. از نظر تئوری، controllerها فقط مکانیزم‌های مسیریابی هستند که یک درخواست را دریافت می‌کنند و آن را به صورت داخلی به سایر سرویس‌ها ارسال می‌کنند و داده‌ها را برمی‌گردانند. واقعاً منطقی نیست که تمام اعتبارسنجی ها و منطق های خود را در کنترلرها قرار دهید.الگوی Mediator به مدیریت وابستگی بین اشیا با جلوگیری از ارتباط مستقیم آنها با یکدیگر کمک می کند. در عوض، ارتباط از طریق یک میانجی انجام می شود. به عنوان مثال، یک سرویس درخواست خود را برای میانجی ارسال می کند که به نوبه خود آن را برای پردازش به request handler مربوطه ارسال می کند. با داشتن یک میانجی در این بین، می‌توانیم درخواست‌ها را از handler آنها جدا کنیم. لازم نیست فرستنده چیزی در مورد handler بداند. الگوی Mediator یکی دیگر از الگوهای طراحی است که به طور چشمگیری coupling بین اجزای مختلف یک برنامه کاربردی را با برقراری ارتباط غیرمستقیم، معمولاً از طریق یک special mediator object، کاهش می‌دهد. اساساً، الگوی Mediator برای پیاده سازی CQRS مناسب است.کتابخانه MediatR کتابخانه MediatR به پیاده سازی Mediator Pattern در دات نت کمک می کند. اولین کاری که باید انجام دهیم این است که بسته های آن را نصب کنیم.Install-Package MediatR Install-Package MediatR.Extensions.Microsoft.DependencyInjectionپیکربندی MediatRbuilder.Services.AddMediatR(Assembly.GetExecutingAssembly()); برخی توسعه دهندگان از دستور فوق که نیازمند استفاده از Reflection در پروژه است استفاده می کنند اما پیشنهاد می کنم بجای آنیک کلاس خالی با نام فرضی "SampleCQRSwithMediatREntrypointEntrypoint" در پروژه SampleCQRSwithMediatREntrypoint ایجاد کنید، این فقط برای اشاره به اسمبلی پروژه ما ایجاد شده است. می توانید در صورت تمایل به جای آن نام هر یک از Handler ها را اضافه کنید.services.AddMediatR(typeof(ProjectNameEntrypoint).Assembly);ایجاد Person Controllerدر پوشه Controllers، یک Empty API Controller جدید اضافه کنید و نام آن را PersonController بگذارید.پیاده سازی عملیات CRUDعملیات CRUD در اصل مخفف Create، Read، Update و Delete است. اینها اجزای اصلی API های RESTFul هستند. بیایید ببینیم چگونه می توانیم آنها را با استفاده از رویکرد CQRS خود پیاده سازی کنیم. یک پوشه به نام PersonFeatures در root directory پروژه وداخل آن زیر پوشه‌های Queries و Command را ایجاد کنید. پوشه Queriesاینجا جایی است که ما پرس و جوها را اصلاحا wire up می کنیم. به عنوان مثال GetAllPersons و GetPersonById. در پوشه PersonFeatures/Queries دو گوشه دیگر به نام های FindPersonById و GetPersonList ایجاد کنید و بترتیب دو کلاس داخل هر یک با نام های GetAllPersonsQuery و GetPersonByIdQuery ایجاد کنید.کوئری برای دریافت همه محصولات/GetAllProductsQuery یادآوری مجدد :گفتیم ایده CQRS این است که به برنامه اجازه می دهد با مدل های مختلف کار کند. به طور خلاصه، شما یک مدل دارید که داده های مورد نیاز برای به روز رسانی یک رکورد، مدل دیگری برای درج یک رکورد، و مدلی دیگر برای پرس و جو یک رکورد را دارد. این به شما انعطاف پذیری با سناریوهای مختلف و پیچیده می دهد. با اجرای CQRS لازم نیست فقط به یک DTO برای کل عملیات CRUD تکیه کنید.بنابراین می بایست یک مدل(class) برای GetAllPersonQuery در پوشه PersonFeatures/Queries/GetPersonsList ایجاد کنیمpublic class GetAllPersonQueryModel : IRequest<List<Person>> { public string Name { get; set; } public string Family { get; set; } public string NationalCode { get; set; } public string MobileNumber { get; set; } public string Email { get; set; } }و برای کلاس GetAllPersonsQueryHandler :برای هر درخواست (Query/Command) باید یک handler وجود داشته باشد. Handler تعریف می کند که وقتی مشتری درخواست خاصی را ارسال می کند چه کاری انجام شود. public class GetAllPersonsQueryHandler : IRequestHandler<GetAllPersonQueryModel, IEnumerable<Person>> { private readonly IApplicationContext _context; public GetAllPersonsQueryHandler(IApplicationContext context) { _context = context; } public async Task<IEnumerable<Person>> Handle(GetAllPersonQueryModel request, CancellationToken cancellationToken) { var personList= await _context.Persons.ToListAsync(); if (personList== null) { return null; } return personList.AsReadOnly(); } }کوئری برای دریافت محصول بر اساس شناسه/GetProductByIdکلاس مدل ما به این صورت خواهد بود:public class GetPersonByIdQueryModel:IRequest<Person> { public Guid Id { get; set; } }و برای کلاس GetPersonByIdQueryHandler : public class GetPersonByIdQueryHandler : IRequestHandler<GetPersonByIdQueryModel, Person> { private readonly IApplicationContext _context; public GetPersonByIdQueryHandler(IApplicationContext context) { _context = context; } public async Task<Person> Handle(GetPersonByIdQueryModel request, CancellationToken cancellationToken) { var person = _context.Persons.Where(a => a.Id == request.Id).FirstOrDefault(); if (person == null) return null; return person; } }دستورات/Commandsدر مسیر ProductFeatures/Commands سه پوشه جدید به نام های Add , Edit , Delete اضافه کنیدو به تناسب نام کلاس های زیر را به پوشه های مربوطه اضافه کنید.1.CreatePersonCommand2.DeletePersonByIdCommand3.UpdatePersonCommandخوب Command برای Create a New Person :کلاس مدل ما به این صورت خواهد بود :public class AddPersonCommandModel:IRequest<Guid> { public string Name { get; set; } public string Family { get; set; } public string NationalCode { get; set; } public string MobileNumber { get; set; } public string Email { get; set; } public string Password { get; set; } public string RepeatPassword { get; set; } }و برای کلاس CreatePersonCommandHandler :public class AddPersonCommandHandler : IRequestHandler<AddPersonCommandModel, Guid> { private readonly IApplicationContext _context; public AddPersonCommandHandler(IApplicationContext context) { _context = context; } public async Task<Guid> Handle(AddPersonCommandModel request, CancellationToken cancellationToken) { var person = new Person { Id = Guid.NewGuid(), Name = request.Name, Email = request.Email, Family = request.Family, MobileNumber = request.MobileNumber, NationalCode = request.NationalCode, Password = request.Password }; context.Persons.Add(person); await _context.SaveChangesAsync(); return person.Id; } }و Command برای Delete a Person By Id :کلاس مدل ما به این صورت خواهد بود :public class DeletePersonCommandModel : IRequest<Guid> { public Guid Id { get; set; } }و برای کلاس DeletePersonCommandHandler :public class DeletePersonCommandHandler : IRequestHandler<DeletePersonCommandModel, Guid> { private readonly IApplicationContext _context; public DeletePersonCommandHandler(IApplicationContext context) { _context = context; } public async Task<Guid> Handle(DeletePersonCommandModel request, CancellationToken cancellationToken) { var person = await _context.Persons.Where(c => c.Id == request.Id).FirstOrDefaultAsync(); if (person == null) return default; _context.Persons.Remove(person); await _context.SaveChangesAsync(); return person.Id; } } حالا Command برای Update a Person :توجه داشته باشید در برنامه های واقعی و بعنوان مثال در اینجا Update معمولا ما تمام اطلاعات مدل خود را به یکباره بروزرسانی نمی کنیم شاید یک مدل جداگانه برای بروزرسانی کلمه عبور داشته باشیم یا یک مدل برای بروزرسانی تصویر پروفایل و ... کلاس مدل ما به این صورت خواهد بود :public class EditPersonCommandModel : IRequest<Guid> { public Guid Id { get; set; } public string Name { get; set; } public string Family { get; set; } public string NationalCode { get; set; } public string MobileNumber { get; set; } public string Email { get; set; } }و برای کلاس EditPersonCommandHandler :public class EditPersonCommandHandler : IRequestHandler<EditPersonCommandModel, Guid> { private readonly IApplicationContext _context; public EditPersonCommandHandler(IApplicationContext context) { _context = context; } public async Task<Guid> Handle(EditPersonCommandModel request, CancellationToken cancellationToken) { var person = _context.Persons.Where(a => a.Id == request.Id).FirstOrDefault(); if (person == null) { return default; } else { person.Name = request.Name; person.Family = request.Family; person.NationalCode = request.NationalCode; person.MobileNumber = request.MobileNumber; person.Email = request.Email; _context.Persons.Update(person); await _context.SaveChangesAsync(); return person.Id; } } }بیایید بریم سراغ Person Controller :[Route(&quotapi/[controller]&quot)] [ApiController] public class PersonController : ControllerBase { private readonly IMediator mediator; public PersonController(IMediator mediator) { this.mediator = mediator; } [HttpPost] public async Task<IActionResult> Create(AddPersonCommandModel command) { return Ok(await mediator.Send(command)); } [HttpGet] public async Task<IActionResult> GetAll() { return Ok(await mediator.Send(new GetAllPersonQueryModel())); } [HttpGet(&quot{id}&quot)] public async Task<IActionResult> GetById(Guid id) { return Ok(await mediator.Send(new GetPersonByIdQueryModel { Id = id })); } [HttpDelete(&quot{id}&quot)] public async Task<IActionResult> Delete(Guid id) { return Ok(await mediator.Send(new DeletePersonCommandModel { Id = id })); } [HttpPut(&quot{id}&quot)] public async Task<IActionResult> Update(Guid id, EditPersonCommandModel command) { if (id != command.Id) { return BadRequest(); } return Ok(await mediator.Send(command)); } }از آنجایی که کار ما تمام شده است، بیایید نتیجه را بررسی کنیم. برای این کار از Swagger UI استفاده می کنیم. ما قبلا package را نصب کرده ایم. بیایید آن را پیکربندی کنیم.پیکربندی Swaggerاین قطعه کد را به Program.cs اضافه کنید:#region Swagger builder.Services.AddSwaggerGen(c => { c.SwaggerDoc(&quotv1&quot, new OpenApiInfo { Version = &quotv1&quot, Title = &quotSample CQRS With MediatR.WebApi&quot, }); }); #endregionسپس این Configure method را اضافه کنید:#region Swagger app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint(&quot/swagger/v1/swagger.json&quot, &quotSampleCQRSwithMediatR.WebApi&quot); }); #endregionمشاهده سورس پروژه در GitHubبیشتر بخوانید : آیا بهمراه CQRS باید از الگوی Repository استفاده کنیم؟بیشتر بخوانید : Implementing DDD - Clean Architectureبیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Corehttps://zarinp.al/farshidazizi