أنماط الهندسة المعمارية المدفوعة بالأحداث
الهندسة المعمارية المدفوعة بالأحداث (EDA) تمكّن الاقتران الفضفاض، القابلية للتوسع، والمعالجة في الوقت الحقيقي. هذه الأنماط ضرورية للهندسات السحابية الحديثة.
أنواع الأحداث ودلالاتها
تصنيف الأحداث
| النوع | الغرض | مثال |
|---|---|---|
| أحداث المجال | حقائق الأعمال | OrderPlaced، PaymentReceived |
| أحداث التكامل | الاتصال عبر الخدمات | CustomerCreatedEvent |
| أحداث النظام | تغييرات البنية التحتية | InstanceTerminated، QueueFull |
بنية الحدث
{
"eventId": "uuid-v4",
"eventType": "OrderPlaced",
"timestamp": "2026-01-05T10:30:00Z",
"version": "1.0",
"source": "order-service",
"data": {
"orderId": "12345",
"customerId": "67890",
"totalAmount": 99.99
},
"metadata": {
"correlationId": "request-uuid",
"causationId": "previous-event-uuid"
}
}
سؤال المقابلة: دقة الأحداث
س: "هل يجب أن نصدر أحداثاً دقيقة (OrderItemAdded) أو أحداثاً خشنة (OrderUpdated)؟"
ج: اعتبر المقايضات:
| النهج | الإيجابيات | السلبيات |
|---|---|---|
| دقيق | ردود أفعال محددة، سجل مراجعة | أحداث أكثر، مستهلكون معقدون |
| خشن | أبسط، أحداث أقل | قد يُخطر بشكل مفرط، سياق أقل |
| هجين | أفضل ما في الاثنين | تعقيد أكثر للإدارة |
التوصية: ابدأ بالخشن، أضف الدقيق عندما تظهر احتياجات مستهلك محددة.
توريد الأحداث (Event Sourcing)
المفهوم
تخزين الحالة كتسلسل من الأحداث بدلاً من الحالة الحالية.
التقليدي:
جدول المستخدم: { id: 1, name: "John", email: "john@example.com" }
توريد الأحداث:
الأحداث:
1. UserCreated { id: 1, name: "John" }
2. EmailUpdated { id: 1, email: "john@example.com" }
3. NameChanged { id: 1, name: "John Doe" }
الفوائد والمقايضات
| الفائدة | المقايضة |
|---|---|
| سجل مراجعة كامل | نمو التخزين |
| الاستعلامات الزمنية ("الحالة في الوقت X") | نماذج قراءة معقدة |
| إعادة تشغيل الأحداث للتصحيح | الاتساق النهائي |
| ملاءمة طبيعية لـ CQRS | منحنى التعلم |
سؤال المقابلة: متى تستخدم توريد الأحداث
س: "شركة خدمات مالية تريد تنفيذ توريد الأحداث لجميع الخدمات. انصحهم."
ج: توريد الأحداث ليس مناسباً في كل مكان:
مناسب:
- متطلبات المراجعة (المعاملات المالية، الامتثال)
- مجال معقد مع قواعد أعمال غنية
- الحاجة للاستعلامات الزمنية
- تكاملات مدفوعة بالأحداث موجودة بالفعل
غير مناسب:
- تطبيقات CRUD البسيطة
- متطلبات تحليلات في الوقت الحقيقي
- الفريق غير مألوف مع النمط
- لا متطلبات مراجعة
التوصية: استخدم توريد الأحداث للمعاملات المالية الأساسية (دفتر الأستاذ، الصفقات). استخدم الاستمرارية التقليدية للخدمات الداعمة (إدارة المستخدمين، الإشعارات).
CQRS: فصل مسؤولية الأمر والاستعلام
الهندسة المعمارية
فصل نماذج القراءة والكتابة:
الأوامر (الكتابة):
العميل → معالج الأمر → نموذج المجال → مخزن الأحداث
↓
الأحداث منشورة
↓
الاستعلامات (القراءة):
العميل → معالج الاستعلام → نموذج القراءة (عروض غير مُطبَّعة)
↑
عارض الأحداث
فوائد CQRS
| الفائدة | الشرح |
|---|---|
| قراءات قابلة للتوسع | نموذج القراءة محسّن للاستعلامات |
| كتابات محسّنة | نموذج الكتابة مركز على منطق الأعمال |
| المرونة | تخزين مختلف للقراءة/الكتابة |
| الأداء | عروض مادية للاستعلامات المعقدة |
سؤال المقابلة: اتساق CQRS
س: "كيف تتعامل مع فجوة الاتساق النهائي في CQRS؟"
ج: استراتيجيات لإدارة الاتساق:
- تحديثات UI متفائلة: أظهر الحالة المعلقة فوراً
- Polling/WebSockets: حدّث UI عندما يلحق العرض
- معرفات الارتباط: تتبع الأمر حتى نموذج القراءة
- اقرأ-ما-تكتب: وجّه قراءات المستخدم للأساسي أثناء الفجوة
// مثال التحديث المتفائل
async function placeOrder(order) {
// أظهر المعلق في UI فوراً
updateUI({ ...order, status: 'pending' });
// أرسل الأمر
const commandId = await sendCommand('PlaceOrder', order);
// استطلع للتأكيد
await pollUntilProjected(commandId);
// حدّث UI بالحالة المؤكدة
const confirmed = await fetchOrder(order.id);
updateUI(confirmed);
}
منصات بث الأحداث
هندسة Apache Kafka
المنتجون → المواضيع (مقسمة) → مجموعات المستهلكين
↓
الأقسام: [P0] [P1] [P2]
↓
مجموعة المستهلكين: [C0] [C1] [C2]
المفاهيم الرئيسية:
- المواضيع: فئة الأحداث
- الأقسام: وحدة التوازي، مرتبة ضمن القسم
- مجموعات المستهلكين: موازنة الحمل عبر المستهلكين
- الإزاحات: تتبع الموقع لإعادة التشغيل
AWS Kinesis مقابل Kafka
| الميزة | Kinesis | Kafka (MSK) |
|---|---|---|
| الإدارة | بدون خادم | كتل مُدارة |
| التوسع | قائم على الشظايا | قائم على الأقسام |
| الاحتفاظ | 24 ساعة-365 يوم | غير محدود |
| التكامل | AWS أصلي | نظام بيئي أوسع |
| نموذج التكلفة | لكل ساعة شظية + بيانات | قائم على المثيلات |
| الأفضل لـ | AWS أصلي، حمل متغير | إنتاجية عالية، احتفاظ طويل |
سؤال المقابلة: استراتيجية قسم Kafka
س: "أنت تصمم نظام إشعارات لـ 100 مليون مستخدم. كيف تقسم موضوع Kafka؟"
ج: صمم للتوازي والترتيب:
الموضوع: user-notifications
مفتاح القسم: user_id
المنطق:
- جميع إشعارات المستخدم تذهب لنفس القسم
- يضمن الترتيب لكل مستخدم
- 100 قسم للتوازي
- كل قسم ~1 مليون مستخدم في المتوسط
اعتبارات التوسع:
- يمكن إضافة أقسام (يزيد التوازي فقط)
- لا يمكن تقليل الأقسام
- إعادة التوازن تحدث تلقائياً
- اعتبر المستخدمين الساخنين (المشاهير) - قد يحتاجون معالجة خاصة
ضمانات تسليم الأحداث
دلالات التسليم
| الضمان | التعريف | حالة الاستخدام |
|---|---|---|
| مرة واحدة كحد أقصى | قد تفقد الأحداث | إشعارات غير حرجة |
| مرة واحدة على الأقل | قد تتكرر | معظم الحالات (مع التساوي) |
| مرة واحدة بالضبط | لا فقدان، لا تكرار | المعاملات المالية |
تنفيذ التساوي
def process_event(event):
# تحقق إذا تمت المعالجة بالفعل
if event_store.exists(event.id):
return # تخطى التكرار
# معالجة الحدث
result = handle_event(event)
# وضع علامة معالجة (ذرياً مع الآثار الجانبية)
event_store.mark_processed(event.id, result)
سؤال المقابلة: الأحداث المكررة
س: "خدمة الدفع الخاصة بك تستقبل أحداث OrderPlaced مكررة. كيف تمنع الشحن المزدوج؟"
ج: نفذ التساوي على مستويات متعددة:
- مفتاح التساوي: استخدم orderId كمفتاح طبيعي
- جدول إزالة التكرار: تتبع الأحداث المعالجة
- بوابة الدفع: معظمها يدعم مفاتيح التساوي
- قيود قاعدة البيانات: قيد فريد على علاقة الطلب-الدفع
-- منع المدفوعات المكررة
CREATE TABLE payments (
id UUID PRIMARY KEY,
order_id UUID UNIQUE, -- دفعة واحدة فقط لكل طلب
amount DECIMAL,
created_at TIMESTAMP
);
المبدأ الرئيسي: افترض أن الأحداث ستتكرر. صمم جميع المستهلكين ليكونوا متساوين.
بعد ذلك، سنستكشف أنماط التوافر العالي والتعافي من الكوارث. :::