هرCQRS یک الگوی طراحی نرمافزار است که عملیات خواندن (پرسوجوها) را از عملیات نوشتن (دستورات) جدا میکند. به جای استفاده از یک مدل برای مدیریت دو، آنها را به دو مدل مجزا تقسیم میکنید:

- دستورات: وضعیت سیستم را تغییر میدهند (مثلاً ایجاد، بهروزرسانی، حذف).
- پرسوجوها: دادهها را بدون تغییر آن بازیابی میکنند.
این جداسازی به هر طرف اجازه میدهد تا به طور مستقل برای عملکرد، مقیاسپذیری و امنیت بهینه شود.
🏗️ چرا از CQRS استفاده کنیم؟
سیستمهای CRUD سنتی از یک مدل برای همه چیز استفاده میکنند که میتواند منجر به موارد زیر شود:
- گلوگاههای عملکرد هنگام رقابت عملیات خواندن و نوشتن.
- مدلهای داده پیچیده که سعی در برآورده کردن نیازهای متناقض دارند.
- خطرات امنیتی ناشی از همپوشانی الگوهای دسترسی.
CQRS این مشکل را با فراهم کردن موارد زیر حل میکند:
- مقیاسبندی خواندن و نوشتن به صورت مستقل.
- استفاده از پایگاههای داده یا ساختارهای داده مختلف برای هر کدام.
- سادهسازی منطق کسبوکار با هماهنگسازی دستورات با اقدامات دنیای واقعی
🔧 اجزای اصلی
- Command: نشاندهندهی قصد تغییر وضعیت است (مثلاً CreateOrder)
- Command Handler: دستور را پردازش کرده و تغییرات را اعمال میکند.
- Query: دادهها را بازیابی میکند (مثلاً GetOrderDetails)
- Query Handler: دستور را اجرا کرده و نتایج را برمیگرداند.
- Event: پس از پردازش یک دستور منتشر میشود (در Event Sourcing استفاده میشود).
🚀 چه زمانی از CQRS استفاده کنیم
CQRS در سناریوهایی مانند موارد زیر میدرخشد:
- منطق تجاری پیچیده که در آن خواندن و نوشتن تفاوت قابل توجهی دارند.
- سیستمهای با کارایی بالا با ترافیک خواندن سنگین.
- معماریهای رویداد محور یا سیستمهایی که از Event Sourcing استفاده میکنند.
اما مراقب باشید - این پیچیدگی را افزایش میدهد. برای برنامههای ساده CRUD، ممکن است بیش از حد باشد.
مثال عملی
در ادامه یک مثال عملی از پیاده سازی CQRS را ارائه می دهیم.
🧱 نمای کلی ساختار پروژه
/Domain Product.cs /Application /Commands CreateProductCommand.cs CreateProductHandler.cs /Queries GetProductByIdQuery.cs GetProductByIdHandler.cs /Core ICommand.cs IQuery.cs ICommandHandler.cs IQueryHandler.cs Dispatcher.cs /Infrastructure ProductDbContext.cs ProductRepository.cs
1️⃣ تعریف اینترفیس های اصلی
// Core/ICommand.cs public interface ICommand { } // Core/IQuery<TResult>.cs public interface IQuery<TResult> { } // Core/ICommandHandler.cs public interface ICommandHandler<TCommand> where TCommand : ICommand { Task HandleAsync(TCommand command); } // Core/IQueryHandler.cs public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult> { Task<TResult> HandleAsync(TQuery query); }
2️⃣ ایجاد یک Dispatcher
// Core/Dispatcher.cs public class Dispatcher { private readonly IServiceProvider _provider; public Dispatcher(IServiceProvider provider) { _provider = provider; } public Task SendAsync<TCommand>(TCommand command) where TCommand : ICommand { var handler = _provider.GetRequiredService<ICommandHandler<TCommand>>(); return handler.HandleAsync(command); } public Task<TResult> QueryAsync<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult> { var handler = _provider.GetRequiredService<IQueryHandler<TQuery, TResult>>(); return handler.HandleAsync(query); } }
3️⃣ Domain Model
// Domain/Product.cs public class Product { public Guid Id { get; set; } public string Name { get; set; } = string.Empty; public decimal Price { get; set; } }
4️⃣ Command & Handler
// Application/Commands/CreateProductCommand.cs public class CreateProductCommand : ICommand { public string Name { get; set; } = string.Empty; public decimal Price { get; set; } } // Application/Commands/CreateProductHandler.cs public class CreateProductHandler : ICommandHandler<CreateProductCommand> { private readonly ProductDbContext _context; public CreateProductHandler(ProductDbContext context) { _context = context; } public async Task HandleAsync(CreateProductCommand command) { var product = new Product { Id = Guid.NewGuid(), Name = command.Name, Price = command.Price }; _context.Products.Add(product); await _context.SaveChangesAsync(); } }
5️⃣ Query & Handler
// Application/Queries/GetProductByIdQuery.cs public class GetProductByIdQuery : IQuery<Product?> { public Guid Id { get; set; } } // Application/Queries/GetProductByIdHandler.cs public class GetProductByIdHandler : IQueryHandler<GetProductByIdQuery, Product?> { private readonly ProductDbContext _context; public GetProductByIdHandler(ProductDbContext context) { _context = context; } public async Task<Product?> HandleAsync(GetProductByIdQuery query) { return await _context.Products.FindAsync(query.Id); } }
6️⃣ Register Services
// Program.cs builder.Services.AddDbContext<ProductDbContext>(opt => opt.UseInMemoryDatabase("ProductDb")); builder.Services.AddScoped<Dispatcher>();
builder.Services.AddScoped<ICommandHandler<CreateProductCommand>, CreateProductHandler>();
builder.Services.AddScoped<IQueryHandler<GetProductByIdQuery, Product?>, GetProductByIdHandler>();
7️⃣ Controller Example
// Controllers/ProductController.cs [ApiController] [Route("api/[controller]")] public class ProductController : ControllerBase { private readonly Dispatcher _dispatcher; public ProductController(Dispatcher dispatcher) { _dispatcher = dispatcher; } [HttpPost] public async Task<IActionResult> Create(CreateProductCommand command) { await _dispatcher.SendAsync(command); return Ok(); } [HttpGet("{id}")] public async Task<IActionResult> GetById(Guid id) { var product = await _dispatcher.QueryAsync<GetProductByIdQuery, Product?>(new GetProductByIdQuery { Id = id }); return product is null ? NotFound() : Ok(product); } }