تواجه تطبيقات المؤسسات الحديثة تحدياً مزدوجاً: يجب أن تتعامل مع عمليات الكتابة المعقدة والثابتة، بينما تدعم في الوقت نفسه استعلامات القراءة المرنة وعالية الإنتاجية. غالباً ما تعاني البنية التقليدية لـ CRUD (إنشاء، قراءة، تحديث، حذف) من هذا العبء، مما يؤدي إلى ارتباط وثيق، واختناقات في الأداء، وهشاشة في سلامة البيانات. هنا يبرز دور CQRS (فصل مسؤولية الأوامر عن الاستعلامات) وEvent Sourcing (تتبع الأحداث).
على الرغم من مناقشتها غالباً معاً، فإن هذه الأنماط تحل مشكلات متميزة. يفصل CQRS بين منطق تحديث الحالة (الأوامر) ومنطق استرجاع الحالة (الاستعلامات). يغير Event Sourcing طريقة حفظ الحالة—بدلاً من تخزين الحالة الحالية، نقوم بتخزين تسلسل من الأحداث يصف كل تغيير. معاً، يشكلان مزيجاً قوياً لبناء نظم قوية، وقابلة للتدقيق، وقابلة للتوسع.
فصل القراءة والكتابة باستخدام CQRS
في البنية القياسية، تخدم مخطط قاعدة بيانات واحد كل من احتياجات القراءة والكتابة. مع تطور المتطلبات، تصبح نماذج الكتابة معقدة بقواعد اتساق صارمة، بينما تتطلب نماذج القراءة فهرسة مرنة وتطبيعاً عكسياً (Denormalization) للأداء. يعالج CQRS هذا الأمر من خلال إدخال نماذج منفصلة.
خذ في الاعتبار تطبيقاً مصرفياً. يتطلب كتابة معاملة تحققاً صارماً، وفحوصات للتزامن، واتساقاً فورياً. قد يتطلب قراءة أرصدة الحسابات تجميع البيانات من مصادر متعددة لعرض لوحة المعلومات. من خلال فصل هذه الجوانب، يمكنك تحسين كل نموذج بشكل مستقل.
إليك تمثيلاً مفاهيمياً لهيكل معالج CQRS:
class TransferMoneyCommandHandler {
constructor(eventStore, accountRepository) {
this.eventStore = eventStore;
this.accountRepository = accountRepository;
}
async execute(command) {
// 1. استرجع الجذر التجميعي
const account = await this.accountRepository.getById(command.SourceAccountId);
// 2. طبق منطق الأعمال
account.transfer(command.Amount, command.DestinationAccountId);
// 3. احفظ الأحداث الجديدة (وليس الحالة)
const events = account.getUncommittedEvents();
await this.eventStore.append(events);
// 4. حدّث نموذج القراءة بشكل غير متزامن
this.publishEventsToReadModel(events);
}
}
بناء سجل تدقيق باستخدام Event Sourcing
يأخذ Event Sourcing جانب "الكتابة" من CQRS خطوة إلى الأمام. بدلاً من حفظ الحالة الحالية للكائن، تقوم بحفظ قائمة بالأحداث التي حدثت. الحالة الحالية هي مجرد إسقاط (Projection) لهذه الأحداث.
يوفر هذا النهج قابلية تدقيق متأصلة. لا تحتاج إلى إنشاء جداول منفصلة لتتبع التغييرات؛ التاريخ هو البيانات نفسها. يتم التقاط كل إجراء—من تسجيل دخول المستخدم إلى تغيير السعر—كحدث غير قابل للتغيير. هذا أمر بالغ الأهمية للامتثال التنظيمي وتصحيح أخطاء سير العمل المعقد.
على سبيل المثال، إذا ادعى مستخدم أن حالة طلبه غير صحيحة، يمكنك إعادة تشغيل سجل الأحداث الكامل لهذا معرف الطلب لمعرفة ما حدث بالضبط، ومتى حدث، ومن قام به.
إعادة بناء الحالة والإسقاطات (Projections)
أحد أهم التحولات في هذه البنية هو التعامل مع عمليات القراءة. بما أن مخزن الكتابة يحتوي فقط على الأحداث، فأنت بحاجة إلى طريقة للإجابة على الاستعلامات بكفاءة. يتم ذلك من خلال الإسقاطات (Projections). الإسقاطات هي نماذج قراءة تشترك في الأحداث وتحدّث مخازن البيانات غير الطبيعية (Denormalized) المحسّنة للاستعلام.
تخيل أنك بحاجة إلى عرض قائمة بالمعاملات الأخيرة مرتبة حسب التاريخ. بدلاً من استعلام سجل الأحداث الخام (وهو أمر مكلف)، لديك إسقاط يستمع إلى TransactionCreatedEvent ويكتب البيانات ذات الصلة في قاعدة بيانات قراءة مخصصة (مثل Elasticsearch أو مخزن NoSQL).
يسمح هذا الفصل لنظامك بالتوسع أفقياً. يمكنك إضافة المزيد من نسخ القراءة للاستعلام لتحميل أحجام الاستعلامات العالية دون التأثير على أداء الكتابة.
اعتبارات عملية وتحديات
على الرغم من قوتها، فإن CQRS وEvent Sourcing يقدمان تعقيداً. يجب عليك إدارة الاتساق النهائي (Eventual Consistency)، والتأكد من أن نموذج القراءة يتم تحديثه في النهاية بعد أمر الكتابة. تحتاج أيضاً إلى أدوات قوية للتعامل مع تطور مخطط الأحداث؛ مع تغير منطق أعمالك، يجب أن يتكيف مخطط الأحداث دون كسر قدرات إعادة تشغيل البيانات التاريخية.
بالإضافة إلى ذلك، قد يكون تصحيح الأخطاء أصعب لأن الحالة ليست مرئية مباشرة في قاعدة البيانات. ومع ذلك، فإن المقايضة تستحق العناء للنظم التي تتطلب قابلية عالية للتوسع، وسجلات تدقيق صارمة، ومنطق أعمال معقد.
الخاتمة
لا يعد CQRS وEvent Sourcing حلولاً سحرية، لكنهما أداتان أساسيتان لتحديات معمارية محددة. يتيحان للمطورين بناء نظم ليست قابلة للتوسع والأداء فحسب، بل أيضاً غنية بالرؤى بسبب تاريخها غير القابل للتغيير. بالنسبة للمطورين من المستوى المتوسط إلى المتقدم الذين يتطلعون إلى التعامل مع سير العمل التجاري المعقد، فإن إتقان هذه الأنماط خطوة حاسمة نحو النضج المعماري.