آموزش CQRS در دات نت فارسی

تاریخ: 1404/12/4 ساعت: 11:58 بازدید: 14

CQRS چیست و چرا باید آن را یاد بگیری؟

اگه توی دنیای برنامه‌نویسی دات نت (.NET) فعال هستی، احتمالاً اسم CQRS به گوشت خورده. شاید اول نگرانت کرده باشه، ولی خوشبختانه این الگوی معماری به اندازه‌ای که فکر می‌کنی پیچیده نیست.

CQRS مخفف Command Query Responsibility Segregation است. به زبان ساده یعنی جداسازی مسئولیت بین عملیات نوشتن (Command) و عملیات خواندن (Query) در اپلیکیشن‌ات.

این مفهوم توسط Greg Young مطرح شد و از اصل قدیمی‌تری به نام CQS (Command Query Separation) که توسط Bertrand Meyer ابداع شده بود، الهام گرفته است. تفاوت اصلی اینه که CQRS این جداسازی رو در سطح معماری کل سیستم اعمال می‌کنه، نه فقط در سطح متدها.

مشکل رایج در معماری سنتی (CRUD)

توی روش سنتی، یه مدل واحد برای هر دو عملیات خواندن و نوشتن استفاده می‌کنیم. این روش که بهش CRUD می‌گن، توی پروژه‌های کوچیک خوبه، ولی وقتی پروژه بزرگ می‌شه، دردسرهای جدی شروع می‌شن:

  • مدل دیتا برای خواندن و نوشتن کاملاً متفاوت می‌شه و اجبار داری هر دو رو توی یه کلاس جا بدی
  • کوئری‌های پیچیده روی سیستم نوشتن (Write Side) فشار میارن
  • اسکیل کردن (Scaling) مشکل می‌شه چون Read و Write یکی هستن
  • تست‌نویسی سخت‌تر می‌شه و Coupling بالا می‌ره
  • با رشد تیم، تداخل کارها و کانفلیکت‌های گیت بیشتر می‌شه

مفاهیم کلیدی در CQRS که باید بشناسی

۱. Command (دستور)

یه Command یه درخواست برای تغییر وضعیت سیستم است. Command هیچ مقداری برنمی‌گردونه (یا فقط یه تأیید ساده برمی‌گردونه). مثال:

  • CreateOrderCommand
  • UpdateProductCommand
  • DeleteUserCommand

۲. Query (پرسش)

یه Query یه درخواست برای خواندن اطلاعات است. Query هیچ‌وقت وضعیت سیستم رو تغییر نمی‌ده. مثال:

  • GetOrderByIdQuery
  • GetAllProductsQuery
  • GetUserProfileQuery

۳. Handler (پردازنده)

برای هر Command و هر Query یه Handler جداگانه داریم که مسئول پردازش اون درخواسته. این جداسازی باعث می‌شه هر Handler تنها یه مسئولیت داشته باشه (اصل Single Responsibility).

آموزش MediatR در دات نت: کتابخانه محبوب CQRS

در دنیای دات نت، محبوب‌ترین ابزار برای پیاده‌سازی CQRS کتابخانه MediatR است که توسط Jimmy Bogard ساخته شده. این کتابخانه یه واسطه (Mediator) بین درخواست‌ها و پردازنده‌هاشون ایجاد می‌کنه و کدت رو خیلی تمیز می‌کنه.

نصب MediatR در پروژه ASP.NET Core

با استفاده از NuGet Package Manager یا dotnet CLI:

dotnet add package MediatR

dotnet add package MediatR.Extensions.Microsoft.DependencyInjection

ثبت MediatR در Program.cs

builder.Services.AddMediatR(cfg =>

cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));

مثال عملی کامل: سیستم مدیریت سفارش

بریم سراغ یه مثال واقعی. می‌خوایم یه سیستم ساده مدیریت سفارش بسازیم.

مرحله ۱: تعریف مدل (Entity)

public class Order

{

public int Id { get; set; }

public string CustomerName { get; set; }

public decimal TotalAmount { get; set; }

public DateTime CreatedAt { get; set; }

public OrderStatus Status { get; set; }

}

public enum OrderStatus

{

Pending,

Confirmed,

Shipped,

Delivered

}

مرحله ۲: ساخت Command برای ایجاد سفارش

// Command

public class CreateOrderCommand : IRequest

{

public string CustomerName { get; set; }

public decimal TotalAmount { get; set; }

}

// Command Handler

public class CreateOrderCommandHandler

: IRequestHandler

{

private readonly IOrderRepository _repository;

public CreateOrderCommandHandler(IOrderRepository repository)

{

_repository = repository;

}

public async Task Handle(

CreateOrderCommand request,

CancellationToken cancellationToken)

{

var order = new Order

{

CustomerName = request.CustomerName,

TotalAmount = request.TotalAmount,

CreatedAt = DateTime.UtcNow,

Status = OrderStatus.Pending

};

await _repository.AddAsync(order, cancellationToken);

return order.Id;

}

}

مرحله ۳: ساخت Query برای خواندن سفارش

// Query DTO

public class OrderDto

{

public int Id { get; set; }

public string CustomerName { get; set; }

public decimal TotalAmount { get; set; }

public string Status { get; set; }

}

// Query

public class GetOrderByIdQuery : IRequest

{

public int OrderId { get; set; }

}

// Query Handler

public class GetOrderByIdQueryHandler

: IRequestHandler

{

private readonly IOrderRepository _repository;

public GetOrderByIdQueryHandler(IOrderRepository repository)

{

_repository = repository;

}

public async Task Handle(

GetOrderByIdQuery request,

CancellationToken cancellationToken)

{

var order = await _repository

.GetByIdAsync(request.OrderId, cancellationToken);

if (order == null) return null;

return new OrderDto

{

Id = order.Id,

CustomerName = order.CustomerName,

TotalAmount = order.TotalAmount,

Status = order.Status.ToString()

};

}

}

مرحله ۴: استفاده در Controller

[ApiController]

[Route(“api/[controller]”)]

public class OrdersController : ControllerBase

{

private readonly IMediator _mediator;

public OrdersController(IMediator mediator)

{

_mediator = mediator;

}

[HttpPost]

public async Task CreateOrder(

[FromBody] CreateOrderCommand command)

{

var orderId = await _mediator.Send(command);

return CreatedAtAction(

nameof(GetOrder),

new { id = orderId },

orderId);

}

[HttpGet(“{id}”)]

public async Task GetOrder(int id)

{

var result = await _mediator.Send(

new GetOrderByIdQuery { OrderId = id });

if (result == null) return NotFound();

return Ok(result);

}

}

Pipeline Behavior در MediatR: قدرت واقعی

یکی از قوی‌ترین ویژگی‌های MediatR، Pipeline Behavior است. با این قابلیت می‌تونی Cross-cutting Concerns مثل Logging، Validation و Caching رو بدون آلوده کردن Handler‌هات پیاده کنی.

مثال: پیاده‌سازی Validation Behavior با FluentValidation

public class ValidationBehavior

: IPipelineBehavior

where TRequest : IRequest

{

private readonly IEnumerable> _validators;

public ValidationBehavior(

IEnumerable> validators)

{

_validators = validators;

}

public async Task Handle(

TRequest request,

RequestHandlerDelegate next,

CancellationToken cancellationToken)

{

if (!_validators.Any()) return await next();

var context = new ValidationContext(request);

var validationResults = await Task.WhenAll(

_validators.Select(v =>

v.ValidateAsync(context, cancellationToken)));

var failures = validationResults

.SelectMany(r => r.Errors)

.Where(f => f != null)

.ToList();

if (failures.Count != 0)

throw new ValidationException(failures);

return await next();

}

}

CQRS + Event Sourcing: ترکیب قدرتمند

خیلی وقت‌ها CQRS رو در کنار Event Sourcing می‌بینیم. در Event Sourcing به جای ذخیره وضعیت نهایی، تمام رویدادهایی که منجر به اون وضعیت شدن رو ذخیره می‌کنیم.

این ترکیب مزایای زیادی داره:

  • Audit Trail کامل: هر تغییری که توی سیستم افتاده رو می‌تونی ببینی
  • Temporal Query: می‌تونی وضعیت سیستم در هر لحظه از تاریخ رو بازسازی کنی
  • انعطاف در Read Model: می‌تونی Read Model رو هر وقت خواستی از اول بسازی

🚀 می‌خوای سایتت مثل رقبا در صفحه اول گوگل باشه؟

خیلی از کسب‌وکارها هر روز مشتری از دست می‌دن چون سایتشون دیده نمی‌شه. سئو یه سرمایه‌گذاری مطمئنه که به‌جای هزینه تبلیغات مداوم، یه بار درست انجام بشه و سال‌ها برات درآمد بیاره.

آیا می‌خواهید سایت شما هم مثل رقبا در صفحه اول گوگل باشد و زنگ‌خورهایتان چند برابر شود؟

سئوی سایت خود را به متخصصان ما بسپارید. همین حالا برای مشاوره رایگان با ما تماس بگیرید:

📞 09190994063 - 09376846692

مزایا و معایب CQRS: واقع‌بینانه بررسی کنیم

✅ مزایای CQRS

  • مقیاس‌پذیری مستقل: Read و Write Side رو می‌تونی مستقل از هم اسکیل کنی
  • بهینه‌سازی مدل: مدل خواندن و نوشتن هر کدوم برای کارشون بهینه می‌شن
  • کد تمیزتر و تست‌پذیرتر: هر Handler فقط یه کار انجام می‌ده
  • همکاری تیمی بهتر: تداخل کد بین اعضای تیم کمتر می‌شه
  • امنیت بهتر: می‌تونی کنترل دسترسی رو در سطح Command و Query اعمال کنی

❌ معایب و چالش‌های CQRS

  • پیچیدگی بیشتر: برای پروژه‌های کوچیک، Overhead اضافه ایجاد می‌کنه
  • Eventual Consistency: در سیستم‌های توزیع‌شده، داده‌های Read و Write ممکنه برای لحظه‌ای ناهماهنگ باشن
  • منحنی یادگیری: تیم باید با الگو آشنا بشه
  • کدنویسی بیشتر: برای هر عملیات باید Command/Query و Handler جداگانه بنویسی

CQRS کِی استفاده کنیم؟ کِی نه؟

این سوال خیلی مهمه. CQRS یه Silver Bullet نیست و برای همه پروژه‌ها مناسب نیست.

CQRS مناسب است وقتی:

  • پروژه بزرگ و پیچیده‌ای داری با منطق تجاری سنگین
  • نسبت عملیات خواندن به نوشتن خیلی نامتوازن است (مثلاً ۱۰۰ به ۱)
  • نیاز به مقیاس‌پذیری مستقل برای Read و Write داری
  • تیم بزرگی داری و نیاز به کار موازی وجود داره
  • معماری Microservices یا Domain-Driven Design استفاده می‌کنی

CQRS مناسب نیست وقتی:

  • پروژه‌ات کوچیکه و CRUD ساده کافیه
  • تیم کوچیکی داری و سرعت توسعه اولویته
  • Domain پروژه ساده‌ست و منطق تجاری پیچیده‌ای نداری

ساختار پوشه‌بندی پروژه CQRS

یه ساختار خوب و استاندارد برای پروژه CQRS در دات نت اینطوریه:

📁 src/

📁 Application/

📁 Orders/

📁 Commands/

📁 CreateOrder/

├── CreateOrderCommand.cs

├── CreateOrderCommandHandler.cs

└── CreateOrderCommandValidator.cs

📁 UpdateOrder/

├── UpdateOrderCommand.cs

└── UpdateOrderCommandHandler.cs

📁 Queries/

📁 GetOrderById/

├── GetOrderByIdQuery.cs

├── GetOrderByIdQueryHandler.cs

└── OrderDto.cs

📁 GetAllOrders/

├── GetAllOrdersQuery.cs

└── GetAllOrdersQueryHandler.cs

📁 Common/

📁 Behaviors/

├── ValidationBehavior.cs

├── LoggingBehavior.cs

└── PerformanceBehavior.cs

📁 Domain/

📁 Entities/

└── Order.cs

📁 Events/

└── OrderCreatedEvent.cs

📁 Infrastructure/

📁 Persistence/

└── OrderRepository.cs

📁 WebApi/

📁 Controllers/

└── OrdersController.cs

نکات طلایی برای پیاده‌سازی CQRS حرفه‌ای

  • Command‌ها باید Immutable باشن: از record در C# 9+ استفاده کن
  • DTO‌ها رو از Entity جدا کن: هرگز Entity‌ها رو مستقیم از Query برنگردون
  • Validation رو در Pipeline انجام بده: از FluentValidation + ValidationBehavior استفاده کن
  • Logging رو در Pipeline قرار بده: نه توی Handler‌ها
  • برای Query‌های سنگین از Caching استفاده کن: CachingBehavior می‌تونه کمکت کنه
  • Command‌ها نباید از دیتابیس چیزی بخونن: اگه نیاز داری، از Domain Service استفاده کن

💡 نکته مهم: اگه داری روی یه پروژه از صفر شروع می‌کنی، پیشنهاد می‌کنم ابتدا با معماری ساده‌تر شروع کنی و وقتی پروژه بزرگ‌تر شد و نیاز واقعی به CQRS دیدی، اون موقع Refactor کنی. این رویکرد pragmatic از Over-Engineering جلوگیری می‌کنه.

جمع‌بندی

در این آموزش، با الگوی CQRS در دات نت به صورت کامل آشنا شدیم. یاد گرفتیم که CQRS چیست، چرا لازمه، چطور با MediatR پیاده‌سازی می‌شه و چه زمانی باید ازش استفاده کرد.

مهم‌ترین چیزی که باید به خاطر بسپاری اینه که CQRS یه ابزاره، نه یه هدف. اون رو وقتی استفاده کن که واقعاً به حل مشکلت کمک می‌کنه، نه صرفاً برای اینکه مد روزه.

💼 سایت شما لایق بیشتر دیده شدن است!

دقیقاً همون‌طور که یه معماری خوب مثل CQRS به کدنویست نظم و قدرت می‌ده، سئوی حرفه‌ای هم به کسب‌وکار آنلاینت نظم و قدرت می‌ده. ما با تجربه‌ای که در سئوی وب فارسی داریم، می‌تونیم سایتت رو به جایگاه شایسته‌اش در گوگل برسونیم.

آیا می‌خواهید سایت شما هم مثل رقبا در صفحه اول گوگل باشد و زنگ‌خورهایتان چند برابر شود؟ سئوی سایت خود را به متخصصان ما بسپارید.

همین حالا برای مشاوره رایگان با ما تماس بگیرید:

📞 09190994063 - 09376846692

سوالات متداول درباره CQRS در دات نت

❓ آیا CQRS همون Clean Architecture است؟

نه، این دو مفهوم متفاوت هستند. Clean Architecture یه سبک معماری کلیه که لایه‌های مختلف برنامه رو از هم جدا می‌کنه. CQRS یه الگوی طراحی است که می‌تونه داخل Clean Architecture یا هر معماری دیگه‌ای پیاده‌سازی بشه. اما در عمل خیلی وقت‌ها این دو تا رو با هم و در کنار MediatR استفاده می‌کنن.

❓ آیا CQRS فقط برای پروژه‌های بزرگ مناسبه؟

بله، به طور کلی CQRS وقتی ارزش واقعیش رو نشون می‌ده که پروژه منطق تجاری پیچیده یا نیاز به مقیاس‌پذیری بالا داشته باشه. برای پروژه‌های کوچیک، این الگو ممکنه Overhead غیرضروری ایجاد کنه. البته خیلی از توسعه‌دهنده‌ها حتی برای پروژه‌های متوسط هم از CQRS با MediatR استفاده می‌کنن چون کد رو منظم‌تر و تست‌پذیرتر می‌کنه.

❓ تفاوت CQRS و CQS چیست؟

CQS (Command Query Separation) یه اصل طراحی در سطح متد است که می‌گه یه متد یا باید فقط داده برگردونه (Query) یا فقط وضعیت رو تغییر بده (Command)، نه هر دو. CQRS این مفهوم رو در سطح کل معماری سیستم اعمال می‌کنه و مدل‌های جداگانه‌ای برای Read و Write ایجاد می‌کنه.

❓ چطور CQRS رو توی پروژه موجود (Legacy) اضافه کنم؟

بهترین روش اینه که به تدریج (Incrementally) این کار رو انجام بدی. اول کتابخانه MediatR رو نصب کن. بعد یکی از Use Case‌های جدید رو به جای روش قدیمی با CQRS بنویس. کم‌کم بخش‌های دیگه رو هم Refactor کن. هیچ‌وقت سعی نکن یه‌شبه همه چیز رو تغییر بدی چون ریسک بالایی داره.

❓ آیا در CQRS باید حتماً دو دیتابیس جداگانه داشته باشیم؟

خیر، این یه سوءتفاهم رایجه. داشتن دو دیتابیس جداگانه برای Read و Write فقط یکی از پیاده‌سازی‌های ممکن CQRS است، نه یه الزام. اکثر پروژه‌ها با یه دیتابیس واحد شروع می‌کنن و فقط مدل‌های منطقی Read و Write رو جدا می‌کنن. جداسازی فیزیکی دیتابیس‌ها فقط وقتی منطقی‌ه که نیاز واقعی به مقیاس‌پذیری مستقل وجود داشته باشه.

❓ بهترین پکیج‌های NuGet برای پیاده‌سازی CQRS در دات نت کدامند؟

محبوب‌ترین پکیج‌ها عبارتند از: MediatR (برای پیاده‌سازی الگوی Mediator)، FluentValidation (برای اعتبارسنجی Command‌ها)، AutoMapper (برای تبدیل Entity به DTO)، و Scrutor (برای ثبت خودکار Handler‌ها در DI Container). ترکیب این چهار پکیج یه پایه خیلی قوی برای CQRS در ASP.NET Core ایجاد می‌کنه.

نظرات کاربران