Event Driven Systems - Data Contracts
في الأنظمة الحديثة اللي شغّالة بأسلوب Event-Driven Architecture (خصوصًا الأنظمة المبنية بمعمارية Microservices)، الخدمات مش بتكلم بعض Direct، لكنها بتتواصل عن طريق رسائل (Events) بتمشي على Message Broker زي Azure Event Hubs في بيئة Azure.
فين المشكلة؟
أي تغيير بسيط في شكل الداتا اللي الـ Producers بيبعتوها كـ Events، من غير تنسيق كامل مع باقي الـ Consumers، ممكن يعمل كارثة حقيقية ويوقّف السيستم كله.
وده سيناريو مرعب…
ووارد جدًا يحصل بدون قصد، خصوصًا تحت ضغط الشغل والديدلاينز.
علشان كده لازم يكون فيه آلية واضحة تحمي كل Service من تغييرات غير متوقعة، وتمنع أي Team إنه يكسر الـ Flow أو يعمل Crash لباقي النظام.
الحل؟
👉 Data Contracts
🤝 يعني إيه Data Contract؟
ببساطة شديدة:
اتفاق رسمي بين اللي بيبعت الداتا (Producer) واللي بيستقبلها (Consumer) على شكل الداتا ونوعها وقواعدها. والاتفاق ده بيتكتب في صورة JSON Schema.
🧩 السيناريو الكامل: من التصميم لحد التنفيذ
خلّينا نمشي الرحلة خطوة خطوة 👇
🥇 الخطوة الأولى: كتابة العقد (JSON Schema)
قبل ما نكتب ولا سطر كود
فريق الـ Producer وفريق الـ Consumer يتفقوا على شكل الرسالة.
مثال: عقد إنشاء أوردر (v1)
🔹 المعنى:
لازم يبقى فيهorderId- السعر
totalلازم يكون رقم ومش أقل من صفر
🥈 الخطوة التانية: تسجيل العقد في Azure Schema Registry
بعد ما العقد يجهز، الـ Producer يرفعه على
👉 Azure Schema Registry
(خدمة جوه Event Hubs لإدارة العقود وإصداراتها).
اللي بيحصل تحت في الكواليس:
- ✅ المطور بيستخدم Azure Messaging SDK
- ✅ الـ SDK يعمل Hash للسكيما
- ✅ يسأل الـ Registry:
"السكيما دي موجودة قبل كده؟" - ✅ لو جديدة:
- تتسجل
- ويتطلع لها Schema ID (مثلاً:
99)
- ✅ الـ Producer يحتفظ بالـ ID في الـ Cache
علشان ميكررّش الطلب كل مرة
🥉 الخطوة التالتة: الإرسال ولصق "الاستيكر"
لما الـ Producer يبعث رسالة حقيقية:
- الداتا تتحول لـ JSON (Serialization)
- ويتلزق عليها Schema ID في الهيدر
شكل الرسالة وهي ماشية في Event Hubs:
🔖 Header (الاستيكر)
SchemaId: 99
📦 Body (الداتا نفسها)
📌 الربط بين الداتا والعقد بيعمله Serializer اللي جوه الـ SDK
🛡️ الخطوة الرابعة: الاستلام والتحقق (Validation)
أول ما الرسالة توصل للـ Consumer:
يقرأ الـSchemaIdمن الهيدر- يجيب العقد من Schema Registry
- يعمل Validation:
- لو
totalطلع String بدل Number → ❌ - الرسالة تترفض فورًا
- لو
🎯 كده إحنا:
- حمينا الكود
- حمينا الداتابيز
- منعنا Garbage Data تدخل السيستم
🔄 الخطوة الخامسة: تطوير العقد (Schema Evolution)
نفترض إن الـ Producer عايز يضيف
اسم العميل 👇
العقد الجديد (v2)
✔️ التغيير ده Backward Compatible
ليه؟ لأن:
الحقل الجديد مش Requiredوليه Default Value
الـ Registry يقبله ويديله: Schema ID: 100
🧠 إزاي Consumers يتعاملوا مع التغيير؟
1️⃣ Consumer قديم (v1)
لسه مش عارف حاجة عن customerName
- أثناء Deserialization
- المكتبة (Newtonsoft / System.Text.Json):
- تقرأ اللي تعرفه بس
- وتتجاهل أي حاجة زيادة
✅ النتيجة:
- مفيش Error
- السيستم شغال زي الفل
2️⃣ Consumer جديد (v2)
مستني customerName
بس جاتله رسالة قديمة (Schema ID: 99)
💡 هنا يشتغل سحر الـ Default Value
- الحقل مش موجود؟
- يبص في السكيما
- يلاقي
default = "Guest" - يحطه تلقائي
✅ النتيجة:
- لا Null
- لا Crash
- ولا صداع
💣 الخطوة السادسة: كسر العقد (Breaking Changes)
نفترض حد قال:
"خلّي
totalString علشان نحط العملة"
🚨 ده كسر عقد وش
اللي يحصل في Azure:
- الـ Producer يحاول يسجل السكيما
- الـ Registry يعمل Compatibility Check
- يكتشف إن:
- Consumers مستنيين Number
- واستلام String هيكسّرهم
❌ النتيجة:
- رفض التسجيل
- فشل الـ Pipeline
- منع الكارثة قبل ما توصل Production
🧾 الخلاصة السريعة
العنصر دوره Data Contract JSON Schema Registry Azure Schema Registry Message Bus Azure Event Hubs Schema ID الرابط بين الرسالة والعقد
🚦Full Transitive Compatibility
عشان نفهم Full Transitive Compatibility، لازم نفك المصطلح لجزئين:
1. يعني إيه Full؟
المقصود هنا هو الدمج بين الاتجاهين:
- Backward: الجديد بيفهم القديم.
- Forward: القديم بيفهم الجديد.
- Full: الاثنين شغالين مع بعض (رايح جاي).
2. يعني إيه Transitive (عابر للأجيال)؟
دي بقى النقطة الجوهرية. الـ Compatibility العادية بتهتم بـ "الجار المباشر" (يعني v2 متوافق مع v1).
لكن الـ Transitive معناها إن التوافق لازم يكون ساري على تاريخ السكيما بالكامل من أول يوم لحد النهاردة.
بمعنى آخر: "أي نسخة (Version) من الـ Consumer تقدر تقرأ بيانات مكتوبة بـ أي نسخة من الـ Producer، مهما كان الفارق الزمني بينهم."
مثال عملي:
تخيل عندنا 3 إصدارات من العقد (Schema Versions):
- v1: فيه name بس.
- v2: زودنا age (اختياري).
- v3: زودنا phone (اختياري).
لو النظام Full Transitive، لازم السيناريوهات دي كلها تنجح:
- الجار مع الجار:
- Consumer (v2) يقرأ بيانات (v1).
- Consumer (v1) يقرأ بيانات (v2).
- القفز عبر الزمن (Transitive):
- Consumer (v3) (أحدث حاجة) لازم يعرف يقرأ بيانات اتبعتت بـ (v1) (أقدم حاجة). -> البيانات القديمة لسه صالحة.
- Consumer (v1) (أقدم كود لسه شغال) لازم يعرف يقرأ بيانات جاية من (v3). -> الكود القديم ما يضربش من البيانات الجديدة.
ليه دي "أصعب" نوع؟ (The Constraints)
عشان تحقق الـ Full Transitive، الـ Azure Schema Registry هيحط عليك قيود صارمة جداً (Handcuffs):
- ممنوع حذف الحقول (Deleting Fields): لو مسحت حقل كان موجود في v1، الـ Consumer v1 مش هيلاقيه في بيانات v3 وهيعمل مشكلة.
- ممنوع إضافة حقول إجبارية (Adding Mandatory Fields): لازم أي حقل جديد تضيفه يكون Optional أو له Default Value. لو ضفت حقل إجباري في v3، الـ Consumer v3 هيفشل في قراءة بيانات v1 (لأن v1 مفيهوش الحقل ده).
- ممنوع تغيير أنواع الحقول: مينفعش الـ String يبقى Int.
إمتى نستخدمها؟
بنستخدم الـ Full Transitive في الأنظمة المعقدة جداً اللي فيها:
- خدمات الـ (Microservices) كثيرة.
- صعب جداً تخلي كل الفرق تعمل Update للـ Consumers بتوعها في نفس الوقت.
- ممكن تلاقي خدمة لسه شغالة بكود من سنة (v1) وخدمة تانية شغالة بكود إمبارح (v10)، ولازم الاتنين يكلموا بعض من غير ما السيستم يقع.
نصيحة من أصحاب الخبرة العملية وأنا اخوك:
الخلاصة:
الـ Full Transitive Compatibility هي الضمان إنك مهما غيرت في المستقبل، أو مهما كنت متأخر في التحديث، "كله شغال مع كله".
"الأدوار الثلاثة" (The Three Roles):
اللي بيلعبهم الـ Azure Schema Registry والـ SDK في السيناريوهات اللي اتكلمنا فيها
1. الـ Central Source of Truth (مصدر الحقيقة الوحيد)
"المرجع اللي مفيش غيره"
- مين هو؟ هو الـ Azure Schema Registry نفسه.
- بيعمل إيه؟ في الشركات الكبيرة، تلاقي فريق الـ Backend عنده ملف Excel فيه شكل البيانات، وفريق الـ Mobile عنده ملف Text قديم، وفريق الـ Data Science شغال بحاجة تالتة خالص. دي فوضى! الـ Registry بيحل المشكلة دي بأنه يبقى هو المكان الوحيد اللي فيه النسخة الصح من العقد.
- الفائدة: لو الـ Consumer والـ Producer اختلفوا، بيروحوا للـ Registry. "يا ريجستري، النسخة رقم 5 بتقول إيه؟". اللي الريجستري يقوله هو اللي يمشي. مفيش حد بيخمن.
2. الـ Compatibility Guardian (حارس التوافق)
- مين هو؟ دي قواعد التحقق (Validation Logic) اللي جوه الـ Azure Schema Registry.
- بيعمل إيه؟ لما الـ Producer ييجي يرفع Schema جديدة (فيها تعديل)، "الحارس" ده بيصحى:
- بيجيب السكيما القديمة من الدرج.
- بيحطها جنب الجديدة.
- بيراجع القواعد اللي إنت حددتها (Backward, Forward, Full Transitive).
- لو لقى إنك "مسحت حقل إجباري" أو "غيرت نوع بيانات"، بيرفع الكارت الأحمر ويرفض التغيير فوراً.
- الفائدة: بيمنع الـ "Human Error". بيحمي الـ Consumers الغلابة من إن الـ System يفرقع في وشهم بسبب تعديل غير مسؤول من الـ Producer.
3. الـ Performance Optimizer (مُحسّن الأداء)
- مين هو؟ ده تعاون مشترك بين الـ Schema ID والـ Client SDK (المكتبة اللي في الكود عندك).
- بيعمل إيه؟ تخيل لو مع كل "أوردر" بتبعته، لازم تبعت معاه "صفحة كاملة" (الـ JSON Schema) بتوصف البيانات. ده هيخلي الرسالة حجمها كبير جداً والشبكة بطيئة (Heavy Payload). الـ Optimizer بيعمل سحر هنا:
- بياخد الـ Schema الكبيرة دي ويرميها.
- بيحط مكانها رقم صغير جداً (مثلاً ID: 99).
- البيانات بتسافر خفيفة جداً عبر الشبكة (Serialization).
- الـ SDK بيعمل Caching (تخزين مؤقت) للـ ID ده عنده، عشان مش كل مرة يروح يسأل الريجستري، فبيوفر وقت ومجهود.
- الفائدة: تقليل حجم البيانات المنقولة (Bandwidth Saving) + سرعة عالية جداً (Low Latency).
ملخص الأدوار في جملة واحدة:
- Central Source of Truth: عشان منتوهش من بعض ونعرف مين الصح.
- Compatibility Guardian: عشان موقعش شغل زمايلي بتغييرات غلط.
- Performance Optimizer: عشان السيستم يبقى سريع وميبقاش تقيل على الشبكة.
🛡Field-Level Encryption (FLE)
ده بيتعمل ازاي على Azure:
1️⃣مفاتيح التشفير – Azure Key Vault
- المفتاح ده هو اللي هيشفر ويفك تشفير الحقول الحساسة
- ولا Producer ولا Consumer بيشوفوا المفتاح نفسه
- كله Access Control + Managed Identity
2️⃣ التشفير عند الـ Producer قبل ما الـ Producer يبعت الـ Event:
- يستخدم Key من Key Vault
- يشفر الحقول دي بس
- وبعدين يبعت الـ Event عادي على Event Hubs
مثال:
يعني: orderId و total واضحين، nationalId متشفر
3️⃣ الإرسال عبر Event Hubs
Azure Event Hubs نفسه مش بيفهم الداتا هو مجرد ناقل:
يشيل الرسالة، يوزعها من غير ما يفك تشفير أي حاجة، وده مهم جدًا عشان: الأمان، الأداء، فصل المسؤوليات
4️⃣ فك التشفير عند الـ Consumer (اللي له صلاحية بس)الـ Consumer اللي مسموح له:
- يكون عنده Managed Identity
- واخد Permission على Key Vault
- يفك تشفير الحقول اللي محتاجها بس
Consumer تاني: معندوش صلاحية؟ هيشوف الداتا مشفرة ومش هيعرف يعمل بيها حاجة
5️⃣ الربط مع Data Contracts
في Data Contract (JSON Schema):بتحدد إن الحقل ده Sensitive، وبيتم الاتفاق إن الحقل ده دايمًا ييجي مشفر وأي Consumer مش ملتزم بكده = عقد مكسور.
مثال:
خطة منظمة للتعامل مع "التغييرات الكاسرة" (Breaking Changes):
مع كل الاحتياطات اللي دايما بنعملها أثناء تصميم النظام إلا أنه مفيش مفر من الوقوع في المحذور، فلو لسبب ما وقعنا في حالة لابد فيها من عمل Breaking Changes، إزاي نعمل تغييرات كبيرة في شكل البيانات (Schema) من غير ما نوقع الدنيا أو نبوظ الشغل عند الconsumers.
١. متعدلش في القديم.. اعمل "تفريعة😉" جديدة
- ماتلعبش في الأساس عشان العمارة كلها متقعش: لو عندك بيانات ماشية في طريق (Stream V1)، سيبها زي ما هي عشان البرامج اللي معتمدة عليها ماتقفش.
- الحل: افتح طريق جديد خالص (Stream V2) وحط فيه التعديلات اللي انت عايزها، وشغل الاتنين مع بعض في نفس الوقت.
٢. عَرّف الناس ونسق معاهم
- ماتشتغلش لوحدك: روح لفرق العمل (Consumers) اللي بتستخدم بياناتك، وقولهم: "يا جماعة إحنا عملنا نسخة جديدة أحسن، ودى التغييرات اللي فيها".
- اتفقوا على ميعاد: لازم تتفقوا على جدول زمني يكون الكل موافق عليه عشان يبدأوا ينقلوا لشغلهم الجديد.
٣. انقلهم على الجديد واقفل القديم بكل حزم
- ساعدهم ينقلوا: خليك معاهم وراقب الدنيا لحد ما تتأكد إن كل الناس بدأت تسحب البيانات من الطريق الجديد (V2).
- الحزم بيكون بتحديد ميعاد نهائي: لازم تحط "تاريخ قاطع" لقفل الطريق القديم. لو سيبته مفتوح "لله وللوطن"، هيفضل عندك كركبة تقنية (Technical Debt) ومشاكل صيانة مالهاش لزوم وتضيع وقتك.
بس كده خلصنا 👋، لو المحتوى عجبك متنساش اللايك والشير.
Comments
Post a Comment