العودة للدورة|إتقان مقابلات تصميم الأنظمة: بنية قابلة للتوسع من التقدير إلى الإنتاج
معمل

بناء خدمة طلبات قائمة على مصادر الأحداث

35 دقيقة
متوسط
محاولات مجانية غير محدودة

التعليمات

الهدف

ابنِ خدمة طلبات كاملة قائمة على مصادر الأحداث تُظهر أنماط بنية البيانات الأساسية التي تمت تغطيتها في الدرس: مصادر الأحداث وCQRS ونمط Saga وتحسين اللقطات. هذا المعمل يعكس البنية المعمارية التي ستقترحها في مقابلة تصميم أنظمة لخدمة طلبات تجارة إلكترونية.

نظرة عامة على البنية المعمارية

Commands                    Event Store (ملحق فقط)
   |                              |
   v                              v
Command Handler -----> Events -> [E1, E2, E3, E4, ...]
                                  |              |
                                  v              v
                          Order Projection   Analytics Projection
                          (الحالة الحالية)   (الإيرادات، الأعداد)
                                  |
                                  v
                          Snapshot Manager
                          (لقطات دورية)

Saga Orchestrator
   |
   +---> Payment Step ----> (نجاح) ---> Inventory Step
   |                                        |
   |                                   (نجاح) ---> Shipping Step
   |                                                    |
   +--- تعويض <--- (فشل في أي خطوة) <-----------------+

المتطلبات

ستنفّذ 8 ملفات TypeScript تعمل معًا:

الملف 1: src/events/event-store.ts — مخزن الأحداث الملحق فقط

  • نفّذ مخزن أحداث ملحق فقط في الذاكرة
  • كل حدث يحتوي على aggregateId (مثلاً orderId) ورقم version تسلسلي
  • ادعم التحكم في التزامن المتفائل: عند إلحاق أحداث، مرر الإصدار المتوقع؛ ارفض إذا كان هناك تعارض في الإصدار (كتابة أخرى حدثت بشكل متزامن)
  • نفّذ append(aggregateId, events, expectedVersion): يرمي استثناء عند عدم تطابق الإصدار
  • نفّذ getEvents(aggregateId): يُرجع جميع الأحداث لكيان مجمّع بالترتيب
  • نفّذ getEventsAfterVersion(aggregateId, afterVersion): يُرجع الأحداث بعد إصدار معين (يُستخدم مع اللقطات)
  • نفّذ getAllEvents(): يُرجع جميع الأحداث عبر جميع الكيانات المجمّعة (لإسقاط التحليلات)

الملف 2: src/events/event-types.ts — أحداث المجال

  • عرّف واجهة DomainEvent أساسية تحتوي على: type وaggregateId وversion وtimestamp وmetadata (correlationId وcausationId)
  • عرّف أحداث مجال الطلبات: OrderCreated وItemAdded وPaymentProcessed وOrderShipped وOrderCancelled
  • كل حدث يحمل البيانات الخاصة بما حدث (مثلاً OrderCreated يتضمن customerId والعناصر)
  • استخدم الاتحادات المميزة في TypeScript لأمان الأنواع

الملف 3: src/commands/command-handler.ts — معالج الأوامر

  • تعامل مع الأوامر: PlaceOrder وAddItem وProcessPayment وShipOrder وCancelOrder
  • كل معالج أوامر: يحمّل الحالة الحالية من مخزن الأحداث، يتحقق من قواعد العمل، ينتج أحداثًا جديدة
  • قواعد العمل التي يجب فرضها:
    • لا يمكن إضافة عناصر لطلب ملغى أو مشحون
    • لا يمكن معالجة الدفع لطلب ملغى
    • لا يمكن شحن طلب لم يتم دفعه
    • لا يمكن إلغاء طلب تم شحنه بالفعل
  • استخدم التحكم في التزامن المتفائل لمخزن الأحداث للكتابات الآمنة

الملف 4: src/projections/order-projection.ts — نموذج قراءة الطلب

  • أنشئ حالة الطلب الحالية بإعادة تشغيل الأحداث
  • نموذج القراءة يجب أن يتضمن: orderId وcustomerId وitems وstatus وtotalAmount وpaymentId وshippedAt وcancelledAt وversion
  • الحالات: pending وpaid وshipped وcancelled
  • نفّذ getOrder(orderId): يُرجع حالة الطلب الحالية
  • نفّذ rebuildFromEvents(events): يعيد بناء الحالة من قائمة أحداث

الملف 5: src/projections/analytics-projection.ts — إسقاط التحليلات

  • حافظ على مقاييس مجمّعة عبر جميع الطلبات:
    • totalRevenue: مجموع مبالغ جميع الطلبات المدفوعة
    • totalOrders: عدد جميع الطلبات المُنشأة
    • averageOrderValue: totalRevenue / عدد الطلبات المدفوعة
    • ordersByStatus: عدد الطلبات في كل حالة
    • cancelledRevenue: إجمالي مبالغ الطلبات الملغاة (الإيرادات المفقودة)
  • عالج الأحداث بشكل تدريجي (لا تعد تشغيل جميع الأحداث في كل مرة)
  • نفّذ processEvent(event) وgetAnalytics()

الملف 6: src/saga/order-saga.ts — منسق Saga

  • نفّذ Saga قائمة على التنسيق لسير عمل الطلب من 3 خطوات:
    1. الدفع: خصم من العميل (محاكاة بدالة غير متزامنة)
    2. المخزون: حجز العناصر (محاكاة بدالة غير متزامنة)
    3. الشحن: جدولة الشحن (محاكاة بدالة غير متزامنة)
  • كل خطوة يمكن أن تنجح أو تفشل (استخدم محاكاة فشل قابلة للتكوين)
  • عند الفشل في أي خطوة، نفّذ التعويض لجميع الخطوات المكتملة سابقًا بترتيب عكسي:
    • فشل الشحن: حرر المخزون، استرد الدفع
    • فشل المخزون: استرد الدفع
  • تتبع حالة Saga: pending وpayment_completed وinventory_reserved وcompleted وcompensating وcompensated وfailed
  • سجّل كل خطوة للرؤية

الملف 7: src/snapshots/snapshot-manager.ts — مدير اللقطات

  • خزّن لقطات دورية لحالة الكيان المجمّع لتجنب إعادة تشغيل جميع الأحداث
  • نفّذ saveSnapshot(aggregateId, state, version): حفظ لقطة عند إصدار معين
  • نفّذ getLatestSnapshot(aggregateId): إرجاع أحدث لقطة
  • نفّذ shouldSnapshot(eventCount): إرجاع true إذا يجب أخذ لقطة (مثلاً كل 5 أحداث في هذا المعمل؛ في الإنتاج كل 100)
  • نفّذ loadAggregateState(aggregateId, eventStore, projector):
    1. حمّل أحدث لقطة (إن وجدت)
    2. احصل على الأحداث بعد إصدار اللقطة
    3. طبّق تلك الأحداث على حالة اللقطة
    4. أرجع الحالة الحالية

الملف 8: src/index.ts — نقطة الدخول الرئيسية

  • اربط كل شيء معًا
  • شغّل سيناريو اختبار يُظهر:
    1. إنشاء طلب بعنصرين
    2. إضافة عنصر آخر للطلب
    3. معالجة الدفع
    4. شحن الطلب
    5. استعلام إسقاط الطلب للحالة الحالية
    6. استعلام التحليلات للمقاييس المجمّعة
    7. إظهار حفظ وتحميل اللقطات
    8. تشغيل منسق Saga (مرة بنجاح ومرة مع فشل محاكى يُظهر التعويض)
  • اطبع النتائج في كل خطوة لإظهار سير العمل الكامل

تلميحات

  • استخدم Map<string, DomainEvent[]> لمخزن الأحداث في الذاكرة، مفتاحه aggregateId
  • للتحكم في التزامن المتفائل، قارن الإصدار المتوقع مع العدد الحالي للأحداث لذلك الكيان المجمّع
  • منسق Saga يمكنه استخدام async/await بسيط مع try/catch لتنفيذ الخطوات والتعويض
  • لإسقاط التحليلات، تتبع مجموعة processedEventIds لتجنب معالجة نفس الحدث مرتين
  • مدير اللقطات هو أساسًا Map<string, { state: OrderState; version: number }>
  • استخدم console.log مع تسميات واضحة (مثلاً [EventStore] و[Saga] و[Projection]) لإظهار ما يحدث

ما يجب تقديمه

يجب أن يحتوي تقديمك على 8 أقسام ملفات في المحرر أدناه، كل منها ينفّذ أحد الملفات المذكورة أعلاه.


معايير التقييم

مخزن الأحداث بسجل ملحق فقط وإصدار وتحكم في التزامن المتفائل. يجب أن يُعيّن المخزن أرقام إصدارات تسلسلية، ويرفض الإلحاق عندما لا يتطابق الإصدار المتوقع مع طول الدفق الحالي (يرمي ConcurrencyError)، ويدعم الاسترجاع حسب الكيان المجمّع وحسب نطاق الإصدار وعبر جميع الكيانات.15 نقاط
أحداث المجال مع أنواع صحيحة وبيانات وصفية. جميع الأحداث الخمسة (OrderCreated وItemAdded وPaymentProcessed وOrderShipped وOrderCancelled) يجب أن تمدد BaseDomainEvent، وتتضمن حقول بيانات خاصة بالحدث، وتشكل اتحادًا مميزًا عبر حقل type، وتتضمن EventMetadata مع correlationId وcausationId.10 نقاط
معالج الأوامر يتحقق من قواعد العمل وينتج الأحداث. يجب أن يتعامل مع جميع الأوامر الخمسة، ويفرض جميع قواعد العمل الأربع (لا عناصر بعد الإلغاء/الشحن، لا دفع بعد الإلغاء، لا شحن قبل الدفع، لا إلغاء بعد الشحن)، ويستخدم التزامن المتفائل عند الإلحاق، ويُرجع الحدث المُنتج.15 نقاط
إسقاط الطلب يبني الحالة الحالية من إعادة تشغيل الأحداث. دالة rebuildFromEvents يجب أن تتعامل بشكل صحيح مع جميع أنواع الأحداث الخمسة، محافظة على قائمة عناصر دقيقة وتحولات الحالة وحساب totalAmount والحقول الاختيارية (paymentId وshippedAt وcancelledAt). دالة rebuildFromSnapshot يجب أن تطبق الأحداث فوق الحالة الموجودة.15 نقاط
إسقاط التحليلات يحافظ على مقاييس مجمّعة. يجب أن يعالج الأحداث بشكل تدريجي (لا يعيد تشغيل الكل في كل مرة)، ويتتبع totalRevenue وtotalOrders وpaidOrders وaverageOrderValue وأعداد ordersByStatus وcancelledRevenue. يجب أن يكون متماثلًا (يتخطى الأحداث المعالجة مسبقًا).10 نقاط
منسق Saga مع منطق التعويض عند فشل الخطوات. يجب أن يحدد 3 خطوات (الدفع والمخزون والشحن) كل منها بدوال تنفيذ وتعويض. عند الفشل في أي خطوة، يجب تعويض جميع الخطوات المكتملة سابقًا بترتيب عكسي. يجب تتبع حالة Saga عبر جميع الحالات والحفاظ على سجل مفصل لكل إجراء.20 نقاط
مدير اللقطات لتحسين الأداء. يجب أن يحفظ اللقطات مع حالة الكيان المجمّع والإصدار، ويسترجع أحدث لقطة، ويحدد متى يجب أخذ لقطة بناءً على فترة عدد الأحداث، ويحمّل حالة الكيان المجمّع بدمج اللقطة مع الأحداث الأحدث من مخزن الأحداث.10 نقاط
نقطة الدخول الرئيسية تُظهر سير العمل الكامل. يجب ربط جميع المكونات معًا وتشغيل سيناريو كامل: إنشاء طلب وإضافة عنصر ومعالجة الدفع وشحن الطلب واستعلام الإسقاط واستعلام التحليلات وإظهار حفظ/تحميل اللقطات وتشغيل Saga (نجاح وفشل مع تعويض). المخرجات يجب أن تكون واضحة ومُعنونة.5 نقاط

قائمة التحقق

0/8

حلك

محاولات مجانية غير محدودة
نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.