در حوزه طراحی سیستمهای توزیعشده، مدل سنتی CRUD (ایجاد، خواندن، بهروزرسانی، حذف) اغلب به عنوان نقطه شروع عمل میکند. با این حال، همانطور که برنامهها از نظر پیچیدگی و مقیاس رشد میکنند، یک لایه پایگاه داده یکپارچه (Monolithic) میتواند به گلوگاه تبدیل شود. در اینجا جداسازی مسئولیت فرمان و پرسوجو (CQRS) وارد میشود. این الگوی معماری که توسط گرگ یانگ و اریک اوانز محبوب شد، راهحلی مستحکم برای جداسازی نگرشهای مربوط به خواندن و نوشتن دادهها ارائه میدهد و به سیستمها اجازه میدهد تا به صورت مستقل مقیاسپذیر شوند و برای عملیات خاص بهینهسازی شوند.
مفهوم اصلی: چرا جداسازی کنیم؟
در قلب CQRS یک اصل ساده نهفته است: فرمانها و پرسوجوها ویژگیهای متفاوتی دارند. یک فرمان وضعیت سیستم را تغییر میدهد (مثلاً ثبت سفارش)، در حالی که یک پرسوجو اطلاعات را بدون ایجاد اثرات جانبی بازیابی میکند (مثلاً مشاهده تاریخچه سفارش). در یک پایگاه داده رابطهای استاندارد، این عملیات اغلب برای منابع یکسان رقابت میکنند که منجر به تقابل و مشکلات عملکردی میشود.
با جداسازی این عملیات، CQRS به شما امکان میدهد از مدلهای دادهای متفاوتی برای خواندن و نوشتن استفاده کنید. ممکن است یک پایگاه داده رابطهای نرمالسازیشده و مطابق با اصول ACID برای مدیریت تراکنشها (مدل نوشتن) و یک پایگاه داده NoSQL یا انبار داده غیرنرمالسازیشده و بهینهشده برای خواندن برای نمایش گزارشها (مدل خواندن) داشته باشید.
مزایای کلیدی CQRS
پذیرش CQRS یک راهحل جادویی نیست، اما در سناریوهای خاص مزایای متمایزی ارائه میدهد:
- مقیاسپذیری: میتوانید کپیهای خواندن و سرورهای نوشتن خود را بر اساس بار کاری به صورت مستقل مقیاس دهید.
- امنیت: کنترل دسترسی میتواند دانهدانهتر باشد. یک سرویس ممکن است مجوز بهروزرسانی پروفایل کاربران را داشته باشد اما نه حذف آنها را.
- سادگی: مدل نوشتن میتواند صرفاً بر منطق کسبوکار و یکپارچگی تمرکز کند، در حالی که مدل خواندن صرفاً بر ارائه و عملکرد تمرکز دارد.
- قابلیت حسابرسی: CQRS به طور طبیعی با منبعسازی رویداد (Event Sourcing) جفت میشود، جایی که هر تغییر به عنوان یک رویداد غیرقابل تغییر ثبت میشود و یک رد کامل حسابرسی را فراهم میکند.
پیادهسازی CQRS: یک مثال عملی
بیایید نگاهی بیندازیم که این موضوع در یک معماری سرویس معمولی به سبک .NET یا Java چگونه به نظر میرسد. توجه کنید که چگونه رابط به وضوح بین فرمانها و پرسوجوها تمایز قائل میشود.
// The Command Interface (Write Model)
public interface ICommand {
Guid Id();
}
public class PlaceOrderCommand : ICommand {
private readonly Guid _orderId;
private readonly List<OrderItem> _items;
public PlaceOrderCommand(Guid orderId, List<OrderItem> items) {
_orderId = orderId;
_items = items;
}
public Guid Id() { return _orderId; }
}
// The Query Interface (Read Model)
public interface IQuery<TResult> {
}
public class GetOrderDetailsQuery : IQuery<OrderDetailsDto> {
private readonly Guid _orderId;
public GetOrderDetailsQuery(Guid orderId) {
_orderId = orderId;
}
}
// The Handler Implementation
public class OrderHandler {
// Handles state changes
public void Handle(PlaceOrderCommand command) {
var order = new Order(command.Id(), command.Items());
// Persist to Write DB
_repository.Save(order);
// Publish Event to Update Read Model
_eventPublisher.Publish(new OrderPlacedEvent(command.Id()));
}
// Handles data retrieval
public OrderDetailsDto Handle(GetOrderDetailsQuery query) {
// Query from Read DB (optimized view)
return _readRepository.GetOrderDetails(query.OrderId());
}
}
در این مثال، متد Handle برای فرمان با منبع حقیقت تعامل دارد، تغییر را ذخیره میکند و یک رویداد را راهاندازی میکند. آن رویداد سپس میتواند توسط یک فرآیند جداگانه مصرف شود که پایگاه داده خواندن غیرنرمالسازیشده را بهروزرسانی میکند، اطمینان حاصل میشود که دادههای موجود برای پرسوجوها در نهایت سازگار هستند.
نتیجهگیری
CQRS یک الگوی قدرتمند است که با به رسمیت شناختن اینکه خواندن و نوشتن عملیاتاً اساساً متفاوت هستند، معماریهای با عملکرد بالا و مقیاسپذیر را امکانپذیر میسازد. اگرچه این الگو پیچیدگیهایی را در مورد سازگاری داده و مدیریت زیرساخت به همراه دارد، اما مزایای آن برای برنامههای بزرگمقیاس و دادهمحور انکارناپذیر است. برای توسعهدهندگانی که هدفشان ساخت سیستمهای مقاوم است که میتوانند از طریقبسیار بالا و منطق کسبوکار پیچیده را مدیریت کنند، تسلط بر CQRS یک گام ضروری در سفر معماری آنها است.