עד כה דיברנו בעיקר על MongoDB CRUD במאמר האחרון כיסינו את ה-D (מחיקת documents ואפילו collection שלם). בת'כלס זה מה שצריך על מנת לתפעל מסד נתונים שמישהו אחר בנה לנו. אבל בחיים האמיתיים אנו נדרשים לבנות ולתכנן את מסד הנתונים שלנו או לפחות חלק ממנו. גם אם קיבלתם את ה-MongoDB כמצב נתון משמים, יגיע הרגע שבו תצטרכו לבנות collection משל עצמכם. בדיוק בשביל זה אנו נדון במודלים של מידע – כלומר איך לייצג מידע ב-MongoDB. זה יותר קשה ממה שחושבים כי רובנו (כולל אני! או בעיקר אני!) רגילים לחשוב במושגים של מסדי נתונים רלציוניים וכאשר אנו מתכננים את מסד הנתונים שלנו הרבה פעמים אנו חושביים בכיוון של JOINים, דבר שלטוב או לרע אין ב-MongoDB. גם מונחים כמו Foreign Keys שהם ממש טבעיים לנו לא ממש קיימים ב-MongoDB. באופן עקרוני, MongoDB עובד במיטבו כאשר אנחנו לא מנרמלים מסד נתונים. אבל אם אנחנו כן רוצים ליצור רמה מסוימת של נירמול – אז כאן נדבר על מה יש ואיך יוצרים את זה. ואם אין לכם מושג מה זה נורמליזציה ומאיפה באתי אליכם – קראו את המאמר שלי על נורמליזציה במסדי נתונים.
הבעיה הנפוצה ביותר, כזו שסביר להניח שתתקלו בה, היא עמוד עם תגובות – כמו טוקבקים (לשם הנוחות בלי שרשורים). יש לנו כאן שתי ישויות. אחת עמוד, שיש לו: id, title, body ותגובות שיש להן: id,title,body וגם page_id – כי כל תגובה 'שייכת' לעמוד מסוים.
אם היינו משתמשים ב-MySQL, אז ה-page_id היה ה-Foreign Key של ה-comment וכאשר היינו רוצים לשלוף את כל התגובות היינו מבצעים שאילתה לטבלת ה-comments כשהמפתח שלנו הוא ה-foreign key. עד כאן זה פשוט ונחמד – האם אפשר לעשות את זה ב-MongoDB? אז התשובה היא: בוודאי שכן! הרי MongoDB יוצר לנו id. את ה-id הזה אפשר וכדאי לשים איפה שצריך.
ישנן שתי דרכים עיקריות לשימוש ב-references. הדרך הראשונה היא דרך ידנית, פשוטה ביותר – בואו ונניח שיש לנו טבלת pages:
for (i = 0; i < 100; i++) {var title="number "+i;var body = "Page Body Number "+i; db.pages.insert({"title":title,"body":body});}
הקוד שלעיל יצור לנו טבלת pages עם מאה דפים. משהו שנראה ככה:
{ "_id" : ObjectId("541e99d7f5e52579d9d7b625"), "title" : "number 14", "body" : "Page Body Number 14" }
נניח ומישהו הגיב לעמוד מספר 14. ב-document של התגובה כל מה שאני צריך לעשות זה להכניס את ה-ObjectId של העמוד שאליו אנו מגיבים לחלק שיהיה לי קל לשלוף אותו אחר כך. משהו בסגנון הזה:
db.comments.insert({"title":"Comment title","body":"body title","page":ObjectId("541e99d7f5e52579d9d7b625")})
מה הבעיה? שאת זה האפליקציה אמורה לעשות. זו דרך מאוד לגיטימית אבל יש עוד דרך שימושית שנקראת Database Reference או בקיצור DB REF. חשוב לי להבהיר שמדובר בקונבנציה – כלומר בניגוד ל Foreign Key שהוא מחייב – כאן מדובר במשהו שהוא לא מחייב – כלומר אתם יכולים להכניס ל-ObjectId שטויות מוחלטות ו-MongoDB לא יצעק ולא יתריע.
באופן עקרוני, DB REF מצריך מאיתנו גם את ה-ObjectId שאנחנו מתחברים אליו וגם את שם ה-collection שה-document שאנו מקשרים אליו נמצא בו. את שני הנתונים האלו אנו מכניסים באופן הבא:
db.comments.insert({
"title": "Comment title",
"body": "body title",
"page": {
"$ref": "pages",
"$id": ObjectId("541e99d7f5e52579d9d7b625")
}
})
אפשר לראות שזה מאוד מאוד דומה להכנסה ידנית של ObjectId. אז למה בכל זאת להשתמש בזה? קודם כל יש כאן ממש פירוט מאיזה collection ה-document שיש אליו הפניה מגיע. שנית, כאשר אנו מבצעים קריאה, MongoDB בחלק מהדרייברים שלו מבצע טעינה מוקדמת לאובייקטים שיש אליהם reference, דבר שיכול להיות נהדר לביצועים (או מיותר לחלוטין, תלוי באפליקציה). באופן עקרוני הדוקומנטציה של MongoDB ממליצה על שימוש ב-reference ידני, אלא אם כן יש לכם סיבה ממש טובה. סיבה טובה יכול להיות, למשל, וודאות גמורה שתקראו ל-documents שיש אליהם references מיד אחרי שתבצעו קריאה ל-document שמכיל אותם. למשל, אם אתם קוראים להזמנה ויש שם הפניה למשתמש שיצר את ההזמנה והאפליקציה שלכם מציגה גם את פרטי המשתמש.
בגרסאות החדשות של MongoDB אפשר לבצע DBREF לא רק ל-collection אחר אלא גם למסד נתונים אחר.
במאמר הבא אנו נדבר על embedded document.