מיקרו-שירותים: API Facade

בפוסט זה אני רוצה לדבר על דפוס עיצוב נפוץ למדי בעולם ה micro-services – ה API Gateway.
בדפוס העיצוב הזה נתקלתי מכמה מקורות, כאשר כולם מפנים בסוף לבלוג של נטפליקס – משם נראה שדפוס העיצוב התפרסם.
מקור: microservices.io

לפני שנתחיל – אני רוצה להצביע על אלמנט מבלבל במינוח של תבנית העיצוב:

Facade הוא הוא אובייקט המספק תמונה (View) פשוטה של רכיבים פנימיים מורכבים (״complex internals״ – מתוך ההגדרה של GoF) – למשל כמו ב Layered Architecture.
Gateway הוא אובייקט שמספק הכמסה לגישה למערכות או משאבים חיצוניים – למשל כמו רכיב הרשת Proxy שנקרא לפעמים גם Gateway.
Proxy הוא אובייקט שמספק אותו ממשק כמו אובייקט אחר, אך מספק ערך מוסף בגישה לאובייקט (למשל Laziness או Caching).

יש משהו לא מדויק ומבלבל בשם ״API Gateway״. כפי שנראה, דפוס העיצוב הוא בעצם שילוב של שלושת הדפוסים הנ״ל.

מכל השמות, דווקא ״API Facade״ נראה לי המדויק ביותר, וזה שמכווין בצורה הטובה ביותר לאופן בו כדאי לממש את דפוס העיצוב – ולכן אצמד אליו. נחזור לדיון על השם במהלך הפוסט.

להזכיר: דפוס עיצוב, ככלל, הוא מקור השראה יותר משהוא "אמת מוחלטת" שכדאי להיצמד אליה בלהבלהבלה…..

דפוס העיצוב

API Facade החל בשל צורך של נטפליקס לארגון ה APIs שלהם: המערכת המונוליטית הפכה לעוד ועוד שירותים – עוד שירותים שעל ה clients היה להכיר. הצורך בהיכרות עם כל השירותים יצר dependency ב deployment שהפך את משימת ה deployment לקשה יותר. למשל: פיצול שירות לשני שירותים חדשים דרש עדכון של כל ה clients ו deploy של גרסה חדשה – גרסה שמודעת לכך ש API X עכשיו שייך לשירות B ולא לשירות A (מה אכפת ל Clients בכלל מהחלוקה הפנימית לשירותים?!).

ה API Facade הוא רכיב שיושב בין ה Client לשירותים, ומסתיר את פרטיהם הלא מעניינים. הוא מציג ל Client סדרת API פשוטים, כאשר מאחורי כל אחד מהם יש flow = סדרת קריאות ל APIs של שירותים שונים.

באופן זה, שינויים ב APIs של השירותים (פיצול, איחוד, או שינויים קלים בחתימה) לא משפיעים על ה Client ולא דורשים את עדכונו.
תפקיד זה הוא Facade קלאסי.

עוד בעיה שצצה היא עניין ה Latency:
כאשר ה Client ביצע יותר ויותר קריאות (כי יש יותר ויותר שירותים) עלויות ה latency של הרשת גברו:

מקור: הבלוג של נטפליקס

Latency לאמזון יכול בקלות להגיע ל 100-200ms. במקום לשלם על latency של קריאה אחת – שילמו על latency של הרבה קריאות. חלק מהקריאות הן טוריות (4 בתרשים לעיל) – ואז ה Latency האפקטיבי הוא פי 4 מ latency של קריאה יחידה.

בעזרת הצבת API Facade שמקבל קריאה יחידה, ואז מבצע סדרה של קריאות בתוך הרשת הפנימית (שהן זולות בהרבה: פחות מ 10ms באמזון, ופחות מ 1ms ב Data Center רגיל) – ניתן לקצר באמת את ה Latency של הרשת שעל ה Client "לשלם":

מקור: הבלוג של נטפליקס

חשוב לממש את ה API Facade כך, שהוא לא יגרע מהמקביליות שהייתה קיימת קודם לכן ב Client – אחרת הוא יכול לגרום ליותר נזק מתועלת. לצורך כך ממשים את ה API Facade בעזרת שפה/כלים שקל לבצע בהם מקביליות וסנכרון שיהיו גם קלים לתחזוקה, וגם יעילים מבחינת ביצועים. נטפליקס בחרה למשימה ב Closure (תכנות פונקציונלי) או Groovy עם מודל ראקטיבי (RX observables). אחרים בחרו בשפת Go או סתם #C או ג'אווה.

תפקיד זה, של ניהול קישוריות בצורה יעילה, היא תפקיד קלאסי של Gateway.
וריאציה אחרת של תפקיד Gateway היא כאשר ה API Facade יודע לתרגם את פרוטוקול ה Client האחיד (נאמר HTTP) לפרוטוקולים שונים של השירותים השונים (נאמר SOAP, ODATA, ו RPC) – וכך לסייע בקישוריות.

עניין אחרון שבו ה API Facade מסייע הוא עניין של העשרת התקשורת. למשל: אתם רוצים שכל ה clients יבצעו SSO (קיצור של Single Sign-On, דוגמת SAML 2.0) או Audit על הגישה לשירותים שלכם. מה יותר נוח מלעשות זאת במקום אחד מרכזי (מאשר בכל אחד מהשירותים, ואז להתמודד עם פערי-גרסאות)?

כנקודת גישה מרכזית, ה API Facade יכול לרכז פעולות אלו ואחרות. זהו תפקיד קלאסי של Proxy.

סכנה!

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

עצה חוזרת ונשנה היא להשאיר את ה API Facade רזה ומינימלי ככל האפשר – כדי שלא יסתבך.
מדוע?
משום שה API Facade הוא ריכוז של תלויות:

  • הוא תלוי בכל ה Services אותם הוא מסתיר
  • הוא תלוי בכל ה Clients אותם הוא משרת

את כל התלויות מהן ניסינו להיפטר – העמסנו עליו, מה שאומר שהוא הופך להיות ל "Single Point of Deployment". הרבה שינויים במערכת (שינוי API של שירות, או שינוי API של Client) – יחייבו לעשות גם לו Deploy.
אם עושים למישהו Deploy תכוף מאוד, כדאי מאוד שהוא יהיה פשוט, אמין, ולא "ייתפס באמצע פיתוח" שיעכב את ה deploy. מצד כזה בדיוק יכול להיות לרכיב שמטפל גם בתרגום פרוטוקולים (Gateway), וגם בסיפוק שירותי אבטחה (Proxy).

לכן, ה Best Practice הוא לבצע extraction של כל לוגיקה אפשרית מה API Facade לשירותי-עזר שיספקו שירותים שכאלה (תרגום, SSO, וכו'), בעוד ה API Facade הוא רק נקודת החיבור של שירותי העזר האלו ל API של ה Client.
התוצאה: ה API Facade נשאר "easily deployable" – הוא מתמחה רק בהחזקת תלויות רבות ובקריאה לרצפים (flows) של APIs של שירותים עסקיים, בעוד שירותי העזר שלו הם בעלי הלוגיקה, ולהם ניתן לבצע עדכון / deploy – רק ע"פ הצורך ובקצב שנוח לעשות כן.

כך בערך זה נראה:

איך תקשרו את זה לצוות שלכם? את העובדה שאתם רוצים שה API Facade יהיה רזה ומינימליסטי, ולא יכלול שום דבר אחר? שאתם לא רוצים "ליפול בפח" של יצירת רכיב מורכב שתלוי כמעט-בכולם, ובאופן זה הוא מערער את יציבות המערכת?

נכון! ע"י כך שתקראו לו "API Facade", ולא "API Gateway" או "API Proxy".
מכיוון ש "Facade", בהגדרה, הוא "שלד ועצמות", רזה ומינימליסטי, בעוד "Gateway" או "Proxy" – הם לא.

הנה עוד המלצה לאופן שיצמצם את התלויות שה-API Facade נושא עליו:
לבצע "partitioning" של ה API Facade לכך שיהיו כמה API Facades במערכת – אחד לכל Client.
נטפליקס גילו שהקמת צוות ייעודי לתחזוקת ה API Facade יצרה צוואר-בקבוק וחוסר תקשורת מול צוותי ה client – הלקוח העיקרי של התוצר. הם העבירו את האחריות לקוד ה API Facade לצוות ה Client – כאשר יש API Facade לכל Client שקיים. התוצאות היו חיוביות – וגם חברות אחרות אימצו גישה זו.
מאוחר יותר, נטפליקס איחדו את התשתית של כל ה API Facades לתשתית אחידה – עבור יעילות ואחידות גבוהות יותר. עדיין כל צוות אחראי להגדרת ה flows שלו (flow = קריאה נכנסת מה Client, שמתתרגמת לסדרת קריאות מול השירותים השונים).

סיכום

בתבנית ה API Gateway Facade נתקלתי כבר מספר פעמים. בקרוב, אנחנו ננסה גם ליישם אותה אצלנו ב GetTaxi.
התבנית נראית כמו חלק טבעי במחזור החיים של ארכיטקטורה: ארכיטקטורת Micro-Services הופכת לפופולרית, ואיתה צצות כמה בעיות חדשות. מה עושים? מתארים Best Practices, או דפוסים – שמציעים פתרונות לבעיות הללו, ומתקשרים אותם כ"דפוסי עיצוב".
עם הזמן, הפתרונות המוצלחים שביניהם יהפכו לחלק מהגדרת הארכיטקטורה של MSA, ויפנו את מקומם ל Best Practices חדשים…

API Facade הוא דפוס שעוזר להתמודד עם בעיה ספציפית שנוצרת בעקבות ריבוי השירותים של Micro-Services Architecture, והצורך של ה Client לתקשר עם כל "הסבך הזה" (יש כאלו שהחלו לצייר את השירותים כ"ענן שירותים"). API-Facade מקל על הבעיה, אך גם יוצר פיתוי מסוים ליצור "גוש מונוליטי של פונקציונליות, מלא תלויות" ממנו כדאי להיזהר…

שיהיה בהצלחה!

—-

מקורות:

דפוסי ארכיטקטורה: מיקרו-שירותים (Micro-Services Architecture)

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

מיקרו-שירותים הוא "הטרנד החם" בנושא הארכיטקטורה בשנים האחרונות, ונראה שהוא כבר עכשיו הדיח את ארכיטקטורת השכבות ממקומה והפך ל "ארכיטקטורת ברירת המחדל למערכות ווב" (או מה שאוהבים לקרוא לו היום Twelve-Factor App)

יש בזה הגיון רב: בסאפ יצא לי לראות מערכות בהן Layered Architecture התאימה בצורה נהדרת לאתגרים והדרישות. מדובר במערכות עסקיות, גדולות ומורכבות בהן ה UI הוא שיקוף של הסכמה בבסיס הנתונים.
במערכות מודרניות יותר, בהן ה UI הוא עשיר יותר ובעצם בסיס הנתונים הוא שיקוף של חווית השימוש שתוכננה, חלק מההנחות של ארכיטקטורת השכבות (למשל: "ה UI משתנה ללא שינויים ב Business Logic ו/או ה Persistence") – פשוט לא משקפות את המציאות.

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

חשוב לזכור: דפוס ארכיטקטורה הוא כמו "מרק מוכן בשקית": סביר להניח שווריאציות על הדפוס שמותאמות אליכם אישית יוכלו להיות טובות יותר מ"הצמדות לדפוס כפי שתועד, ללא עוררין". כמו כן חשוב לזכור שלמרות שזהו דפוס פופולרי במיוחד בימים אלו – זהו לא הפתרון לכל בעיה. עדיין יש מערכות, עם UI מודרני (וכו') – שדפוסי ארכיטקטורה אחרים (גם Layered Architecture) מתאימים להם יותר. חשוב לזהות את סט הבעיות הייחודי למערכת שלכם – ולהתאים את הארכיטקטורה שעונה בצורה הטובה ביותר עליהם, ולא דווקא את הארכיטקטורה הנפוצה / פופולרית / "נחשבת" ביותר.

הסבירו לי נא, בדקה, מה "הקטע" של מיקרו-שירותים?

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

הגורם הדוחף העיקרי לארכיטקטורה של מיקרו-שירותים הוא כנראה הנושא של Continuous Deployment: כיצד אנו מרכיבים את המערכת שלנו כך שנוכל לעשות deploy לגרסה חדשה 10 פעמים ביום – ומשם מאיצים את הקצב עוד ועוד?
ארכיטקטורה של שכבות בכלל לא מסייעת לעניין זה, אלא אם אתם הולכים לעשות deploy כל פעם ל Layer בודד אחר.

בפועל, רוב היכולות של מערכות הווב המודרניות מרכיבות גם UI, גם לוגיקה, וגם Persistence – כך שכל deploy דורש את כל שכבות המערכת.

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

  • ניתן לעשות לכל שירות deploy באופן בלתי-תלוי באחרים.
    לאמזון יש עשרות אלפי מיקרו-שירותים, והיא עושה deploy כל 10 שניות (בממוצע). איך ייתכן? היא פשוט עושה deploy כל פעם לחתיכה קטנה אחרת של קוד.
    בדרך כלל מיקרו-שירותים מורצים כתהליכים נפרדים של מערכת ההפעלה, המתקשרים זה עם זה על גבי HTTP / REST.
  • ניתן לפתח כל שירות באופן בלתי תלוי: CI, בדיקות, וסביבת העבודה היא בלתי-תלויה. אולי גם שפת התכנות.
    תכונה זו היא מרכזית מאוד, ומטרתה לשפר את ה scalability של הפיתוח: כמה מפתחים בצורה יעילה במקביל על הקוד. ההנחה כאן שהיא שיותר קל לפתח הרבה רכיבים קטנים – מאשר רכיב אחד גדול.
  • לכל שירות יש Persistence בלתי-תלוי.
    תנאי זה בא לשרת את שני העקרונות הקודמים – והוא לא מטרה בפני עצמה. Persistence היא תלות בין רכיבים במערכת, אם כי פחות מורגשת. אם שני שירותים ישתמשו באותן טבלאות בבסיס הנתונים – יהיה לכם הרבה יותר קשה להגיע ל deployment בלתי תלוי. גם אי-התלות בסביבת הפיתוח תשתפר מתנאי זה.
  • מיקרו-שירותים הם קטנים.
    "כמה קטנים?" – זהו דיון חם עליו אפשר לכתוב פוסט בפני עצמו. הנה כמה מדדים מקובלים (ולא-מתואמים) לגודל המומלץ למיקרו-שירותים. למרות שהם שונים – כולם מרמזים על "קטן למדי":

    • מספיק קטן כך שאדם אחד יכול להכיר היטב את כל הקוד של השירות ("כזה שיכנס כולו לראש שלי" – הגדיר ג'יימס לואיס, חלוץ בתחום ה MSA)
    • מספיק קטן שצוות אחד (שניתן להאכיל בעזרת 2 פיצות אמריקאיות גדולות) – יכול לפתח ולתחזק אותו.
    • כמה מאות שורות של קוד.
    • מספיק קטן שלא יהיה קשה לארגון "לזרוק" אותו – ולכתוב אותו מחדש.
      מספרים שדף הבית של אמזון מפעיל כ 100-150 שירותים שונים (תלוי במקרה המדויק) בכדי לשרת כניסה של משתמש.
  • שירות עושה "דבר אחד בלבד" – Single Responsibility Principle.
    כמובן שגם "מתפעל את הלוגיסטיקה של וולמארט" הוא דבר אחד בלבד – ולא דבר קטן בכלל!
    זהו כמובן כלל קונספטואלי, שלא ניתן לאמוד אותו "מתמטית" – ושיהיה עליכם ליצור קונצנזוס ארגוני לגביו. כלל זה בא לשרת ולסייע לכם לקיים את כל התנאים הנ"ל.
היתרונות העיקריים של ארכיטקטורת המיקרו-שירותים הם:
  • ארכיטקטורה שתומכת ב Continuous Deployment. שניה! האם CD הוא מטרה או אמצעי? ובכן… גם וגם. CD משרת מטרות עסקיות (feedback מהיר ולמידה מהירה) והוא מנבא מוצלח של ההצלחה העסקית של הארגון (מקור).
  • Scalability של הפיתוח, הרבה מפתחים יכולים לעשות הרבה שינויים במקביל – ולפחד פחות.
    הנחה סמויה – יש לכם לא רק את הארכיטקטורה של מיקרו-שירותים, אלא גם מערך מקיף של בדיקות אוטומטיות שהכרחיות לקיום של CD סביר.
  • High Availability – קריסה של שירות אחד (או חמישה) – לא משביתים את המערכת שלכם, אם הם לא קריטיים ל flows המרכזיים של המערכת. שירות קרס בצורה בלתי צפויה? יש memory leak? מכיוון שכל שירות רץ כתהליך נפרד של מערכת ההפעלה – הוא עשוי לא להשפיע על השירותים האחרים. קריסה של קוד ב Layered Architecture / Monolith – סביר יותר שתגרום להשבתה.
  • אפשור / קידום שכתוב הדרגתי של המערכת.
    אם למשל, החלטתם לעבור מרובי לסקאלה (עוד מופע של "Ruby doesn't scale") או מ Framework אחד ל Framework שני – המשמעות בארכיטקטורה Layered היא דרמטית: עצירה ארוכה בכדי לאפשר שינוי שכזה.
    מצד שני, זה הרבה יותר פשוט בארכיטקטורת מיקרו-שירותים: ניתן להתחיל להעביר שירותים ל Stack החדש בזה אחר זה – וחלק מהם לא להעביר לעולם. רק שימו לב שאתם לא נגררים לתפעול של stacks רבים רק כי "אפשר", או מחוסר תשומת לב – זו טעות קלאסית.
    תכונה זו של הארכיטקטורה היא שימושית במיוחד כאשר אתם חיים בעולם שבו המערכת שלכם נמצאת ב Refactoring תמידי (כי העסק דינאמי ומבנה השירות הישן כבר לא עושה את העבודה אחרי שנה-שנתיים…).
  • Scalability של ה production – מכיוון שאתם יכולים "לשכפל" ו/או לבצע אופטימיזציות על שירות X מבלי להתמודד עם שכפול או השפעות הביצועים של שירות Y שמתקשה להשתכפל. בעיות Scalability מטופלות בשיטה "פרה, פרה".
    נושא זה הוא גם, בד בבד, חולשה של מיקרו-שירותים, נסביר מיד.
באופן טבעי, יש לארכיטקטורת מיקרו-השירותים גם כמה חולשות:
  • קושי ב monitoring ושחזור בעיות ב production. כאשר יש לכם עשרות (שלא לדבר על מאות) שירותים שונים – קשה יותר לשחזר בעיות ולנתח. למשל: לאסוף ולסנכרן לוגים של שירותים שונים, במיוחד אם כמה שירותים התעדכנו לגרסאות שונות מאז. צפו השקעה בניהול והפצה של ה session id בין כל הקריאות. מיקרו שירותים "מפרקים את התמונה" להרבה תמונות קטנות, וכדי להבין מה קורה במערכת – יש להשקיע ב "להרכיב את התמונה בחזרה".
  • Scalability של ה production, ויעילות בכלל – מכיוון ששירותים שיכלו עד עכשיו לתקשר בזיכרון, מתקשרים כעת על גבי HTTP  – לכל קריאה נוסף overhead מסוים. מכיוון שיש הרבה מיקרו-שירותים, שיגרמו להרבה קריאות וה overhead הזה ילך ויגבר.
    פעמים רבות, ה overhead הזה מחייב מעבר ל I/O אסינכרוני – מה שמסבך את הקוד.
  • Refracting יכול להיות מורכב – אם הוא חוצה-שירותים.
  • Operational Complexity – בעוד monitoring הוא האתגר הראשון, הוא לא האחרון. תפעול של שירות בודד אולי הוא קל יותר, אך לתפעל מערכת של מאות שירותים (גרסאות, היכן רצים, כיצד לשדרג) – היא משימה לא קלה לכל הדעות. אומרים שאימוץ של מיקרו-שירותים מעביר מורכבות מהפיתוח ל Operations. סביר להניח שאימוץ ארכיטקטורה של מיקרו-שירותים תאלץ את גוף ה Operations שלכם "לעלות מדרגה" מבחינת היכולות שלו. אם הוא לא יצליח – זה הולך להיות כואב מאוד…

באופן מעט מפתיע, המונח Microservice Envy נוסף כמשהו שכדאי להיזהר ממנו, בדו"ח של Technology Radar Jan2015 של חברת Thoghtworks. למה מפתיע? כי Micro-Services הוא משהו שמדברים עליו קצת יותר משנה, אז הגענו מהר למדי למצב בו מתריעים משימוש יתר / שימוש ללא הבנה מספיקה. הנה הציטוט המדוייק:

We remain convinced that microservices can offer significant advantages to organizations, in terms of improving team autonomy and faster frequency of change. The additional complexity that comes from distributed systems requires an additional level of maturity and investment. We are concerned that some teams are rushing in to adopting microservices without understanding the changes to development, test, and operations that are required to do them well. Our general advice remains simple. Avoid microservice envy and start with one or two services before rushing headlong into developing more, to allow your teams time to adjust and understand the right level of granularity.

אז מה ההבדל בין מיקרו-שרותים ל SOA?

זו כנראה השאלה השנייה הכי נפוצה ששמעתי בכל דיון על מיקרו-שירותים (הראשונה היא: "אז מה הם בעצם מיקרו-שירותים?").

הקושי הבולט שאני נתקל בו בניסיון לענות על השאלה הזו, היא שבעוד שאנו מסכימים (כנראה) מהם מיקרו-שירותים – מי בכלל מסכים בעולם על ההגדרה של SOA?!

הדרך היחידה בה אני יכול לענות על השאלה הזו, היא להניח כל פעם על פרשנות שונה של "SOA" – ואז לנסות ולענות.

אם SOA עבורכם הם SOAP ותקני ה *-WS
אז התשובה היא פשוטה: אתם יכולים להשתמש ב *-WS גם במיקרו שירותים, אבל סביר שתקורת הפיתוח ותקורת הביצועים – יהרגו אתכם.
אחת מהנחות היסוד של *-WS היא "coarse grained services" – וזה היהפך המוחלט בהנחת היסוד של מיקרו-שירותים שהם קטנים. חוץ מזה – אין ספק שיש גם הרבה עקרונות משותפים.

אם SOA עבורכם היא ניהול ("mix and match") של שירותים בדמות ESB או CMDB
ESB (קיצור של Enterprise Service Bus) הוא הרעיון בו ניתן יהיה לעשות שינויים במערכת לא בקוד, אלא בקונפיגורציה – וכנראה ע"י power business users. יהיו הרבה שירותים שעושים פעולות טכניות (חישוב עלות טיסה, רישום הזמנה, וכו') – אך כל flow בעל משמעות יורכב בעצם ב ESB ע"י חיווט של השירותים הנתונים בקונפיגורציות שונות.

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

לגבי CMDB (קיצור של Configuration Management DB) – עדיין אין לי תמונה ברורה לגביהם. לי אישית זה נראה כמו "ניסיון שני" לעשות ESB, כאשר העקרונות מעט שונים (בשאיפה: לקחים) ואין שום התייחסות רשמית לקשר / לדמיון ל ESB – אולי בכדי לא להבריח משתמשים.

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

אם SOA עבורכם הוא מידול של המערכת לשירותים stateless ופונקציונליים
כלומר: אתם מאמינים שניתן לחלק את המערכת לפונקציות ("רכיב לכל משימה") – ולא דווקא ע"פ חלוקה OO. את השירותים עצמם – ניתן כמובן לפתח ב OO. כמו כן – אי-תלות מובנה בין השירותים.

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

ההבדלים שניתן למצוא (כי חיפשנו) הם:

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

האנטומיה של מיקרו-שירות

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

עד עכשיו סיפקתי סקירה high level של הנושא – אך יש עוד פרטים רבים שהייתי רוצה לגעת בהם.
למשל: כיצד מתחילים? כיצד "מפרקים" מערכת למיקרו-שירותים (להזכיר: MSA = Micro-Services Architecture)?

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

על השירות להיות self-contained: להכיל את ה deployment script העצמאי משלו, את הפרוייקט ב IDE משלו, את הבדיקות שלו, את התלויות שלו (ספריות צד-שלישי, בגרסאותיהן השונות) – וכו'.
מבחן טוב לעצמאות הזו היא מפתח חדש שמקנפג סביבה רק עבור השירות הזה, פותח רק אותו ב IDE – ומצליח לעשות שינויים ולהעביר אותם ל production מבלי להסתבך. השאיפה היא ליצור מערכת גדולה – מתוך הרבה רכיבים קטנים ופשוטים, ושפיתוח של כל רכיב קטן שכזה – יהיה פשוט כמו כתיבה של רכיב פשוט עצמאי (נאמר: תרגיל בקורס באוניברסיטה).

השלכה משמעותית של רעיון זה, הוא שאת השירות צריכה לכתוב קבוצת אנשים שיכולה לבצע את כל המטלות הנדרשות מהם (UI, Operations, DB, ולוגיקה עסקית). ע"פ Conway's law, אם בארגון שלכם יש "צוותי UI", "צוותי DB", ו"צוותי server" (ולא "צוותים פונקציונליים") – אזי סביר שהיישום של השירותים אצלכם יהיה: שירותי UI, שירותי DB, ושירותי Server – שזה נחשב מידול לא טוב של מיקרו-שירותים. ייתכן ויהיה עליכם לשנות את המבנה הארגוני בכדי להגיע ל MSA מוצלח.

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

הנה כמה תשובות לדילמות נפוצות. שימו לב שהן רק guideline – מכיוון שהן נענות "למקרה הממוצע", ולא לכל מקרה באשר הוא.

   האם על כל שירות להיות פרויקט עצמאי ב IDE?
כדאי שכן.
מצד אחד נראה ש"לתחזק סביבה פעם אחת" זה פשוט יותר, מצד שני אם אתם נוגעים רק ב 5% מהקוד במערכת, ועליכם לתחזק את הסביבות הנדרשות לקוד זה – בד"כ התחזוקה זו תגזול פחות זמן. למה לכם "להיתקע" עכשיו כמה שעות בגלל migration שעשו ברכיב שלא קשור אליכם ויש בו בעיות – רק בכדי לגרום ל build של הרכיב שלכם לפעול?!

   האם על כל שירות להיות מנוהל ב Git Repository עצמאי?
זה עניין של טעם. למשל: גוגל מנהלים 90% מהשירותים שלהם ב repository יחיד. Git repository לכל שירות – גם הוא סביר בעיני.

   מה עושים עם ספריות שכל השירותים צריכים? למשל Logging או אבטחה?

מה שאתם עושים עם כל ספריית Open Source שאתם משתמשים בה הרבה – הוסיפו אותה כ dependency לכל השירותים שזקוקים לה. זה אמנם לא שירות – אבל לא כל הקוד חייב להתמפות לשירותים, יכולים להיות גם "סתם רכיבים לשימוש חוזר". הרגישו נוח לייצר וריאציה שלכם על דפוס הארכיטקטורה.
אפשר לדמיין מיקרו-שירות שעובר ל production כתא חי: יש את פנים התא שהוא ייחודי, ועוד קצת infrastructure שיש לשלוח עם כל תא ל production בכדי לקיים אותו. וכן – יהיו הרבה instances של השירותים הללו חיים (מה שמקשה על ניטור ו root cause analysis).

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

   מה??? יהיו לי במערכת גרסאות שונות של 3rd Parties? יהיה עלי להתמודד גם עם באגים של SpringFW 3.0.1 וגם עם אלו של SpringFW 4.1.2?
ובכן – היכולת להשתמש בגרסאות שונות של ספריות הוא דווקא יתרון גדול של MSA. כשיש מערכת גדולה ועותק יחיד של הספריה – אנחנו נאלצים לרוב "להיתקע" עם גרסה ישנה לאורך זמן, או לצאת ל"מסע צלב" בכדי לשדרג אותה. הפחד מ"ריבוי באגים" הוא לא תמיד כ"כ ריאלי. בכל מקרה – ברור שיש תקורה בשימוש במספר רב של גרסאות של אותה הספרייה – הייתי ממליץ לנהל "רשימת גרסאות מאושרת" שניתן יהיה לבחור רק ממנה במוצר. לאזן בין גמישות לשליטה.

יש מצבים בהם יש בעיה טכנית להשתמש בגרסאות שונות של אותה הספרייה (למשל: כאשר השירותים הם לא תהליכים שונים של מערכת ההפעלה אלא למשל wars ב JEE application server, או שימוש ב jQuery כאשר UI של שירותים שונים מוצג זה לצד זה על אותו דף של דפדפן. מה עושים? מתפשרים, או מחליפים טכנולוגיה – כמו תמיד.

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

הייתי מסכם זאת כך: RDBMS (בסיסי נתונים רלציוניים) הם בעלי התאמה גבוהה לארכיטקטורת Layers, אך בעלי התאמה פחות טובה – ל MSA. אם אתם עובדים עם MSA (או רק CD) – הבעיות יהיו קטנות יותר אם תעבדו עם בסיסי נתונים שהם schema-less כמו K/V DB או Document DB.
האמת שהיכולות המתקדמות של RDBMS עודדו הפרות משמעותיות של עקרונות ארכיטקטוניים חשובים (הכמסה, שבירת ה Layering שהוגדר, וכו'). איך קיבלנו זאת כל השנים? – זה פשוט עבד.

   איך מנהלים Transactions אם הנתונים שלנו מבוזרים לטבלאות שונות?
פשוט לא עושים. לא מגיעים ל Internet Scale כשעובדים עם ACID. נוקטים מדיניות של Eventually Consistent.

   מה עושים כאשר שירות "תופס נפח", והוא כבר לא כ"כ קטן?
מפצלים.

   כיצד שירותים מתקשרים זה עם זה? כיצד הם יודעים על קיומו אחד של השני?
בדרך כלל לכל שירות יש URL וניתן להשתמש ב DNS ו Load Balancer בכדי לנהל כמה עותקים – ולמצוא עותק זמין. הכי נפוץ הוא להשתמש בתקשורת REST, הרי – למה להמציא את הגלגל מחדש. אפשר גם על גבי TCP/UDP, אולי RPC או Thrift – הכל ע"פ הצרכים שלכם. בד"כ יהיו לכם גרסאות שונות של ה APIs של השירות, וכמה גרסאות של API שיחיו במקביל – עד שתוכלו באמת להשבית אותם.

דפוס נפוץ בארגון APIs של מיקרו-שירותים הוא ה API Gateway (מסמך: כיצד נטפליקס עושים זאת): שירות נוסף שכל תפקידו לקבל בקשה מה Client (ב URL אחד וב HTTP request אחד) – ולקרוא לכמה שירותים, להרכיב את התוצאות, ולהחזיר הכל באותה הקריאה. שירות זה הוא סוג של Facade שיסתיר מ clients חלק מהשינויים במבנה השירותים (פיצול, שינויי API, וכו')

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

עקרון ההפרדה (isolation) הוא מספיק חשוב בכדי להצדיק תקשורת דרך הרשת. מצד אחד – זה מוסיף ל resilience של המערכת, מצד שני זו תקורה ברורה בפיתוח ובזמן הריצה.

הערה: חשוב לא "לסמוך" על שירותים אחרים. לכל קריאה לשירות אחר, נהלו timeout ונסו לפעול גם בלעדיו – אם אפשר. אחרת – אתם מאבדים את ה resilience האפשרי מארכיטקטורה זו, ומתכננים לעצמכם תקלת "דומינו": שירות אחד, אולי שולי, נופל – וכל השירותים אחריו בזה אחר זה, כי אף אחד לא תוכנן לעבוד בלי השירותים שבהם הוא תלוי.

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

   האם אין תשתית כזאת, שתפתור לי את כל הבעיות שנובעות מ MSA – ותשאיר אותי עם כל התהילה, והיכולת להתמקד בכתיבת הקוד העסקי?
יש כל-מיני ניסיונות. יש את סנסה ל node.js, את rodent לרובי, או DropWizard של Yammer לעולם ה JVM. משום מה כולם עוסקים בפיתוח של המיקרו-שירותים (החלק הקל) ולא בתפעול שלהם (החלק היותר מורכב).
בכל מקרה… אם אתם קוראים את הבלוג – אתם אמורים לדעת מה דעתי האישית בנושא.

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

Bounded Contexts

רעיון שחוזר על עצמו שוב ושוב בהקשר ל MSA הוא רעיון ה Bounded Context, רעיון של מתודולוגיית ה Domain-Driven Design (בקיצור: DDD) של אריק אוונס. הרעיון מזהה שה Domain Model שלנו (תיאור האובייקטים בעולם והקשרים ביניהם. לא בתוכנה – ב"ביזנס") הוא לא אבסולוטי – אלא תלוי הקשר.

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

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

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

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

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

יש לכך כמה פתרונות: אפשר לנהל Shared Context שמכיל את ההזמנה ומשותף לשני העולמות, ניתן לעשות "המרה" של אובייקט מ BC אחד ל BC שני, אפשר שה BC יראה רק view – אבל בבסיס הנתונים (שכבה נמוכה יותר) יהיה מודל שהוא aggregation של כל התכונות מכל המודלים, וכו' וכו'.

הקשר בין BC ל MSA הוא פשוט: אותן בעיות שהתמודדו איתן במשך שנים ב DDD, ובפרט בעקבות רעיון ה BC – צצות עכשיו בעולם ה MSA. בשני העולמות מאמינים שחלוקת המודל למודלים מקומיים וקטנים / פשוטים יותר – היא הדרך הנכונה להצליח.

 
 
 
 
 

בדיקות

בעוד אנו חווים ב MSA עלות לינארית להוספת שירותים (דבר טוב!), קל להגיע מהר לעלות מעריכית בכתיבת וניהול בדיקות אינטגרציה. אנו רוצים לבדוק את כל השירותים בכל התסריטים, בכל deploy – ויש יותר ויותר כאלו.
דווקא בדיקות יחידה (פשוטות, או של קבוצות של מחלקות) – הן דווקא פשוטות ועובדות היטב. מקסימום "זורקים" כמה Mock Objects לדמות את השירותים בהם השירות שנבדק תלוי – וזה אמור להסתדר בצורה קלה.
הבעיה היא יותר בבדיקה של אינטגרציה של כמה שירותים. כיצד והיכן מרימים סביבה? היכן מנהלים את הקונפיגורציה? האם יש לבדוק גם את התסריטים שתלויים בשירות שלנו – או רק את ה API של השירות עצמו? האם לבדוק עם שירותים אחרים חיים או רק mocks שלהם?
השימוש ב MSA פותח כל מיני שאלות על Testing שלא היינו צריכים להתמודד איתן קודם לכן.
טובי קלמסון ניסה לסכם את כל אפשרויות הבדיקה במצגת דיי מקיפה. קשה לי לתמצת את מה שהוא אמר – כי הוא חוזר על הרבה רעיונות מוכרים ומנסה להתאים אותם לסביבה של MSA:
  • נסו להריץ כמה שיותר בדיקות בזיכרון / עם mocks – שירוצו מהר.
  • כתבו יותר בדיקות יחידה ואינטגרציה ופחות בדיקות End-to-End שקשה לתחזק ("פירמידת הבדיקות").
  • בדקו לוגיקה בעזרת בדיקות יחידה, וקוד יותר "משעמם" עם בדיקות אינטגרציה או בדיקות-רכיב (בעיה: יש פרשנויות שונות לכמעט כל סוג בדיקה שנציין).
גישה אחרת (אציין: מקובלת) היא להשקיע פחות בבדיקות אינטגרציה – ויותר בניטור המערכת והיכולת לבצע root cause analysis מהיר. במקום prevention (בדיקות) – להתמקד בטיפול הבעיה ברגע שהיא צצה. יש הגיון כלכלי ברור לגישה זו, אם כי לוקח זמן להגיע לרמה "מספיק טובה" של ניטור והתאוששות בכדי שנסמוך על המערכת הזו להחליף את תפקידם של בדיקות האינטגרציה.
אם אתם יודעים לכתוב בדיקות אוטומציה – כנראה שתוכלו למצוא את הרכב הבדיקות שיעבוד למערכת שלכם. פשוט קחו בחשבון זמן לתכנון מחדש של מתודולוגית הבדיקות שלכם בעקבות MSA.

סיכום

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

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

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

החלטנו לעבור למיקרו שירותים – אבל הייתה הסתייגות רבה מניהול תהליכים נפרדים המתקשרים ב REST, בעיקר בגלל שזה קוד רב "ומשעמם" שיש להוסיף למערכת. המערכת כיום (כמו רוב מערכות ה Layered Architecture) מלאה בקשרים רוחביים בתוך ה Layers שקשה מאוד לנתק. באמת התרנו לעצמנו לקרוא מכל מקום ב Layer לכל פונקציה אחרת ב Layer – מה שגרם לריבוי תלויות. כמובן שגם ברמת ה DTO בעיות רבות נפתרו בעזרת "joins" – מה שקשר את הפונקציות השונות של המערכת גם בשכבה זו. ניתוח תלויות ראשוני נראה קצת מבהיל במחשבה שכל תלות הופכת לקריאת HTTP.

לכן…החלטנו לוותר על התכונה של "isolation by process" של MSA – ויצרנו וריאציה קצת חדשה: כל שירות מקבל פרויקט נפרד (pom.xml במייבן משלו) עם build משלו, בדיקות משלו, וכו' – והוא נארז לתוך קובץ jar. (המערכת כתובה בשפת ג'אווה + UI בשפת ג'אווהסקריפט). את כל השירותים אנו אח"כ אורזים לתוך קובץ war. גדול – ואז עושים deploy.

כיצד אם כן עושים deploy לשירות בודד?
שומרים תמיד את קובץ ה war. האחרון שעבר deploy, מעדכנים רק את קבצי ה jar. של השירות הרלוונטי (הוא עצמו + תלויות ישירות) – ועושים deploy מחדש.

איך מוודאים isolation בין השירותים?
הגדרנו שבכל שירות יהיה package מיוחד בג'אווה בשם facade המתאר את הממשק החיצוני של השירות – ורק לו שירותים אחרים יכולים לקרוא. אנו מתכוונים לאכוף התנהגות זו בעזרת JDepend שירוץ בעת ה build ויפיל אותו בעת חריגות.
בעת build של כל שירות – ה jar. שנוצר מתעדכן ב maven repository של הארגון, ושירותים אחרים שתלויים בו פשוט מגדירים תלות ל jar הזה ומקבלים גרסה עדכנית בעת ה build שלהם.

ל facade יכולים לקרוא שירותים אחרים או שכבה שנמצאת בתוך השירות (בשם webaccess) – במידה והשירות גם זמין על גבי HTTP.

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

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

אז… אל תפחדו ללמוד את החוקים, להבין אותם – ואז לשנות אותם קצת.

שיהיה בהצלחה!

—–

לינקים מעניינים

תיאור של חברת SoundCloud על האתגרים במעבר מאפליקציה אחת גדולה ("Monolith") לארכיטקטורה של מיקרו שירותים. מומלץ!
חלק א': http://goo.gl/1ciqbS, חלק ב': http://goo.gl/31Gmy7, חלק ג': http://goo.gl/0aeq3u.

פוסט ידוע, ומצוטט רבות, של ג'יימס לואיס ומרטין פאוולר על מיקרו-שירותים: http://martinfowler.com/articles/microservices.html.
גם שווה קריאה. הם עוסקים בכמה היבטים בהם לא נגעתי בפוסט.

הקלטה של סשן מ Buraco 2012 של Fred George על מיקור-שירותים מיקרו-שירותים: https://www.youtube.com/watch?v=2rKEveL55TY

יש גם מצגת של ג'יימס לואיס בנושא (איך לא?): http://www.infoq.com/presentations/Micro-Services

מצגת מעניינת: http://www.slideshare.net/fuglylogic/microservices-26369481

פרק בפודקאסט "Software Engineering Radio" שעוסק במיקרו-שירותים: http://www.se-radio.net/2014/10/episode-213-james-lewis-on-microservices/

רשמים ראשוניים מ GOTO; Berlin 2014

זה עתה חזרתי מ-3 ימי כנס \";GOTO\" שהתרחש בברלין.

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

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

Martin Fowler מגיע למתחם Kosmos – לתת את ה Keynote הראשי. חבר הסביר לי שהמבנה נבנה בתקופה הסובייטית – ושיש עוד רבים ממש כמותו.

כנסי ;GOTO הם סדרה של כנסים שרצים ברחבי ארה\"ב ואירופה (שיקאגו, ברלין, אמסטרדם, אורהוס, וקונפהגן) ומתמקדים ב Software Craftsmanship, ארכיטקטורה, אג\'ייל, DevOps, וטכנולוגיות \"חמות\".
הרבה מרצים מפורסמים מגיעים לשם כ\"דרך קבע\": Martin Fowler, Kent Beck, רוד ג\'ונסון, בכירים באמזון, עובדי ThoughtWorks השונים, ועוד. כמעט לא תמצאו שם אנשים מגוגל או מסטראט-אפים אלמונים, אלא יותר מרצים שהם מחברי ספרים (איני יודע אם זו דרישה מפורשת), מובילים של קהילות (למשל מובילת קהילת הפייטון באנגליה), או לקוחות של ThoughtWorks (למשל: חברות ביטוח ובנקים) ושל אמאזון (למשל: נטפליקס).

נדמה לי שהשנה הייתי הישראלי היחידי בכנס.

מה נשתנה?

חוץ מכך שהייתי ב QCON London 2008 (סדרת כנסים \"אחות\" של ;GOTO) – אני עוקב כמעט כל שנה אחרי תוכן כנס GOTO בכדי להתעדכן בטרנדים העכשוויים.

מגמה אחת שאני מזהה בתכנים, היא נוכחות גוברת של נושאי UX בכנס. לפחות ע\"פ מתכנני הכנס – נראה אנשי תוכנה הולכים ומתעניינים יותר ב UX כחלק אינטגרלי מהפיתוח – וכחלק שמעניין את אנשי הפיתוח. מי יודע? אולי עוד שנתיים נשמע על UX Culture (כמו DevOps culture) ועל כך שיש לשלב את איש ה UX כחלק אינטגרלי מצוות הפיתוח?

ז\'רגון

כמה ביטויים ששמעתי בכנס שוב ושוב, ושאני לא נוהג לשמוע כ\"כ ביום-יום:

  • a Unicorn – כינוי לחברות שהן thought Leaders בנושאי טכנולוגיה, כמו פייסבוק, נטפליקס, או Etsy, בניגוד ל Horse – חברות \"רגילות\".
    \"ההבדל היחידי בין unicorns לסוסים – הוא PR\" – מיהר אחד המרצים לנסות ולאזן את ההייפ. כן, גם אצל ה unicorns הקוד נרקב (\"code rots\").
  • Shaving Yaks – פעולה ארוכה ומתישה, שבסופה אף אחד לא זוכר כיצד ומדוע היא התחילה:
    \"?!Why the hell, were we shaving this Yak\"
  • Bounded Context – רעיון מעולם ה DDD, שמציע פתרון ל business logic מורכב מדי לתחזוקה.
    הרעיון הוא להגדיר \"הקשרים מוגבלים\" (bounded context) במודל,
    לדוגמה: יש לנו מערכת לניהול לקוחות. במקום ליצור מודל אחד שמטפל גם במכירות וגם בתמיכה (support), אנו יוצרים שני מודלים, בלתי תלויים, של העולם (לקוח, מוצר, עסקה) שכל אחד מתמחה בהקשר המוגבל שלו: מכירות או תמיכה.
    הרעיון הועלה כמה פעמים בכנס – כדרך לפרק מערכת מונוליטית גדולה למיקרו-שירותים.
  • Two-pizzas Team – ביטוי שצמח באמזון, ואומץ ע\"י SCRUM לבטא צוותים של \"בערך\" 8 אנשים (או 16 בחורות ממש רזות 🙂 ) – מה שניתן להאכיל בעזרת 2 פיצות אמריקאיות גדולות.
  • T-Shaped Person – תיאור המבטא איש מקצוע רצוי, בעל רוחב (קו אופקי), וגם עומק מקצועי (קו אנכי). הרוחב מתבטא בהכרות עם נושאים שונים והיכולת לתקשר היטב עם אנשים מתחומים שונים.
    באחת המצגות המציג הציג שארכיטקט אמור להיות π-Shaped Person, כאשר לו רגל של הבנה טכנית עמוקה, ורגל שנייה של הבנה עסקית עמוקה.
רעיונות שחזרו על עצמם שוב ושוב בהרצאות

  • Micro-Services היו כנראה ה-Highlight של הכנס, ומדברים עליהם כבר כארכיטקטורת \"ברירת-המחדל\" למערכות ווב בענן. 
  • Breaking Down Silos (למשל עם DevOps או עם ה Business) – בכדי להשיג אג\'ייל אפקטיבי. 
  • אג\'ייל (וסקראם בפרט) דורשים תיקונים. סקראם הוא מעט נוקשה, ולא כ\"כ פתוח לשינויים.
  • התלהבות גדולה מ Docker – ככלי ל Continuous Deployment מהיר יותר.
  • התלהבות מה Netflix Chaos Monkey (סיפרו עליו ב 3 הרצאות שהייתי, כולל ה keynote) – תהליך שתפקידו להרוג שירותים אמיתיים ב production בכדי לאתגר את מנגנוני ההתאוששות של המערכת.
  • שימוש ב Strangler Pattern על מנת לבצע מעבר הדרגתי בארכיטקטורה. בעיקר מ Layered Architecture ל Micro-Services Architecture.

ה Keynote הפותח (מרטין פאוולר)

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

  1. Software Development in the 21st Century – שבעצם עסק ב Micro-Services והחיבור הטוב שלהם לענן ול Continuous Deployment.
  2. מצגת ללא כותרת – מכיוון שהוא התקשה למצוא לה כותרת. אולי הקהל יוכל לעזור לו.
ההרצאה הראשונה הייתה מבוססת בעיקרה על הפוסט של מרטין ממרץ השנה על micro-services, וכללה מעט הסברים חדשים. בקיצור, מרטין מתאר שם 9 תכונות מקובלות של מיקרו-שירותים:

  1. Componentization via Services
  2. Organized around Business Capabilities
  3. Products not Projects
  4. Smart endpoints and dumb pipes
  5. Decentralized Governance
  6. Decentralized Data Management
  7. Infrastructure Automation
  8. Design for failure
  9. Evolutionary Design
נושא המיקרו-שירותים שווה פוסט בפני עצמו. האמת שיש לי כבר פוסט כזה בעבודה, נקווה שאגיע ל delivery…

ההרצאה השנייה היתה אוסף של רעיונות שונים, ולכאורה לא קשורים:

הרצון לבצע כמה תיקונים ועדכונים ל SCRUM – לאור הניסיון שנצבר
התלונה העיקרית הייתה בכך שה PO הוא ה\"מוח\" היחידי בכל הנוגע להגדרת המוצר – והמפתחים הם רק מבצעים.
מרטין סיפר על מקרה שקרה באמזון – בו מתכנת באמזון הציע פי\'צר חדש: \"לקוחות שקנו מוצר X קנו גם מוצר Y, עם קישור למוצר Y\". הרעיון עלה ל VP Marketing שביטל אותו במקום: \"לא צריך להפריע ללקוחות בזמן תהליך הקנייה, שהם כבר שם – לא מפריעים להם!\". המתכנת בכל זאת הלך ו\"דחף\" ל production את הפי\'צר – שהראה מיידית (בעזרת A/B testing) עליה של 3% ברווחים. ה VP התעצבן על המהלך – אך לא יכל ללכת נגד צמיחה של 3% ברווחים בהשקעה כ\"כ קטנה[א].

מלבד כך ש AB Testing היא לא פרקטיקה של סקראם (אלא Lean Startup) – הרעיון העיקרי הוא שלמתכנתים יש יתרון בהבנת ה feasibility של פיצ\'רים – והם יכולים לתרום הרבה. \"השתקה\" שלהם, מפאת קולו היחיד של ה PO – נראית כמו טעות היסטורית גדולה.
מרטין המליץ שבמוצר יהיה Conversational Stories – שנוצרים במשותף ע\"י POs ולא-POs. בכל זאת, עדיין ההמלצה היא לתת משקל רב יותר ל POs (למשל: לקבוע priorities, בהנחה שזה לא כלי לייבש כל מה שלא הגיע מה PO).
עוד אלמנט היה האחריות של המפתחים לדאוג למשתמשי ולקוחות המערכת (ולא רק לרזומה שלהם עצמם – באימוץ טכנולוגיות חדשות). למשל: בהגנה על פרטיות ואבטחה על המשתמשים – אפילו אם ה PO לא דוחף לשם ביזמתו.
Dark Patterns
Dark Patterns הם דפוסים של מערכת, שנוהגת בהטעיה / חוסר צדק כלפי המשתמש. לדוגמה: חנות אונליין שמוסיפה בעת הרכישה של מוצר מסוים, ביטוח או אחריות בתשלום – שהלקוח בכלל לא ביקש. הניסיון הוא שהלקוח לא ישים לב וירכוש בנוסף מוצר שלא התכוון לרכוש – מה שאנו מכירים בלבנט היטב כ \"שיטת מצליח\" (קרה לי פעם – זה באמת מעצבן!).
בשלב זה מרטין נכנס למצב תוכחה וממש גער במפתחים \"אם המנהל שלכם אמר לכם להכניס כזה פיצ\'ר למוצר – זה לא פוטר אתכם!\".
\"…אל תגידו \'רק מילאתי פקודה\'. אתם שותפים באחריות!\".
לא היה ברור לי אם מרטין הבין את משמעות המשפט הזה לקהל הגרמני, ועוד בכמעט-צעקה. אני בלעתי את הרוק כשהוא קרא את הקריאה הזו.
בסופו של דבר הוא דווקא יצא מזה יפה: הוא לא קרא לאנשים להתפטר (מה שלא סביר לדרוש) – הוא אמר להם כך: \"עשיתם משהו רע, וזה באחריותכם. עכשיו יהיה עליכם למצוא משהו טוב לעשות – על מנת לאזן את זה\". הנה דרישה שאנשים יכולים לחיות איתה.
קריאה לפעולה
עוד 2 עניינים שעלו, כקריאה לפעולה מהמפתחים הם:
  1. להשפיע ולתרום למאבק בפרטיות ברשת האינטרנט. הייתה לו הרצאה נוספת שלמה בנושא, בניסיון להסביר ש: \"לא עשיתי כלום רע\" היא לא סיבה שלא יעקבו אחריכם. ברגע שעקבו כבר אחריכם – יש מידע שניתן לנצל כנגדכם. לדוגמה: היה לי משבר בחיים שאני לא מתבייש בו, אבל פרסום מגמתי שלו בשלב מאוחר יותר בחיים – יכול לפגוע בי.
  2. להימנע מ Alienating Atmosphere (אווירה גיקית עמוקה) בתעשיית התוכנה, שמרחיקה \"אנשים חברתיים\" ונשים מהתחום.
    למה לדאוג? כי באופן הזה התעשייה מאבדת הרבה כישרון. הנה מאמר שהוא הזכיר – כיצד אחוז הנשים בהייטק שבארה\"ב פחת משמעותית לאורך השנים, אולי בעקבות \"מתקפת הגיקים\" על התעשייה.
בסוף המצגת עלה למרטין \"לפתע\" רעיון לשם למצגת, ואז השם הופיע בשקף האחרון: \"Professionals, not just code monkeys\".

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

    סדנה: Continuous Delivery

    עוד לפני הכנס עצמו, היה יום שלם של סדנאות לימוד בקבוצות קטנות. מאוד התעניינתי ב System Thinking של מייקל נייגארד, אבל בחרתי בסוף בפרקטי – והלכתי לסדנאת Continuous Delivery של Jez Humble.

    ג\'ז (קיצור של Jessie שזה קיצור של Jayson, אני מניח) הוא הבחור שכתב את הספר המפורסם \"Continuous Delivery\" וגם העביר בהמשך הכנס הרצאה בהשראת הספר החדש שלו: \"The Lean Enterprise\" (שנראה לי שבעקבותיה ארכוש אותו).

    ג\'ז היה מגניב למדי. שמח, משעשע, משוחרר (קרא \"Shitty\" לכל דבר שחשב שהוא לא מוצלח), וסיפק תובנות טובות.
    אני לא הולך לכתוב פה פוסט שלם על Continuous Delivery, אלא רק להזכיר את התובנות העיקריות שעלו:

    דבר ראשון הוא ההבחנה שההבדל בין Continuous Delivery ל Continuous Deployment הוא לא בשלבים שונים של אותו התהליך, אלא ביכולת לספק כל אחד בהקשר הנתון. ג\'ז קיטלג מערכות תוכנה לשלוש קטגוריות:

    • מערכות ווב – שם אין טעם לעשות Continuous Delivery, אם ניתן לעשות Continuous Deployment.
    • מערכות Packaged (למשל SQL Server), ומערכות Embedded – שם לא ניתן לעשות Continuous Deployment, אך עדיין יש ערך רב ב feedback התמידי, ולכן עושים Continuous Delivery.

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

    הוא עבר על use-case כיצד HP העבירו את תהליך פיתוח ה Firmware שלהם למדפסות שלהם – ל CD. רוב העיסוק היה באופן בו נבנה והתפתח ה deployment pipeline שלהם.

    המעבר \"עלה\" בשנתיים של כמעט-השבתה של התהליכים השוטפים, אבל ההישגים – היו משמעותיים. כמות פיתוח הקוד החדש (Innovation) – עלתה פי 8!
    בצד ימין, סה\"כ התשומות לא מתחברת ל 100%. 23% נמצאים בתחזוקת האוטומציה (בדיקות בעיקר) – מה שעשוי להראות ב waste – עד הרגע בו משווים כמה זמן נותן סה\"כ ל innovation – שזהו בעצם יצירת הערך היחידה בכל התהליך.

    ה Use-Case הזה מתואר במפורט בספר בשם Large-Scale Agile Development.

    מקור: Jez Humble

    עוד תובנות ספורדיות:

    • חשוב מאוד להחזיק את ה App Configuration במערכת ניהול הקוד – על מנת שיהיו גרסאות מדויקות.
    • מייבן הוא כלי מחורבן לביצוע CD. ה build life-cycle שלו הוגדר בעולם פרה-CD-י. Gradle הוא לא יותר טוב.  הוא ציין לטובה רק את Rake (של רובי).
      ה mitigation העיקרי למייבן הוא להפסיק להשתמש ב snapshots – כדי שיהיה אפשר לשחזר במדויק build שכשל – ולאבחן מה הייתה התקלה.
    • התייחס לפוסט Google Platforms Rant לגבי מדיניות micro-services עבור CD והאופן לנהל גרסאות של APIs.
    • בדיון על גרסאות קוד, בחור גרמני עצר את הדיון וקרא: \"אבל versioning על REST API נחשב כ bad practice…\".
      ג\'ז לא חיכה: \"אתה צריך להחליט: אתה רוצה CD או לא? אם כן – אל תתן לאיזה כלל של REST להפריע לך.\"
      \"אתה יודע מה? אני אדבר מחר עם ג\'ים וובר (מחבר של ספר מפורסם בנושא) – אני בטוח שהוא יהיה בסדר עם זה… כן לג\'ים לא תהיה בעיה…\".
      הוא אמר את זה בצורה מצחיקה למדי, ונראה לי שהצליח גם לא לפגוע בבחור שהעלה את זה.
      אהבתי את הגישה: באמת לפעמים עקרונות לא לגמרי מסתדרים זה עם זה – וחשוב להבחין מה חשוב לנו יותר.
    • עברנו על מספר פתרונות לתצורות CD, ובעיקר:
    • אי אפשר להוכיח Releasability, רק להפריך אותה (בעזרת בדיקות ואוטומציה).
    • כאשר build נשבר (ברמת ה CI) – יש לחזור מהר מאוד (דקות) למצב תקין ע\"י revert של השינויים האחרונים. את הניטור עושים offline. באופן דומה מאוד – עושים זאת ב production.
    • הוא לא מתלהב מגיט עבור CD / CI – וממליץ כ mitigation לעבוד על trunk (או master) בלבד.
      בגוגל, 10,000 מהנדסים עובדים רק על trunk גלובלי שכולל כמעט את כל הקוד של גוגל (מלבד אנדרואיד ו chromeOS, מסיבות היסטוריות).
    • מדד שהוא ממליץ לעשות לו אופטימיזציה הוא Time To Restore Service.
    • עקרון: deploy the same way to every environment.
      • היה לו סיפור על נטפליקס שבדקו קוד על MRI (ה VM הרגיל של רובי) – כי קל לדבג, אבל ב production עבדו עם JRuby (שהוא מהיר בהרבה, ואמור להיות זהה). כצפוי: באג בלתי-צפוי ב JRuby גרם להם להשבתת פעילות מאוד לא נעימה.
    • עקרון משנה: only build packages once. מצד אחד שיקול ביצועים (לא לעשות אותה העבודה פעמיים), מצד שני – ייתכן שבנייה מאוחרת יותר (בעיקר בסביבת CD) תיצור image מעט שונה.
    • עיקר האתגר ב CD הוא לא הכלים ו/או הטכניקות, אלא בעיקר ליצור DevOps Culture. בחלק זה היה לו סיפור מדליק על נומי (NUMMI) – שיתוף הפעולה בין טויוטה וGM, שיצר הצלחה מדהימה – ש GM לא הצליחו לשחזר. אלו סיפורי שחר ההיסטוריה של Lean שהם תמיד מעניינים…
      • הוא המליץ על הספר Toyota Kata 
      • בלי, ועם קשר, המליץ על הספר The Phoenix Project כתחליף מודרני ומכוון IT לספר \"המטרה\" (ספר שנכתב ע\"י בחור ישראלי, והיה דיי מהפכן בכל העניין של ניהול תהליכים ע\"פ אילוצים)
      • המלצה נוספת – The Corporate Culture Survival Guide. אין מה לומר, הבחור אוהב ספרים.
    • לראשונה שמעתי מישהו קורא ל Router (רכיב רשת) – \"רוטר\". דיי הגיוני, אני יודע שבארה\"ב קוראים ל Route (למשל Route 66) לסירוגין כ ראוט 66, או רוט 66.
      חלק גדול מהסדנה (כמעט חצי) – עסק בביצוע בדיקות. החלק הזה היה קצת יותר משעמם עבורי – כי זה נושא שכבר טחנתי רבות לאורך הקריירה.
      במקום פירמידה – הוא הציג את סוגי הבדיקות השונים במטריצה.
      • גם הוא התלונן על חוסר הבהירות במינוח של בדיקות פונקציונליות / קבלה / אינטגרציה (נושא שעלה אצלנו לדיון לאחרונה בעבודה).
        • בזמן הכנס, פחות או יותר, מרטין פאוולר פרסם עדכון לפוסט ישן על בדיקות יחידה, שמכיל דיון על Solitary Tests ו Sociable Tests. כמה נוח – אנו מנהלים בדיוק דיון מקביל בעבודה, ואין כמו גורו בינלאומי, בכדי לתמוך ולתת הרגשה שאנו בכיוון הנכון.
      • קריאה מומלצת Google Testing Culture
      • ציין את PageObject כדפוס מוצלח לבדיקות Acceptance.
      • עודד למחוק מהקוד בדיקות שכבר לא תורמות / לא רלוונטיות (בגלל שינויים בדרישות).
      • הבחין בין Acceptance Tests (\"מקטע בודד של פעילות\") ל Journey Tests (המכסים flow גדול של acceptance), ושוחח קצת על הבחירה ביניהן (כמובן שגם וגם – אבל כמה ומתי כל אחד).
      • \"בדיקות לא-יציבות הן יותר גרועות מבדיקות חסרות-תועלת\" (כי אז אנשים מפסיקים לסמוך על בדיקות).
      נושא גדול אחרון היה ההתמודדות עם בסיסי נתונים בסביבת CD:
      • DB Versioning – רעיון שלכל שורה בבסיס הנתונים יש גרסה (בדומה ל multi-tenancy), וכך ניתן להריץ בדיקות של בסיס הנתונים במקביל / לעשות deploy הדרגתי.
      • טכניקה נוספת לבדיקות: לעשות טרנזקציה ו rollback בכל בדיקה.
      • שימוש של In-Memory DB (למשל: H2) לבדיקות בלבד – בכדי להשיג מהירות גבוהה. מחיר בעייתי נדרש: שוני מסביבת ה production.
      • יכולת Fork DB של Heroku – שיכול לעזור מאוד לבדיקות. אולי ניתן לעשות זאת עם backup/restore גם בצורה מקומית.
      • טכניקות שונות ל nZDM (קיצור של near Zero Downtime for Maintenance) בכדי להחליף \"גרסה\" של בסיס נתונים ב production (או לבצע migration ל schema) – תוך כדי צמצום ההשפעה על הלקוח הסופי. הכוונה היא להשאיר את המערכת חיה, אך למשך זמן קצר (דקות) – לא לאפשר למשתמשים לבצע כתיבות לבסיס הנתונים (או לאגור אותם בצד בנוסח CQRS – ולעדכן אח\"כ).
      • noSQL – כדרך לעקוף רבות מהבעיות. מה לעשות: noSQL, micro-services ו CD מתאימים זה לזה – ובמקום הזה בסיסי-נתונים רלציוניים משתלבים פחות טוב.
      מקור: Jez Humble

      סיכום

      טוב, היה כנס מרשים ומחדש – ולא קל לסכם את הכל בפוסט אחד. בשאיפה, ארכז עוד תובנות מעניינות בפוסט המשך.
      בעוד הביקור ב QCON 2008 היה עבורי OVERWHELMING, הפעם הכנס היה עדיין מעניין מאוד – אבל לא הפך לי את כל דפוסי החשיבה. אני מניח שאני פשוט מעודכן הרבה יותר היום במתרחש בחזית התעשייה – ממה שהייתי אז.

      שיהיה בהצלחה,
      ליאור

      ——

      [א] בסשן אחר שאל המציג את הקהל: \"כמה מכם יכולים לדחוף מ production פיצ\'ר לאחר שה VP Marketing אמר שזה רעיון רע?\" (כמו בסיפור של אמזון).
      באופן מפתיע למדי, כעשרים אחוז מהקהל הרים יד. אולי בגלל שהיו הרבה סטארט-אפיסטים (?!) אולי גם האווירה הליברלית של ברלין.