חשיבות קשרים בין ישויות - דרופל, סיפור מקרה

דרופל

באתר גדול שבניתי לאחרונה נזקקנו ל-13 ישויות הקשורות זו לזו. תרשים ישויות (Class Diagram) מאפשר תצוגה מידית של הקשרים ביניהן.

הקדמה

לאחר אפיון ממושך הצלחתם לבודד ולאבחן את הישויות הנדרשות למערכת אותה יש לבנות. מהם הקשרים הנכונים בין הישויות השונות? איזה מערך של קשרים יהווה תשתית אובייקטלית נוחה ללקוח? אני משתף תרשים ישויות של אתר גדול שבניתי - כל ישות במערכת היא סוג תוכן במערכת דרופל - Content type - והן קשורות ביניהן באמצעות הרכיב המתקדם Entity Reference. אתן דוגמאות לחלופות קישור ובחירה ביניהן.

האתר: יהדות גליציה ובוקובינה.
סינונים לפי ישויות ספציפיות: ראו כאן (מומלץ).

האתר מכיל רבבות פריטי תוכן, פרי עבודת מחקר של עמותה ששמה לה מטרה לתעד את אחת הקהילות היהודיות הגדולות באירופה - גליציה לשעבר, אוקראינה כיום. קהילה עצומה זו, בת מיליוני נפשות, מתועדת מאז שנת 1400 ועד שנחרבה בזמן השואה (כיום נמצא האזור בתחום אוקראינה). מבחינת הדקדוק בפרטים והיחס ביניהם דומה המחקר - והאתר המשקף אותו - למעין מפעל של "יד ושם" בזעיר אנפין. אנשים, קהילות, בניינים, ארגונים ועוד. עיקר הפוסט מדבר על הצדדים הלוגיים של תכנון האתר ושל ביצועו. למעוניינים לקרוא על המיזם החשוב, המגלומני והמרגש הזה - קפצו לחלק התחתון של הכתבה.

מבנה ישויות ושדות

להלן התרשים ובהמשך ההסברים.
דרופל UML

מי נגד מי?

כמו שניתן לראות, מדובר בהרבה סוגי תוכן וחצים. איך אפשר להבין מי נגד מי?... התרשים כתוב בשפת דיגום שנקראת UML והרי מקרא לחלקיה היסודיים כפי שמופיעים בתרשים:

  • מלבן מייצג 'מחלקה' בשפה של מתכנתים ומנתחי מערכות, 'תבנית עמוד' בשפה של לקוחות ובשפה של דרופל: 'סוג תוכן'. הערה למתכנתי דרופל 7 ומעלה: באופן גנרי יותר, החל מגרסת דרופל 7 מלבן מייצג Entity אך לרוב מדובר בישות מסוג Content Type.
  • חץ מייצג קשר וראש החץ את כיוון הקשר. הערה לאוהבי מסדי נתונים: החל מגרסת דרופל 7 כל שדה מהווה טבלה במסד ולכן יחס של רבים-לרבים הנו מובנה במערכת. בגרסת דרופל 6 טבלה ייעודית לכל שדה דרופלי הייתה רק אם באחד מסוגי התוכן נעשה שימוש של רבים-לרבים.
  • המספרים שליד ראש החץ מייצגים קרדינליוּת, כלומר, האם הקשר עבור פריט מסוים הוא יחיד או רבים. למשל, פריט תוכן מהסוג מצבה (Tombstone) יכול להיות מצוי רק בפריט אחד מסוג התוכן בית קברות (Cemetery), ולכן ערך הקרדניליות הוא אפס עד אחד (אפס מציין שלא חייב שיהיה קשר, זה במקרה של מצבה שלא ברור עדיין לאיזה בית קברות היא שייכת). לעומת זאת, פריט תוכן מסוג כרטיס (Card) יכול לאזכר מספר פריטי תוכן מסוג ארגון (Organization) ולכן ערך הקרדינליות הוא אפס עד * (כוכבית מציינת ריבוי).

חשיבות כיוון הקשרים

בבניית התרשים נלקחו בחשבון הן הלוגיקה המתבקשת בין הישויות והן מספר הפריטים החדשים המוזנים מידי תקופה. בנייה נכונה של הקשרים בין הסוגים השונים הנה קריטית: 1. להזנה נוחה של ממצאים חדשים במהלך חייו של האתר; 2. למניעת טעויות בהזנה ובעריכה של פריטי תוכן.

מדוע? - אין זה נוח מבחינת המשתמש לשוב ולערוך פריט תוכן א ולקשר אותו לפריט ב, הפריט החדש אותו הוסיף דקה קודם לכן. השיטה של עריכה מחודשת לא רק שאינה נוחה, אלא שיש בה גם סכנה מסוימת של מחיקה בטעות או שינוי בטעות של נתונים בעת עריכה מחודשת של הפריט המקשר.

למשל, לא היה זה שגוי מבחינה לוגית לקשר את הסוג בניין לסוג אדם. בתרשים ניתן היה אז לסמן זאת בניין->*אדם, כלומר, בניין מצביע לאדם. המשמעות של סימון כזה הייתה אז שפריט תוכן מסוג בניין עשוי להתקשר לכמה פריטי תוכן מסוג אדם (שהרי בבניין עשויים להתגורר מספר אנשים, קל וחומר לאורך חייו של הבניין).

אולם, מחקירה שניהלתי מול הלקוח, הבנתי שישנם הרבה יותר פריטי תוכן מסוג אנשים לעומת פריטי תוכן מסוג בניין - וזה גם נשמע הגיוני. כמו כן, וזה חשוב לא פחות, במשך חיי האתר - כלומר השנים הבאות לאחר עליית האתר לאוויר - יוסיפו מזיני התוכן באתר הרבה יותר פריטי תוכן מסוג אדם מאשר פריטי תוכן מסוג בניין - וגם זה הגיוני, שהרי מצבות ומסמכים לפיהם יודעים על קיומם של האנשים בעבר מתגלים בכמות רבה יותר מאשר בניינים שעומדים גלויים. מכיוון שכך, עריכה מחודשת של ישות מקשרת - עריכה לא רצויה, כאמור - תתרחש בתכיפות נמוכה פחות אם הקישור יהיה בכיוון אדם->בניין ולכן זה הקישור שנבחר.

אגב, מבחינת התצוגה, כיוון הקשרים אינה משנה. ניתן להציג כל וריאציה של השדות - הן של פריט התוכן המקשר והן של פריט התוכן המקושר - בכל אחד מפריטי התוכן. כיצד?

  • לגבי הפריט המקשר אין כמובן כל בעיה, שהרי שדותיו מוצגים בעמוד באופן טבעי, כולל קישור לפריט המקושר. אם נחזור לדוּגמה שלנו, אז בעמוד המציג אדם, ניתן לראות את שדותיו (שם פרטי, שם משפחה וכולי) וכן קישור לכל פריטי התוכן מסוג בניין אליהם הוא מקשר. לחיצה על הקישור תוביל אותנו לבניין המסוים המקושר. לחלופין, ניתן לבחור בתצוגה שונה של פריטי התוכן המוצבעים, על מנת להציג גם את שדותיהם, כבר בפריט המצביע. למשל, כבר בעמוד של אדם מסוים להציג תמונות של כל הבניינים בהם התגורר.
  • לגבי הפריט המקושר לכאורה קיימת בעיה, מכיוון שהפריט המקושר אינו "מכיר" פריטי תוכן המצביעים עליו. בדוגמה שלנו, בניין אינו יודע מי האנשים שהתגוררו בו (לא כי הוא בניין ולכן הוא טיפש אלא בגלל כיוון הקשרים). אך גם בעמוד הבניין ניתן בהחלט להציג את אותם אנשים או שדות שלהם. ניתן ליצור רשימה של כל האנשים המקושרים לאותו הבניין (המצביעים עליו) באמצעות שאילתא למסד הנתונים, תוך אספקת המפתח המזהה של הבניין. במערכת דרופל מבצעים זאת באמצעות היבט (רכיב Views המפורסם) תוך שימוש בארגומנט (Contextual Filter) - הוא המפתח המזהה, קרי: node:nid.

מה עוד מעניין פה

קישור רפלקסיבי - סוג התוכן אדם מקושר לא רק לסוגי תוכן אחרים אלא גם לעצמו לשם יצירת יחסי קרבה במשפחה. שלושת החצים מייצגים את הקשרים הבאים: 1. אביו של אותו אדם; 2. אמו של אותו אדם; 3. זוגן של אותו אדם. ומה לגבי ילדים? - זהו נתון המחושב על פי אחד משני הקשרים הראשונים.

מצביע עשיר - סוג התוכן תמונה (הכי למעלה) הנו סוג טכני בעיקרו, ומטרתו לאפשר שדות לצד התמונה (מתי צולמה, מי צילם, וכדומה). כל התמונות המצביעות לפריט תוכן מסוים מוצגות באמצעות מצגת נעה (סליידשואו) בעמוד של אותו פריט תוכן. כמובן, תמונה מסוימת יכולה להיות משויכת ליותר מפריט תוכן אחד וכן לפריטי תוכן מסוגים שונים. באופן זה נחסך זמן בהזנת התמונה לכל פריט בנפרד.

מוצבע עשיר - סוג התוכן קהילה (הכי למטה) אינו מכיל שדות רבים אך הוא מוצבע על ידי סוגי תוכן רבים, בשל מרכזיותו בעולם הישן. בפריט תוכן מסוג זה מופיעות רשימות עם כל הפריטים - מכל סוגי התוכן השונים - המצביעים על אותה קהילה. באופן זה ניתן לראות בעמוד של קהילה מהם הבניינים, האנשים, הארגונים, בתי הקברות, המפות וכולי המקושרים לקהילה זו. כמובן, בכל פריט מקשר יש קישור חזרה לעמוד הקהילה.

מוצבע מתוּוֵך - בפריט תוכן מסוים ניתן להציג פריטי תוכן שאינם מצביעים עליו ישירות אליו אלא באמצעות פריט תוכן ביניים. למשל, ניתן להציג תמונות של מצבות בפריט תוכן מסוג בית קברות, ניתן להציג רשימת מסמכים (פריטי תוכן מסוג Doc) עבור אדם מסוים, וכדומה.

דוגמאות לחיפושים וסינונים באתר

1. עמוד סינון פריטים בסוג תוכן "אישיות" (Personality)

שדות הסינון מופיעים למעלה ומתחתם תוצאות הסינון. לחיצה על שם האישיות או על התמונה שלו, תוביל לעמוד המוקדש לו. כמובן, באותו העמוד, יש קישור לקהילה (Community) במסגרתהּ פעל. מימין ישנו תפריט המאפשר דילוג בין סינונים שונים - כל אחד מהם מאפשר סינון פריטי תוכן מסוג מסוים, על פי השדות המסוימים שקיימים באותה הישות. תמונת המסך כאן הנה של סינון לפי השנים בהן חיו אישים שעסקו בכתיבה.
דרופל אנשים

2. עמוד סינון פריטים בסוג תוכן "מסמך" (Document)

כאן מובאים רק שדות הסינון. שימו לב שהשדות כאן שונים, בחלקם, מהשדות שבתמונה הקודמת. שדות השנים והקהילה מצויים הם בעמוד הסינון הקודם והן בעמוד סינון זה. לעומת זאת, בעמוד סינון זה אין מסנן שֵם (Name) מכיוון שלמסמך אין שדה שם וגם אין שדה משלח יד (Occupation) מסיבה דומה. לעומת זאת, בעמוד סינון זה יש אפשרות לסנן לפי סוג המסמך (מכתב, פרוטוקול, וכולי) - שדות ייחודיים לסוג תוכן זה.
דרופל מסמכים

3. עמוד סינון פריטים בסוג תוכן "מצבה" (Tombstone)

כאן המסננים כבר מוכּרים לנו. לאחר לחיצה על התוצאה נגיע לעמוד המצבה עצמו (התמונה העוקבת).
מצבות

ולהלן תמונת מסך של עמוד המצבה עצמו עם ערכי השדות השונים. ניתן לראות שישנם קישורים לפריטי תוכן קשורים: למשל, ישנם קישורים הן לקהילה בה חי האדם לשמו הועמדה המצבה והן לקהילה בה נמצאת המצבה (במקרה זה מדובר בקישור לאותו פריט התוכן - קהילת סולוטבין - מכיוון שפרידה דבורה בת-דוד חיתה ונקברה באותו המקום). כמו כן, ישנו קישור לבית הקברות בו נמצאה המצבה, ושדות רגילים שאינם שדות קישור (שדות טקסט, שדה מספר ושדה בחירה מתוך אפשרויות).
מצבה

4. סינון כללי בכל הישויות לפי שדות אשר משותפים לכולן

השדות הרשומים משמאל הנם השדות המשותפים לכל הישויות. מימין (באמצע התמונה) מצוי מסנן המאפשר לצמצם את התוצאות לפי סוג תוכן מסוים, ישות מסוימת. כמו כן, ניתן לראות שהמסנן הראשון מאפשר חיפוש לפי מלל חופשי (Free text) לפי מפתוח (indexing) של פריטי האתר. המפתוח מתבצע אוטומטית באופן שוטף באמצעות שעון-עצר פנימי (cron).
חיפוש דרופל כללי

דרופל אתר ארכיב ארכיון

מימין: כתבה מתוך ynet

הכתבה המלאה ארוכה ויש בה סרטון על המיזם.
קישור לכתבה במלואה.



הכלי לבניית תרשים הישויות

התרשים נבנה באמצעות הכלי המקוּון yUML.me ולהלן הסקריפט שמרנדר אותו. מה שאני במיוחד אוהב בכלי הזה זה שגם אם יום אחד הוא ייעלם מהרשת, הרי שיש לי את הסקריפט שהוא ברור ונוח לקריאה ולהבנה, ומהווה תיעוד למערכת.


[Person]->0..*[Building]
[Building]->[Community]
[Card]->0..*[Community]
[Card]->0..*[Organization]
[Card]->0..*[Person]
[Cemetery]->[Community]
[Doc]->0..*[Community]
[Doc]->1[Card]
[Doc]->0..*[Organization]
[Image]->0..*[Community]
[Image]->0..*[Person]
[Image]->0..*[Building]
[Image]->0..*[Doc]
[Image]->0..*[Tombstone]
[Image]->0..1[Personality]
[Interview]->1..*[Community]
[Map]->1..*[Community]
[Person]->0..1[Person]-[note: mother]
[Person]->0..1[Person]-[note: father]
[Person]->0..1[Person]-[note: spouse]
[Organization]->0..*[Community]
[Bibliography]->0..*[Community]
[Person]->0..*[Community]
[Person]->0..*[Bibliography]
[Person]->0..*[Organization]
[personality]->0..*[Community]
[Tombstone]->0..1[Cemetery]
[Tombstone]->0..*[Person]
Drupal report. Permalink: http://practicall.co.il/1/node/610