System Design

إتقان CQRS: فصل عمليات القراءة والكتابة لتصميم أنظمة قابلة للتوسع

في مجال تصميم الأنظمة الموزعة، غالباً ما يكون نموذج CRUD التقليدي (إنشاء، قراءة، تحديث، حذف) نقطة البداية. ومع ذلك، مع زيادة تعقيد التطبيقات وحجمها، يمكن أن تصبح طبقة قاعدة البيانات الموحدة عنق زجاجة. هنا يأتي دور فصل مسؤولية الأوامر والاستعلامات (CQRS). يقدم هذا النمط المعماري، الذي شهده غريغ يونغ وإيريك إيفانز، حلاً متيناً لفصل اهتمامات قراءة البيانات وكتابتها، مما يسمح للأنظمة بالتوسع بشكل مستقل وتحسين الأداء للعمليات المحددة.

المفهوم الأساسي: لماذا الفصل؟

في جوهره، يعتمد CQRS على مبدأ بسيط: الأوامر والاستعلامات لها خصائص مختلفة. الأمر يغير حالة النظام (مثل وضع طلب)، بينما الاستعلام يسترجع المعلومات دون آثار جانبية (مثل عرض سجل الطلبات). في قاعدة البيانات العلائقية القياسية، غالباً ما تتنافس هذه العمليات على نفس الموارد، مما يؤدي إلى التعارض ومشاكل الأداء.

من خلال فصل هذه العمليات، يسمح لك CQRS باستخدام نماذج بيانات مختلفة للقراءة والكتابة. قد تستخدم قاعدة بيانات علائقية مطابقة لمعايير ACID ومطابقة بشدة للتعامل مع المعاملات (نموذج الكتابة)، وقاعدة بيانات NoSQL غير مطابقة ومحسنة للقراءة أو مستودع بيانات لعرض التقارير (نموذج القراءة).

الفوائد الرئيسية لـ CQRS

اعتماد CQRS ليس حلاً سحرياً، لكنه يوفر مزايا مميزة في سيناريوهات محددة:

  1. القابلية للتوسع: يمكنك توسيع نطاق نسخ القراءة والخوادم المكتوبة بشكل مستقل بناءً على الحمل.
  2. الأمان: يمكن أن يكون التحكم في الوصول أكثر دقة. قد يكون لدى الخدمة إذن لتحديث ملفات تعريف المستخدمين ولكن ليس لحذفها.
  3. البساطة: يمكن لنموذج الكتابة التركيز بشكل صارم على منطق الأعمال وسلامتها، بينما يركز نموذج القراءة بشكل كامل على العرض والأداء.
  4. إمكانية التدقيق: يتوافق CQRS بشكل طبيعي مع مصدر الأحداث (Event Sourcing)، حيث يتم تسجيل كل تغيير كحدث غير قابل للتغيير، مما يوفر سجلاً تدقيقياً كاملاً.

تنفيذ CQRS: مثال عملي

لننظر في كيفية ظهور ذلك في بنية خدمة نموذجية على غرار .NET أو Java. لاحظ كيف يميز الواجهة بوضوح بين الأوامر والاستعلامات.

// واجهة الأمر (نموذج الكتابة)
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; }
}

// واجهة الاستعلام (نموذج القراءة)
public interface IQuery<TResult> {
}

public class GetOrderDetailsQuery : IQuery<OrderDetailsDto> {
    private readonly Guid _orderId;

    public GetOrderDetailsQuery(Guid orderId) {
        _orderId = orderId;
    }
}

// تنفيذ المعالج
public class OrderHandler {
    // يعالج تغييرات الحالة
    public void Handle(PlaceOrderCommand command) {
        var order = new Order(command.Id(), command.Items());
        // الحفظ في قاعدة بيانات الكتابة
        _repository.Save(order);
        
        // نشر الحدث لتحديث نموذج القراءة
        _eventPublisher.Publish(new OrderPlacedEvent(command.Id()));
    }

    // يعالج استرجاع البيانات
    public OrderDetailsDto Handle(GetOrderDetailsQuery query) {
        // الاستعلام من قاعدة بيانات القراءة (عرض محسن)
        return _readRepository.GetOrderDetails(query.OrderId());
    }
}

في هذا المثال، تتفاعل طريقة Handle الخاصة بالأمر مع مصدر الحقيقة، وتحفظ التغيير، وتطلق حدثاً. يمكن بعد ذلك استهلاك هذا الحدث بواسطة عملية منفصلة تقوم بتحديث قاعدة بيانات القراءة غير المطابقة، مما يضمن أن البيانات المتاحة للاستعلامات متسقة بشكل نهائي.

الخاتمة

يُعد CQRS نمطاً قوياً يتيح بنية عالية الأداء وقابلة للتوسع من خلال الاعتراف بأن عمليات القراءة والكتابة مختلفة جوهرياً. وعلى الرغم من أنه يقدم تعقيداً فيما يتعلق بتسوية البيانات وإدارة البنية التحتية، فإن فوائده للتطبيقات واسعة النطاق والمكثفة البيانات لا يمكن إنكارها. بالنسبة للمطورين الذين يهدفون إلى بناء أنظمة مرنة يمكنها التعامل مع عبء عمل هائل ومنطق أعمال معقد، فإن إتقان CQRS هو خطوة أساسية في رحلتهم المعمارية.

Share: