لماذا لا تتعامل البنية التقليدية مع المهام الحديثة؟
\n\nهل سبق لك أن واجهت موقفًا حيث تتحول إضافة ميزة بسيطة إلى تطبيق Swift إلى كابوس؟ تظهر الأخطاء، وتُفقد البيانات، وينمو المنطق إلى أبعاد لا تُصدق؟ هل هذا مألوف؟ مرحبًا بك في عالم يصبح فيه CQRS و Event Sourcing أفضل أصدقائك.
\n\nهذه الأنماط ليست مجرد كلمات رنانة. إنها طريقة تفكير تغير جذريًا نهج بناء التطبيقات في Swift. واليوم سنقوم بتحليلها بأمثلة حقيقية. بدون حشو، بدون نظريات معقدة - فقط ممارسة.
\n\nما هو CQRS؟ فرق تسد
\n\nCQRS (Command Query Responsibility Segregation) هو مبدأ يقول: "كفى خلطًا بين القراءة والكتابة في نفس الفئة". يبدو الأمر بسيطًا، لكن العواقب هائلة.
\n\nفي البنية التقليدية CRUD (إنشاء، قراءة، تحديث، حذف)، تستخدم نموذجًا واحدًا لكل شيء. على سبيل المثال، User. يتم حفظه وقراءته وتحديثه. ماذا لو كانت القراءة تحتاج إلى حقول مختلفة عن الكتابة؟ تبدأ الحلول المؤقتة: الحقول الاختيارية، الأعلام، التعيينات...
\n\nيقترح CQRS فصل المسؤوليات:
\n- \n
- الأوامر (Commands) — تغير الحالة. إنها تحوّر البيانات، لكنها لا تُرجع شيئًا. \n
- الاستعلامات (Queries) — تقرأ البيانات. إنها لا تغير الحالة، فقط تُرجع النتيجة. \n
في Swift قد يبدو هذا كالتالي:
\n\n// أمر — تحوير فقط\nstruct CreateUserCommand {\n let id: UUID\n let name: String\n let email: String\n}\n\n// استعلام — قراءة فقط\nstruct GetUserQuery {\n let userId: UUID\n}\n\n// معالج الأمر\nclass CreateUserCommandHandler {\n func handle(_ command: CreateUserCommand) {\n // منطق إنشاء المستخدم\n print("تم إنشاء المستخدم: \\(command.name)")\n }\n}\n\n// معالج الاستعلام\nclass GetUserQueryHandler {\n func handle(_ query: GetUserQuery) -> User? {\n // منطق الحصول على المستخدم\n return User(id: query.userId, name: "أليكسي", email: "alex@example.com")\n }\n}\n\nهل ترى؟ لا خلط. كل فئة تقوم بشيء واحد بالضبط. هذا يبسط الاختبار والصيانة والتوسع.
\n\nEvent Sourcing: نخزن التاريخ، وليس الحالة
\n\nالآن ننتقل إلى Event Sourcing — النمط الذي لا يخزن الحالة الحالية للكائن، بل يخزن التسلسل الكامل للأحداث التي أدت إليها.
\n\nتخيل حسابًا بنكيًا. بدلاً من تخزين الرصيد (1000 روبل)، تخزن قائمة الأحداث: "فتح الحساب"، "إيداع 500"، "سحب 200"، "إيداع 700". لمعرفة الرصيد الحالي، تقوم ببساطة بإعادة تشغيل جميع الأحداث. هذا يوفر إمكانيات لا تصدق: التدقيق، التراجع عن التغييرات، التحليلات.
\n\nلننفذ هذا في Swift:
\n\n// حدث\nprotocol Event {}\n\nstruct AccountCreated: Event {\n let accountId: UUID\n let initialBalance: Double\n}\n\nstruct MoneyDeposited: Event {\n let accountId: UUID\n let amount: Double\n}\n\nstruct MoneyWithdrawn: Event {\n let accountId: UUID\n let amount: Double\n}\n\n// المُجمّع — يستعيد الحالة من الأحداث\nclass BankAccount {\n private(set) var balance: Double = 0\n private(set) var isActive: Bool = false\n private(set) var accountId: UUID?\n\n func apply(_ event: Event) {\n switch event {\n case let created as AccountCreated:\n accountId = created.accountId\n balance = created.initialBalance\n isActive = true\n case let deposited as MoneyDeposited:\n balance += deposited.amount\n case let withdrawn as MoneyWithdrawn:\n balance -= withdrawn.amount\n default:\n break\n }\n }\n\n // استعادة من تاريخ الأحداث\n static func load(from history: [Event]) -> BankAccount {\n l