CQRS و Event Sourcing في Java: بنية لا تخشى التعقيد

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

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

ابدأ الدورة

مقدمة: عندما يعجز CRUD عن الأداء

\n\n

تخيل أنك تكتب نظامًا للتداول في البورصة. كل ثانية - آلاف الأوامر. كل أمر ليس مجرد "إدراج سجل". إنه التحقق من الرصيد، إعادة حساب المحفظة، التدقيق، الإشعارات. والآن تخيل أنك لا تحتاج فقط إلى تسجيل الحالة الحالية، بل أيضًا إلى استعادة ما حدث في الساعة 14:35:22.003 أمس. CRUD العادي (إنشاء، قراءة، تحديث، حذف) يفشل هنا. مرحبًا بك في عالم CQRS و Event Sourcing في Java.

\n\n

هذان النمطان ليسا مجرد كلمات رنانة. إنهما أدوات تسمح ببناء أنظمة ذات أداء عالٍ، وتدقيق كامل، وإمكانية "إعادة الزمن إلى الوراء". في هذه المقالة، سنشرح كيفية تنفيذ CQRS (فصل مسؤولية الأوامر والاستعلامات) و Event Sourcing في Java باستخدام أطر عمل حديثة وكود نظيف. بدون حشو، مع أمثلة.

\n\n

ما هو CQRS بكلمات بسيطة

\n\n

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

\n\n

لماذا هذا ضروري؟

\n
    \n
  • الأداء: يمكن توسيع نطاق القراءة والكتابة بشكل مستقل. يمكن أن يكون لديك 10 نسخ متماثلة للقراءة و2 للكتابة.
  • \n
  • الأمان: يمكن للأوامر التحقق من قواعد العمل، بينما يمكن للاستعلامات ببساطة إرجاع البيانات.
  • \n
  • المرونة: للقراءة، يمكن استخدام تمثيلات غير معيارية (denormalized)، وللكتابة، تمثيلات معيارية (normalized).
  • \n
\n\n

مثال كود: فصل الأوامر والاستعلامات

\n\n

لنفترض أن لدينا نظامًا لإدارة الطلبات. بدلاً من عمل OrderService.save(order)، نقوم بالفصل:

\n\n
// الأمر - هو نية لتغيير الحالة\npublic class CreateOrderCommand {\n    private final String orderId;\n    private final String customerId;\n    private final List<OrderItem> items;\n\n    // المُنشئ، الدوال الجالبة...\n}\n\n// معالج الأمر\n@Component\npublic class CreateOrderCommandHandler {\n    @Autowired\n    private OrderRepository orderRepository;\n\n    public void handle(CreateOrderCommand command) {\n        // التحقق من قواعد العمل\n        if (command.getItems().isEmpty()) {\n            throw new IllegalArgumentException("يجب أن يحتوي الطلب على عنصر واحد على الأقل");\n        }\n        Order order = new Order(command.getOrderId(), command.getCustomerId(), command.getItems());\n        orderRepository.save(order);\n    }\n}\n\n// الاستعلام - هو الحصول على البيانات\npublic class GetOrderQuery {\n    private final String orderId;\n    // المُنشئ، الدوال الجالبة...\n}\n\n// معالج الاستعلام\n@Component\npublic class GetOrderQueryHandler {\n    @Autowired\n    private OrderReadRepository orderReadRepository;\n\n    public OrderDTO handle(GetOrderQuery query) {\n        // قراءة مباشرة من قاعدة بيانات محسّنة\n        return orderReadRepository.findById(query.getOrderId());\n    }\n}
\n\n

لاحظ: OrderRepository (الكتابة) و OrderReadRepository (القراءة) هما واجهتان مختلفتان. يمكن أن يشيرا إلى قواعد بيانات مختلفة أو مخططات مختلفة.

\n\n

Event Sourcing: نخزن ليس الحالة، بل التاريخ

\n\n

Event Sourcing هو نمط نقوم فيه بحفظ ليس الحالة الحالية للكائن، بل سلسلة الأحداث التي أدت إلى هذه الحالة. بدلاً من "الطلب رقم 123: الحالة = تم التوصيل" نخزن: "الحدث 1: تم إنشاء الطلب"، "الحدث 2: تمت إضافة المنتج"، "الحدث 3: تم دفع الطلب"، "الحدث 4: تم توصيل الطلب".

\n\n

كيف يعمل هذا في Java

\n\n

كل حدث هو كائن غير قابل للتغيير (immutable). نقوم بحفظها في Event Store (قاعدة بيانات متخصصة). يتم حساب الحالة الحالية للمجمع (aggregate) عن طريق إعادة تشغيل (replaying) جميع الأحداث.

\n\n
// الحدث الأساسي\npublic interface Do                

المدونات

توصيات الكتب