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 مضمنة -> لأنها جزء لا يتجزأ من الطلب التطبيق العملي المباشر
تحدي الدرس الأول: نمذجة البيانات
قم بالتعديل على الكود بداخل المحرر أدناه لتطبيق المتطلبات التالية:
- أضف مستند
usersيحتوي على كائنaddressمضمن. - أضف مصفوفة
contactsمضمنة (كائنات phone/email). - أضف مستند
ordersيشير للمستخدم بـuser_id. - أضف مستند مركّب يجمع التضمين والمرجعية معاً.
- اعرض جميع المستندات + اطبع العدد الإجمالي.
⏳ جارٍ تحميل المحرر التفاعلي...