Database Engineering

إتقان نمذجة البيانات للتزويد بالأحداث: الإسقاطات وتطور المخطط

يُعد التزويد بالأحداث (ES) نمطاً معمارياً قوياً يخزن حالة التطبيق كسلسلة من الأحداث بدلاً من الحالة الحالية فقط. بينما يوفر ES فوائد لا تصدق مثل إمكانية التدقيق، والاستعلام الزمني، وإمكانية إعادة التشغيل، فإنه يقدم تعقيداً كبيراً عند التعامل مع نمذجة البيانات، وتحديداً فيما يتعلق بكيفية قراءة هذه البيانات (الإسقاطات) وكيفية التعامل مع التغييرات بمرور الوقت (تطور المخطط).

بالنسبة لمهندسي قواعد البيانات ومعماريي الخلفية، يُعد التنقل في تقاطع الأحداث غير القابلة للتغيير والنماذج المقروءة القابلة للتغيير أحد أكثر الجوانب تحدياً في بناء الأنظمة الموزعة القابلة للتوسع. يستكشف هذا المنشور آليات بناء إسقاطات قوية واستراتيجيات لتطور المخططات في بيئة تعتمد على التزويد بالأحداث.

الجانب المقروء: تصميم إسقاطات فعالة

في بنية التزويد بالأحداث، يتكون "الجانب المكتوب" من جذور التجميع والأحداث. يتكون "الجانب المقروء" من الإسقاطات. الإسقاط هو عملية تستهلك الأحداث وتحدث مخزناً محسناً للقراءة، مثل قاعدة بيانات علائقية، أو مخزن مستندات (مثل MongoDB)، أو محرك بحث (مثل Elasticsearch).

التحدي الرئيسي هو الحفاظ على تحديث هذه الإسقاطات دون تدهور أداء الكتابة. يتحقق ذلك عادةً من خلال أنظمة غير متزامنة ومتسقة في النهاية.

افترض سيناريو بسيطاً لطلب تجارة إلكترونية. عندما يضع المستخدم طلباً، فإننا نطلق حدث OrderCreated. يجب أن يستمع معالج الإسقاط لهذا الحدث ويحقن سجلاً في جدول orders_view لاسترجاع سريع.

class OrderCreatedHandler:
    def handle(self, event: OrderCreated):
        projection_db.insert({
            "order_id": event.order_id,
            "customer_id": event.customer_id,
            "total_amount": event.total,
            "status": "CREATED",
            "created_at": event.timestamp
        })

ومع ذلك، تتطلب الأنظمة الواقعية التعامل مع منطق أعمال معقد. قد يؤدي حدث واحد إلى تشغيل إسقاطات متعددة. على سبيل المثال، قد يقوم OrderCreated بتحديث لوحة المبيعات، ونظام حجز المخزون، ونظام نقاط ولاء العملاء. يعد تصميم هذه المعالجات لتكون قابلة لإعادة التشغيل (Idempotent) أمراً بالغ الأهمية لمنع التأثيرات الجانبية المكررة أثناء عمليات إعادة المحاولة أو انقسامات الشبكة.

التعامل مع تطور المخطط: فخ عدم القابلية للتغيير

المبدأ الأساسي للتزويد بالأحداث هو أنه بمجرد كتابة حدث، لا يمكن تغييره. وهذا يخلق مشكلة فريدة: كيف نطور تطبيقنا عندما تتغير متطلبات الأعمال؟ إذا قمت بإعادة هيكلة فئة أو تغيير اسم حقل، فإن الأحداث التاريخية تبقى بتنسيقها الأصلي.

لا يمكننا ببساطة تحديث مخطط قاعدة بيانات مخزن الأحداث لأن الأحداث غير قابلة للتغيير. بدلاً من ذلك، يجب أن نعتمد استراتيجية للإصدار والتحويل. هناك نهجان شائعان:

  1. التغييرات المتوافقة مع الإصدارات السابقة: أضف حقولاً جديدة إلى الأحداث دون إزالة الحقول القديمة. على سبيل المثال، إذا احتجنا إلى تتبع مزود شحن جديد، فإننا نضيف حقل shipping_provider إلى حدث OrderShipped. سيكون للأحداث الموجودة هذا الحقل كقيمة فارغة (null)، ويتعامل التطبيق مع ذلك بسلاسة.
  2. إصدار الأحداث: إذا كان التغيير كاسراً (على سبيل المثال، تغيير رمز العملة من "USD" إلى "US")، فيجب علينا إنشاء نوع حدث جديد، مثل OrderShippedV2. يحتاج النظام بعد ذلك إلى منطق لتحديد أي إصدار من الحدث يجب معالجته.

إليك مثال على كيفية تعامل المعالج مع الإصدار:

def process_event(event):
    if event.type == "OrderShipped" and event.version == 1:
        # تحويل الحدث القديم إلى الهيكل الحالي
        normalized_data = migrate_v1_to_current(event.data)
        return save_event(normalized_data)
    elif event.type == "OrderShippedV2":
        return save_event(event.data)
    else:
        raise UnsupportedEventVersionError()

إعادة تشغيل الأحداث للتسوية

لا يتعلق تطور المخطط بقراءة البيانات الجديدة فحسب؛ بل يتعلق غالباً بإصلاح البيانات القديمة. إذا أدخلت خطأً في منطق الإسقاط أو احتجت إلى إضافة حقل جديد إلى نموذج القراءة الخاص بك، فيمكنك ببساطة إعادة تشغيل معالجات الإسقاط الخاصة بك ضد تيار الأحداث الموجود.

تُعد قدرة "إعادة التشغيل" هذه هي القوة الخارقة للتزويد بالأحداث. إنها تتيح لك إعادة بناء قاعدة البيانات المقروءة بالكامل من الصفر للتأكد من تطابقها تماماً مع المخطط الحالي.

def rebuild_projection(event_store, projection_engine):
    # 1. مسح مخزن الإسقاط الحالي
    projection_engine.clear()
    
    # 2. تدفق جميع الأحداث التاريخية
    events = event_store.get_all_events()
    
    # 3. إعادة معالجتها عبر منطق المعالج الأحدث
    for event in events:
        projection_engine.process(event)

الخاتمة

تتطلب نمذجة البيانات في التزويد بالأحداث تحولاً في العقلية عن عمليات CRUD التقليدية. لم تعد تصمم الجداول فحسب؛ بل تصمم خطاً زمنياً للحقائق. من خلال التركيز على هياكل الأحداث غير القابلة للتغيير، وتنفيذ استراتيجيات إصدار قوية، والاستفادة من قوة إعادة تشغيل الأحداث، يمكنك بناء أنظمة موزعة ليست فقط مرنة ولكن أيضاً قابلة للتكيف مع احتياجات الأعمال المتغيرة.

الخلاصة الرئيسية هي أن تطور المخطط هو مواطن من الدرجة الأولى في التزويد بالأحداث. احتضن عدم قابلية تغيير الأحداث، وصمم إسقاطات مرنة، وستتمكن من فتح الإمكانات الكاملة لهذا النمط المعماري القوي.

Share: