لماذا CQRS و Event Sourcing ليسا مجرد كلمات رنانة
\n\nتخيل أن الكود الخاص بك هو متحف ضخم. عادةً ما نخزن فقط أحدث نسخة من القطعة الأثرية: "هكذا يبدو الطلب الآن". ولكن ماذا لو أردنا معرفة كيف وصل الطلب إلى هذه الحالة؟ من ضغط على زر "إرسال"؟ متى تغيرت الحالة؟ هنا يأتي دور CQRS (فصل مسؤولية الاستعلام عن الأوامر) و Event Sourcing في Kotlin.
\n\nفي هذا الدليل، لن ننسخ النظرية من ويكيبيديا فحسب. سنقوم بتحليل أمثلة حقيقية، وكتابة كود، وفهم كيف تحول هذه الأنماط فوضى عمليات CRUD إلى بنية منظمة. إذا كنت قد سئمت من "كود السباغيتي" وترغب في بناء أنظمة يسهل توسيع نطاقها وتصحيح أخطائها، فأنت في المكان الصحيح.
\n\nما هو CQRS بكلمات بسيطة
\n\nفي البنية القياسية، نستخدم نفس النماذج لقراءة البيانات وكتابتها. هذا يشبه تناول الحساء وتحريكه بنفس الملعقة. يقترح CQRS فصل المسؤولية:
\n\n- \n
- Command (أمر) — يغير الحالة. طلبات الكتابة (إنشاء، تحديث، حذف). \n
- Query (استعلام) — يقرأ الحالة. عمليات SELECT فقط. \n
لماذا نحتاج هذا؟ تخيل متجرًا إلكترونيًا. عندما يقوم المستخدم بتقديم طلب، يجب علينا التحقق من المخزون، وخصم المنتج، وإنشاء سجل في قاعدة البيانات، وإرسال بريد إلكتروني. هذا أمر معقد. بينما عندما ينظر العميل فقط إلى قائمة طلباته، فهذا استعلام بسيط. خلطهم في طريقة واحدة هو طريق أكيد للأخطاء.
\n\nمزايا CQRS في Kotlin
\n- \n
- الأداء: يمكن تحسين القراءة والكتابة بطرق مختلفة (قواعد بيانات مختلفة، تخزين مؤقت، فهارس). \n
- الأمان: فصل صلاحيات الوصول (المسؤول يكتب، المستخدم يقرأ). \n
- قابلية التوسع: يمكن توزيع الأوامر والاستعلامات على خوادم مختلفة. \n
Event Sourcing: حفظ التاريخ، وليس الحقائق
\n\nإذا كان CQRS يفصل العمليات، فإن Event Sourcing يغير طريقة تخزين البيانات. بدلاً من تخزين الحالة الحالية (على سبيل المثال، "الرصيد: 1000 روبل")، نقوم بتخزين سلسلة من الأحداث: "إيداع 500"، "شراء بمبلغ 200"، "خصم عمولة 50".
\n\nالحالة الحالية هي مجرد نتيجة تطبيق جميع الأحداث بالترتيب. وهذا يعطي قوة خارقة: يمكننا "التراجع" في الزمن، واستعادة الحالة في أي تاريخ، أو تحليل سلوك المستخدمين.
\n\nمثال من الحياة الواقعية: حساب بنكي
\n// حدث\ndata class MoneyDeposited(val amount: BigDecimal, val timestamp: Instant)\ndata class MoneyWithdrawn(val amount: BigDecimal, val timestamp: Instant)\n\n// المُجمّع (الكيان)\nclass BankAccount {\n private var balance: BigDecimal = BigDecimal.ZERO\n private val events = mutableListOf<Any>()\n\n fun deposit(amount: BigDecimal) {\n if (amount <= BigDecimal.ZERO) throw IllegalArgumentException("يجب أن يكون المبلغ موجبًا")\n val event = MoneyDeposited(amount, Instant.now())\n apply(event)\n events.add(event)\n }\n\n fun withdraw(amount: BigDecimal) {\n if (amount > balance) throw IllegalStateException("رصيد غير كافٍ")\n val event = MoneyWithdrawn(amount, Instant.now())\n apply(event)\n events.add(event)\n }\n\n private fun apply(event: Any) {\n when (event) {\n is MoneyDeposited -> balance += event.amount\n is MoneyWithdrawn -> balance -= event.amount\n }\n }\n\n fun getBalance(): BigDecimal = balance\n fun getUncommittedEvents(): List<Any> = events.toList()\n}\n\nكيف يعمل CQRS و Event Sourcing معًا
\n\nهذا ثنائي كلاسيكي. CQRS مسؤول عن فصل القنوات، بينما Event Sourcing مسؤول عن التخزين