CQRS و Event Sourcing في JavaScript: بنية للأنظمة المعقدة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا CQRS و Event Sourcing ليسا "للمختارين فقط"؟

\n\n

عندما نسمع عبارتي "CQRS" أو "Event Sourcing"، يتخيل العديد من مطوري JavaScript شيئًا من عالم البنية المؤسسية في Java أو C#. مخططات معقدة، أحداث لا نهائية، مخازن غريبة... ولكن ماذا لو أخبرتك أنه يمكن (ويجب!) استخدام هذه الأنماط في تطبيقات Node.js العادية؟

\n\n

في هذه المقالة، سنفهم كيف يساعد CQRS و Event Sourcing في حل المشكلات الحقيقية: تضارب البيانات، التسجيل المعقد، التوسع، وتصحيح الأخطاء. وكل هذا بلغة JavaScript نقية، بدون سحر أو أطر عمل معقدة.

\n\n

ما هو CQRS (فصل مسؤولية الأوامر والاستعلامات)؟

\n\n

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

\n\n

CQRS هو مبدأ فصل العمليات إلى قناتين:

\n
    \n
  • الأوامر (Commands) — تغير حالة النظام (إنشاء، تحديث، حذف)
  • \n
  • الاستعلامات (Queries) — تقرأ البيانات فقط (قراءة)
  • \n
\n\n

في REST API التقليدي، نستخدم عادةً نفس الكائن للقراءة والكتابة. على سبيل المثال، UserController يحتوي على دوال GET /users/:id و POST /users. يقترح CQRS فصلها إلى طبقات مختلفة.

\n\n

مثال بسيط على CQRS في JavaScript

\n\n
// الأمر (تغيير الحالة)\nclass CreateUserCommand {\n    constructor(name, email) {\n        this.name = name;\n        this.email = email;\n    }\n}\n\nclass UserCommandHandler {\n    async handle(command) {\n        // التحقق، منطق الأعمال، الحفظ\n        return await db.users.insert({\n            name: command.name,\n            email: command.email,\n            created_at: new Date()\n        });\n    }\n}\n\n// الاستعلام (قراءة فقط)\nclass GetUserQuery {\n    constructor(userId) {\n        this.userId = userId;\n    }\n}\n\nclass UserQueryHandler {\n    async handle(query) {\n        // قراءة فقط، لا تغييرات!\n        return await db.users.findOne({ _id: query.userId });\n    }\n}
\n\n

هل يبدو أن هذا يعقد الكود؟ نعم، للوهلة الأولى. ولكن عندما ينمو تطبيقك إلى أكثر من 50 نقطة نهاية، سينقذك فصل الأوامر والاستعلامات من:

\n
    \n
  • تغيير البيانات بطريق الخطأ في استعلامات GET
  • \n
  • مشاكل التخزين المؤقت (من السهل تخزين الاستعلامات مؤقتًا، أما الأوامر فلا)
  • \n
  • التضارب عند العمل المتوازي لعدة مطورين
  • \n
\n\n

Event Sourcing: تذكر كل إجراء

\n\n

تخيل الآن أنه بدلاً من تخزين الحالة الحالية للمستخدم، تقوم بتخزين جميع الأحداث التي حدثت له. ليس "إيفان، 25 سنة، موسكو"، بل تسلسل:

\n
    \n
  1. "تم إنشاء المستخدم: إيفان"
  2. \n
  3. "قام المستخدم بتغيير العمر إلى 25"
  4. \n
  5. "انتقل المستخدم إلى موسكو"
  6. \n
\n\n

Event Sourcing هو نمط يتم فيه حفظ كل تغيير في الحالة كحدث غير قابل للتغيير. يتم حساب الحالة الحالية عن طريق "إعادة تشغيل" جميع الأحداث.

\n\n

تنفيذ مخزن الأحداث (Event Store) في JavaScript

\n\n
class EventStore {\n    constructor() {\n        this.events = [];\n    }\n\n    append(aggregateId, event) {\n        this.events.push({\n            aggregateId,\n            event,\n            timestamp: new Date()\n        });\n    }\n\n    getEvents(aggregateId) {\n        return this.events.filter(e => e.aggregateId === aggregateId);\n    }\n}\n\n// استعادة الحالة من الأحداث\nfunction rebuildState(events) {\n    return events.reduce((state, event) => {\n        switch(event.event.type) {\n            case 'USER_CREATED':\n                return { ...state, name: event.event.name, age: 0 };\n            case                

المدونات

توصيات الكتب