NahdaAIنهضة
NoSQL Databases • المستوى الثالث • الدرس 1

نمذجة العلاقات المضمنة والمرجعية (Embedding vs Referencing)

العودة لفهرس المستوى

أهداف الدرس

  • فهم متى يُفضّل التضمين Embedding لتسريع القراءة.
  • تطبيق المرجعية Referencing لمنع تضخم حجم المستند.
  • التمييز بين علاقات 1:1 و 1:كثير و كثير:كثير في NoSQL.
  • تصميم نموذج بيانات واقعي يمزج الاستراتيجيتين بحكمة.

1. التضمين — Embedding (Denormalization)

نضع البيانات المرتبطة داخل نفس المستند ككائنات أو مصفوفات مضمنة. هذا يجعل القراءة أسرع (استعلام واحد بدل عدة استعلامات).

javascript
db.users.insertOne({
  name: "جمال سالم",
  // كائن مضمن
  address: {
    city: "عمان",
    country: "الأردن",
    zipCode: "11118"
  },
  // مصفوفة كائنات
  contacts: [
    { type: "phone", value: "+962-79-1234567" },
    { type: "email", value: "[email protected]" }
  ]
});
متى نستخدمه؟
  • العلاقة 1:1 أو 1:قليل
  • البيانات صغيرة الحجم
  • القراءة أكثر من الكتابة
  • لا تحتاج الاستعلام منفصلاً
متى نتجنبه؟
  • البيانات تنمو بلا حدود
  • الحجم قد يتجاوز 16MB
  • تحديثات متكررة على البيانات المضمنة
  • قد تحتاج للبيانات وحدها

2. المرجعية — Referencing (Normalization)

نفصل البيانات في مجموعات منفصلة، ونربطها عبر _id. هذا يحل مشكلة التضخم والعلاقات المتشعبة.

javascript
// مجموعة users
db.users.insertOne({
  _id: ObjectId("6512a1"),
  name: "جمال سالم",
  email: "[email protected]"
});

// مجموعة orders — تشير للمستخدم عبر user_id
db.orders.insertOne({
  orderNumber: "ORD-100",
  user_id: ObjectId("6512a1"),  // مرجع
  total: 150.00,
  status: "pending"
});

// لجلب الطلبات مع بيانات المستخدم تحتاج aggregation بـ $lookup
// (سنتعلمها في الدرس الثالث)
متى نستخدمه؟
  • العلاقة 1:كثير أو كثير:كثير
  • البيانات كبيرة النمو
  • الكتابة أكثر من القراءة
  • البيانات مستقلة منطقياً
متى نتجنبه؟
  • القراءة تحتاج كل البيانات دفعة
  • الفصل يُبطئ الاستعلامات
  • العلاقة بسيطة 1:1

3. قاعدة القرار الذهبية

"Data accessed together, stored together"

إذا كانت البيانات تُقرأ معاً دائماً ← ضمّنها. إذا كانت تُقرأ منفصلة أو تنمو بلا حدود ← ارجع إليها بـ Reference.

javascript
// السيناريو 1: مدونة
// المقال + التعليقات تُقرأ معاً -> تضمين
db.posts.insertOne({
  title: "مقدمة في MongoDB",
  content: "...",
  comments: [   // مضمنة لأنها تُقرأ مع المقال
    { author: "أحمد", text: "مقال رائع!" },
    { author: "سارة", text: "شكراً على الشرح" }
  ]
});

// السيناريو 2: متجر إلكتروني
// المستخدم له آلاف الطلبات -> مرجعية
db.orders.insertOne({
  orderNumber: "ORD-500",
  user_id: "6512a1",  // مرجع
  total: 350,
  items: [ /* items مضمنة لأنها تُقرأ مع الطلب */ ]
});

4. المزيج الذكي — أفضل ما في العالمين

في التطبيقات الحقيقية، نمزج الاستراتيجيتين حسب طبيعة كل بيانات:

javascript
// مستند طلب واقعي:
db.orders.insertOne({
  orderNumber: "ORD-101",
  
  // مرجعي: المستخدم كبير ومستقل
  user_id: "6512a2",
  
  // مضمن: عنوان الشحن ثابت مع الطلب
  shippingAddress: {
    city: "الرياض",
    country: "السعودية"
  },
  
  // مضمن: المنتجات جزء من الطلب
  items: [
    { product_id: 101, quantity: 2, unitPrice: 1500 },
    { product_id: 102, quantity: 5, unitPrice: 50 }
  ],
  
  total: 3250,
  status: "pending"
});

// لماذا هذا التصميم؟
// • user_id مرجعي -> لأن المستخدم له بيانات كثيرة مستقلة
// • shippingAddress مضمن -> لأن العنوان يُقرأ دائماً مع الطلب
// • items مضمنة -> لأنها جزء لا يتجزأ من الطلب
التطبيق العملي المباشر

تحدي الدرس الأول: نمذجة البيانات

قم بالتعديل على الكود بداخل المحرر أدناه لتطبيق المتطلبات التالية:

  1. أضف مستند users يحتوي على كائن address مضمن.
  2. أضف مصفوفة contacts مضمنة (كائنات phone/email).
  3. أضف مستند orders يشير للمستخدم بـ user_id.
  4. أضف مستند مركّب يجمع التضمين والمرجعية معاً.
  5. اعرض جميع المستندات + اطبع العدد الإجمالي.
⏳ جارٍ تحميل المحرر التفاعلي...