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

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

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

ابدأ الدورة

عندما يتوقف CRUD العادي عن العمل: التعرف على CQRS و Event Sourcing

\n\n

هل سبق لك أن واجهت موقفًا حيث ينمو تطبيق PHP الخاص بك، وينمو... وفي يوم من الأيام يتحول UserController العادي إلى وحش من 2000 سطر؟ مألوف؟ تتكاثر الأخطاء، وينخفض الأداء، وتسبب إضافة ميزة جديدة ارتعاشًا عصبيًا للفريق بأكمله. تهانينا، لقد وصلت إلى اللحظة التي تحتاج فيها إلى تطبيق أنماط معمارية جادة.

\n\n

سنتحدث اليوم عن أقوى أداتين — CQRS (Command Query Responsibility Segregation) و Event Sourcing. لا تخف من المصطلحات المعقدة. في الواقع، إنها مجرد طرق أنيقة للقول: "دعنا نتوقف عن خلط القراءة والكتابة في كومة واحدة" و "سنخزن ليس الحالة الحالية، بل تاريخ التغييرات".

\n\n

هذه المقالة مخصصة لمطوري PHP الذين أتقنوا أساسيات OOP ويريدون الارتقاء بمشاريعهم إلى المستوى التالي. ستتعلم كيفية تطبيق CQRS و Event Sourcing عمليًا، وسترى كودًا حقيقيًا، وستفهم متى تكون هذه الأنماط ضرورية حقًا ومتى يكون من الأفضل التخلي عنها.

\n\n

ما هو CQRS؟ فصل الأوامر والاستعلامات

\n\n

تخيل أن الكود الخاص بك هو مطعم. عادةً، يأخذ النادل (الـ Controller) الطلب (طلب كتابة)، ويركض إلى المطبخ (قاعدة البيانات)، ويجهز الطبق (يعالج البيانات)، ويقدمه فورًا (يعيد الرد). كل شيء رائع عندما يكون في المطعم 10 أشخاص. ولكن عندما يأتي 1000 ضيف، يبدأ المطبخ في الاختناق.

\n\n

CQRS (Command Query Responsibility Segregation) يقترح فصل المسؤوليات: بعض الطهاة (الأوامر) يطبخون فقط، والنوادل الآخرون (الاستعلامات) يقدمون فقط. من الناحية الفنية، هذا يعني أنك تستخدم نماذج مختلفة لقراءة البيانات وكتابتها.

\n\n

تطبيق أساسي لـ CQRS في PHP

\n\n

لنرى كيف يبدو هذا في الكود. سنبدأ بمثال بسيط بدون أطر عمل، لفهم الجوهر.

\n\n
// الأمر هو DTO مع بيانات لتغيير الحالة\nclass CreateUserCommand {\n    public function __construct(\n        public readonly string $email,\n        public readonly string $name\n    ) {}\n}\n\n// معالج الأمر — هنا يحدث منطق الأعمال\nclass CreateUserHandler {\n    public function __construct(\n        private UserRepository $repository,\n        private EventDispatcher $dispatcher\n    ) {}\n\n    public function handle(CreateUserCommand $command): void {\n        $user = new User($command->email, $command->name);\n        $this->repository->save($user);\n        $this->dispatcher->dispatch(new UserCreatedEvent($user->getId()));\n    }\n}\n\n// الاستعلام هو DTO لجلب البيانات\nclass GetUserQuery {\n    public function __construct(\n        public readonly int $userId\n    ) {}\n}\n\n// معالج الاستعلام — قراءة فقط، لا تغييرات\nclass GetUserHandler {\n    public function __construct(\n        private UserReadRepository $readRepository\n    ) {}\n\n    public function handle(GetUserQuery $query): ?UserDTO {\n        return $this->readRepository->findById($query->userId);\n    }\n}
\n\n

لاحظ: لدينا مستودعان مختلفان. UserRepository مسؤول عن الكتابة (يمكنه استخدام منطق أعمال معقد، والتحقق من الصحة)، بينما UserReadRepository مسؤول عن القراءة (يمكن أن يكون استعلام SQL بسيطًا أو حتى جدولًا غير معياري).

\n\n

ناقل الأوامر (Command Bus)

\n\n

في المشاريع الحقيقية، يتم إرسال الأوامر والاستعلامات عبر ناقل — موزع مركزي يجد المعالج المناسب بنفسه.

\n\n
// تطبيق بسيط لـ Command Bus\nclass SimpleCommandBus {\n    public function __construct(\n        private array $handlers = []\n    ) {}\n\n    public function registerHandler(string $commandClass, object $handler): void {\n        $this->handlers[$commandClass] = $handler;\n    }\n\n    public function dispatch(object $command): void {\n        $handler = $this->handlers[$co                

المدونات

توصيات الكتب