top of page
  • תמונת הסופר/תJonathan Barda & Niv Sluzki

מתכון לאבסטרקציה בשלוש שכבות




מכירים את זה שאתם רוצים לעשות איזה שינוי רציני בדירה?

זה בדרך כלל הולך ככה:

״נעיף את הספה, השידה, המיטות ושולחנות.

ננקה את כל הבית עם אקונומיקה.

נצבע את כל הקירות בצבע קרם ברולה.

נקנה מלא תמונות.

נמלא בעציצים.

נעשה סאונה במקלחת.

יומיים-שלושה, סיימנו.״


בואו, זה לא עובד. עדיף כבר לעשות פינוי בינוי.


כשיש יותר מדי דברים שצריך לסדר - קשה לנו להחליט מאיפה מתחילים.

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


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



איפה מתחילים?


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


אבל אנחנו לא היפסטרים.

יותר צפון מערביים כאלה, אנגלוסקסיים-אורתוגונליים.


ולמרות שאלה טיפים לא רעים, הם מאוד מאוד כלליים ולא נותנים לנו מתודולוגיה ברורה ל״איך להזיז את הקוד שלנו״.

ואוי, כמה שאנחנו אוהבים להזיז קוד.


אולי נמליץ לכם לקרוא את Clean Architecture? אנחנו לקחנו ממנו הרבה מאוד רעיונות.

אבל Uncle Bob קצת דוגמטי בעניינו וחלק גדול מהטכניקות שהוא מציע לא בהכרח מתאימות לכל שפה וחברה.

ויותר חשוב מזה, אם תקראו אותו - איך נוכל להתנשא עליכם ולשאול: ״מה, לא קראתם את קלין ארכיטקצ'ור? נו טוב, זה לא מתאים לכל אחד...״.


אה הנה רעיון, אנחנו הרי בלוג על פייתון.

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

גם זה לא.


לא באנו לבאס.

אנחנו רוצים להציע רעיון שמתבסס על כל הדברים שציינו לעיל ועל עוד הרבה רעיונות של אנשים אחרים. אנחנו רוצים להרכיב מכל הרעיונות האלה מתכון שיעזור לנו לגשת לבעיות מהסוג הזה.


אז איך מתחילים לסדר את ה-Backend שלנו כשהוא במצב מבאס?

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



היי, אתה נורא מוכר לי...


הדבר הראשון שאנחנו צריכים לעשות זה לזהות את המודל המנטלי.

זוכרים אותו מהפעם שעברה?


המודל הזה מורכב מכל מיני אלמנטים וחוקים.

ניקח לדוגמא את מערכת הקניות שלנו, שקיימות בה הישויות הבאות, ולכל אחת מהן חוקים משלה:

  1. ישות: עגלת קניות. חוקים: איך מוסיפים ומוציאים מוצרים מהעגלה, איך מציגים את המחיר הנוכחי, איך עושים צ׳ק אווט, איך עושים תחרות עגלות.

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


תחרות עגלות, אילוסטרציה

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

שימו לב שמונחים כמו: טבלאות או Database או תור הם ממש לא חלק מהמודל.

זה השלב לכתוב דברים על דף, לצייר כמה חצים ועיגולים, ולקשקש מסביב כל מיני חוקים.

תאמינו לנו, זה נראה מרשים מהצד.


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

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



שלוש, ארבע, לעבודה


כמו כל מתכון טוב, נתחיל מהסוף - התמונה של העוגה המטורפת עם הפצפצים למעלה.

כשנסיים, אנחנו מצפים לצאת עם עוגת שוקולד ב3 שכבות:

  • שכבת ה-API למעלה - Endpointים שאליהם פונים השירותים השונים (החל מ-ui ועד סרוויסים אחרים)

  • קראסט של DAL למטה (ה-Data Access Layer שאחראי לנהל את הגישה ל-DB)

  • ובאמצע, מה שמחזיק את הכל, שכבה נימוחה של Domain ו-Service שמנהלת את כל ההצגה ומחזיקה את העוגה דבוקה טוב טוב.

במילים אחרות, המטרה שלנו בתהליך הזה היא לחלץ את המודל לשכבה פנימית שלא תהיה תלויה באף טכנולוגיה.

אז איך עושים את זה?


שכבת ה-API

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


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

אתם אמורים לקבל Endpoints כאלו:


בדוגמה אנחנו משתמשים ב: flask, marshmallow, flask-apispec



אותן פונקציות שאנחנו קוראים להן דרך Endpoints (בדוגמא שלנו: add_item), נמצאות ברמת ה-Service. שימו אותן בקבצים בהתאם לנושא שהן מטפלות בו:

  1. טיפול בעגלת קניות

  2. טיפול במוצרים

מה עם הלוגיקה של מה שביניהן?

אתגר לקורא/ת.


תעשו Commit, תפתחו Merge \ Pull Request, שימו את זה על ה-Reviewer האהובה עליכם, וצאו לנוח בסופ״ש.


שכבת ה-Service

בשלב השני אנחנו נעבוד עם הפונקציות ברמת ה-Service.

אלה יהיו החלקים במערכת שמבצעים פעולות לוגיות במודל באמצעות אבסטרקציה לעבודה מול משאבים שונים: Storage, Events וכו׳.


נרצה להפריד בין שכבת ה-Service לשכבת ה-Repository (זו שאחראית על הפעולות מול ה-DB).

כדי לעשות זאת, ניקח כל פעולה מול ה-DB ונשים אותה בפונקציה משלה. נשאר עם פונקציות ב-Service שנראות כך:



ושוב: MR\PR, ולכו לים.


שלב ה-Domain

בשלב השלישי ננסה לאסוף את האובייקטים שירכיבו את הדומיין שלנו.

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


שכבת ה-Repository

הגענו לשלב האחרון במתכון הנוכחי.

קחו את הפונקציות ברמת ה-Repository, אלה שעובדות מול ה-DB והפרדנו אותן מה-Service בשלב השני, ותדאגו לצמצם מהן לוגיקה.

הרעיון הוא שרוב הלוגיקה תשב ברמת ה-Service, והיא צריכה להתבצע באמצעות אבסטרקציה מסוימת אל מול ה-Repository.

למשל היכולת לשמור מידע ואז לעשות Commit, או לשלוח Event לאוויר העולם צריכה להישמר בידיים של ה-Service ולא להיות חלק מה-Repository, שלרוב יכיל אך ורק קריאות ל-DB.

בסוף אתם אמורים לקבל משהו כזה:


לא המצאנו כלום.

כל אחד מהמקורות שסיפרנו לכם עליהם למעלה (וגם DDD שעוד לא הזכרנו) מתארים מודל כזה.

אנחנו מציעים שיטה שלוקחת קוד קיים ומחלצת מתוכו את הארכיטקטורות האלה, ואנחנו אוהבים לתת לקוד לכתוב את עצמו.


אם נסתכל על Clean Architecture, נראה שהמתכון שלנו מביא אותנו לאותה עוגה:

טוב, זה לא בדיוק אותו הדבר. אמרנו לכם, דוד בוב עושה דברים טיפה אחרת.



זה לא נגמר, זה רק הסוף


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

תצטרכו לקבל הרבה החלטות לא פשוטות בתהליך:

  • מתי להתפשר על ביצועים לטובת אבסטרקציה - ומתי לא.

  • איך לפרק את התהליך הזה לתתי משימות כדי להבטיח שלא תעשו ריפקטור עצום (בבקשה לא).

  • איך לקבל החלטות במקומות שהמתכון הזה לא מתאים להם.

  • האם מתיאס הוא מוצר חלב או יותר פרווה.

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



למה לי, למה לי? רק תגיד לי


אז למה אנחנו בעצם עושים את כל זה?


בשורה התחתונה - זה עושה סדר.

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

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


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

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

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

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



נשמה, מה עם טיפ?


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

  • אל תעשו אופטימיזציה מהר מדי. גם מפתחים מנוסים מאוד חוטאים בזה. אתם נכנסים לפרויקט רציני והדבר האחרון שאתם צריכים זה להוסיף עליו עומס עם ״אם אני כבר נוגע בחתיכה הזאת בקוד, בואו נגרום לה לרוץ יותר מהר״.

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

  • בחייאת, תעבדו בשלבים. אל תפתחו לנו MR \ PR ענקי שיקח לנו שבוע רק להבין מה עשיתם שם.

  • כנראה שלא תהיה לכם יכולת לקחת חודש הפסקה ולעשות Refactoring לקוד שלכם. שיטה טובה היא להתחיל לטפל באיזור בעייתי רק ברגע שצריך להכניס אליו קוד חדש.

  • תעצרו כמה שיותר מוקדם. תנו לשינויים לשקוע להתייצב ולחלחל במערכת. תקנו טעויות שעשיתם בתהליך ותנו לקוד לכתוב את עצמו.


סיכום


הצענו פה מתכון שעובד לנו בחברה בתהליך של מעבר מ״מהרבה בלגאן״ למשהו מובנה וטוב.

המתכון עובד לנו מהסיבה הפשוטה - אנחנו ממקסמים קוד נקי לוקאלית.

אנחנו משתמשים בכללי אצבע כדי לייצר סדר נקודתי שלאט לאט מתורגם לסדר משמעותי בכל רחבי הקוד.


חילקנו את ה-API הרקוב שלנו מהפוסט הקודם ל-3 שכבות יפות: API, Domain, Repository.

דאגנו שכל שכבה תעשה רק את מה שהיא אמורה לעשות על ידי חילוץ ה-Business Logic לתוך פונקציות ומיקום שלהן באיזור הנכון.


המתכון הזה לא מחליף ארכיטקטורה טובה ומסודרת לאורך זמן, אלא רק הצעה לשלב הראשון.


בפוסטים הבאים נספר איך אנחנו מתמודדים עם בעיות מורכבות יותר ועל שלבים נוספים שהקוד שלנו עובר בדרך למיקרו סרוויסים.



1,056 צפיות2 תגובות
bottom of page