‏הצגת רשומות עם תוויות micro-services. הצג את כל הרשומות
‏הצגת רשומות עם תוויות micro-services. הצג את כל הרשומות

2017-04-03

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

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

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

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

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


לא גלידה, לא פירמידה - אלא יהלום


המודל הקלאסי של בדיקות אוטומציה הוא מודל "פירמידת הבדיקות":


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


המודל המועדף עלי הוא דווקא מודל היהלום:


לאחר שנים שהייתי חסיד של מודל הפירמידה, וריבוי בכתיבות Unit Tests - השתכנעתי שמודל היהלום הוא יעיל יותר.
ההבחנה של ההבדלים בין בדיקות Integration, Component, ו E2E - היא משנית. העיקר הוא:
  • יש לנו מעט בדיקות ידניות: על מנת לא לשחוק, לא לעשות עבודה רוטינית שוב-ושוב-ושוב - אלא להשתמש במוח האנושי שאין לו תחליף, לזהות דברים לא טובים במערכת. (ויש אנשים שעושים זאת טוב יותר מאחרים).
  • יש לנו כמות בינונית של בדיקות יחידה: 
    • בדיקות יחידה הן מעולות (!!) לבדיקת Pure Business Logic, קרי parsers, business rules, אלגוריתמים וכו' - כל מה שיש לו מחזור: קלט - הרבה עבודה לוגית - פלט. ניתן לזהות ביתר קלות אזורי קוד כאלה בעזרת צפיפות של משפטי if ו for (בשפות המתאימות).
    • בדיקות יחידה הן פחות יעילות לקוד אינטגרציה ("עשה א, ואז עשה ב, ואז עשה ג" - כמו שליפת נתונים מבסיס נתונים).
    • בדיקות יחידה הן דיי לא יעילות ל UI.
    • בקיצור: נשתמש בהן ב sweet spot שלהן בלבד: Pure business logic.
  • הדגש של המודל הוא על בדיקות Component (לעתים נקראות אינטגרציה, או API) - הבודקות התנהגות של כל שירות בפני עצמו. בבדיקה פשוטה אפשר לכסות הרבה מאוד קוד, בסביבה יחסית מציאותית, מה שמייצר מעין  sweet spot של עלות-תועלת: בין כמות ההשקעה בבדיקה - והערך שהיא מחזירה.
העקרונות של הפירמידה עדיין נשמרים ביהלום:
  • כמה שעולים למעלה הבדיקות הן: איטיות יותר להרצה, דורשות יותר תחזוקה --> יקרות יותר, ולכן ממעיטים בהן.
  • כמה שיורדים למטה הבדיקות הן ספציפיות יותר, רצות מהר יותר, ותלויות פחות בסביבה / אמינות יותר.


מה עם סוגי הבדיקות השונים? 


ישנה חוסר סטנדרטיזציה ברורה ובעייתית בשמות בהן משתמשים לתיאור בדיקות שונות: מהו API Test ומהו Integration Test? - רבים לא יסכימו על ההגדרה. אני אצמד למונחים שמקובלים ב Gett, לא כי הם "בהכרח הטובים ביותר", אלא כי אני רגיל אליהם כרגע.

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

לצורך הדיון, כך נראה שירות:


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

הנה כמה דוגמאות לבדיקות יחידה:



  • המחלקה A היא מחלקה "חברה" של מחלקה X - הבודקת אותה. זוהי בדיקת pure unit tests, האידאל של בדיקות יחידה.
  • המחלקה B היא מחלקה "חברה" של מחלקה Y - הבודקת אותה. מכיוון ש Y תלויה ב X, ואנו רוצים בדיקה "טהורה", קרי: נקודתית ומבודדת - אנו יוצרים Mock של X וכך מריצים את הבדיקה B על המחלקה Y בלבד. 
  • מחלקה C בודקת את המחלקה Z, אבל גם את החברה האחרת שלה - מחלקה X. לכן היא נקראת sociable unit tests. היא חברותית. 
  • כמה שבסיס הקוד שנבדק ע"י בדיקת יחידה הוא גדול יותר (יותר branching של ה flow - בעיקר), בדיקת היחידה היא יעילה פחות: יהיה קשה יותר לבדוק מקרי קצה, וכשלון של בדיקות יצביע בצורה פחות מדוייקת על מקור התקלה.
  • ככלל, אנו מעדיפים בדיקות pure unit tests על פני ב sociable unit tests - אבל זו הבחנה שלא תמיד מדייקת. למשל: אם עשינו refactoring למחלקה גדולה Z, והוצאנו ממנה קוד למחלקה חדשה X - אזי הבדיקה C הפכה ל sociable unit tests. למרות זאת, היא טובה בדיוק באותה המידה כפי שהייתה לפני ה Refactoring.
  • הערה: בתרשים נראה שאני בודק את כל המחלקות שיש לי, בפועל כנראה שאבדוק בעזרת בדיקות יחידה רק 10-30% מהמחלקות ב Service (תלוי כמובן בשירות)


המאסה העיקרית של האוטומציה מתבצעת ע"י בדיקות Component - הבודקות רכיב בודד במערכת.



  • במקרה של MSA, הרכיב הוא שירות, עם ה Database שלו - אך ללא תלויות בשירותים חיצוניים.
  • הבדיקות מתבצעות רק דרך ה APIs של השירות, אם כי פעמים רבות מייצרים נתונים ב Database לפני הבדיקה ישירות דרך ה Models של השירות עצמו (כך שקל לייצר נתונים עקביים, בפורמט המעודכן ביותר).
  • המטאפורה של השם Component היא כמו של רכיב אלקטרוני. המערכת עשויה להיות מחשב ויש לה ערך עסקי רק כשהיא שלמה, אבל כל רכיב (Component) נבדק ביסודיות בפני עצמו: זיכרון, דיסק, מעבד, וכו'.
    יצרני הזיכרון, למשל, בודקים את הרכיב שלהם בקנאות - בודקים שהוא עובד, גם בכל מצבי הקצה, כפי שמצופה ממנו. כאשר הממשקים ("contracts") ברורים, וכל רכיב בדוק כראוי - ניתן כבר להשיג רמת אמינות מרשימה של המערכת.

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

במונחים שלנו מדובר על End-To-End Tests, ובקיצור E2E Tests (מה שלעתים קרוי גם System Test):


  • אמנם אין בתרשים הרבה שירותים - אנא הניחו שמדובר במערכת מלאה (עשרות Services ויותר), עם תצורה קרובה ככל האפשר ל Production, עם האפליקציה / UI, ושירותים חיצונים בהם משתמשים (למשל: שירות לשליחת הודעות SMS).
  • המטרה: לפני שמשחררים שינוי משמעותי לפרודקשיין, נרצה להשקיע בבדיקה ולאתר תקלות לפני ההגעה לפרודקשיין, בסביבה אמיתית ומלאה ככל האפשר.

ואכן בדיקות E2E הן יקרות, וכדאי למעט בהן.
כאן מגיע התפקיד של בדיקות אינטגרציה: לאזן קצת בין עלות לתועלת:


  • בבדיקות אינטגרציה בודקים כמה שירותים כשהם עובדים ביחד. בדרך כלל זו קבוצה קבועה של שירותים הנמצאים בקשר עמוק זה עם זה, מה שניתן גם לכנות גם: "sub-system".
  • לפני הרצת בדיקת E2E - מריצים את בדיקת האינטגרציה המתאימה לשירות / תסריט (אם יש כזו). הקמת הסביבה היא מהירה וזולה יותר (נניח 2 עד 7 שירותים - במקום עשרות רבות של שירותים). הבדיקות רצות מהר יותר והן ממוקדות יותר (כלומר: סבירות גבוהה יותר לאתר תקלות).
  • עבור שינוי קטן או בינוני באחד השירותים, ניתן להסתפק בהרצה של ה sub-system הרלוונטי. עניין של בחירה וניהול סיכונים. 
  • ב Sub-System שבתרשים - שירות A הוא "המוביל" ותמיד דרכו ייעשו הבדיקות, גם כאשר השינוי שנבדק הוא בשירות אחר.
    • לא נשלח באמת SMSים, אלא נבצע Mock לשירות החיצוני.
    • ייתכן ואחד השירותים שלנו ב Sub-system הוא מורכב מדי להפעלה / קונפיגורציה - ולכן גם אותו נחליף ב Mock. כדאי להימנע מגישה זו במידת האפשר.

סיכום


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


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


-----


קישורים רלוונטיים

First Class Tests - מאמר דעה של הדוד בוב בנושא, הוא הרי חסיד של בדיקות יחידה קפדניות ו TDD.



2016-04-02

מצגת על מיקרו-שירותים ב DevConTLV בפורים

בפורים, ממש לפני כמה שבועות הופעתי בכנס DevConTLV שהיה בסינמטק.

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


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

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

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

מקווה שתמצאו את החומר מעניין ושימושי!

ליאור


2015-10-31

הכנס השני לארכיטקטורת תוכנה (30 לנובמבר - 1 בדצמבר)

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

הכנס מאורגן ע"י IASA ואילתם.

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

ILTAM, היא קהילה מקצועית, רחבה יותר, המורכבת בעיקר מחברות הייטק גדולות ומבוססות (לא רק חברות תוכנה נטו: אלתא, 3M, וצה"ל למשל - הם חברים) במטרה לקדם ולשתף ידע.


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


היום ראשון מורכב מהרצאות. הנה התכנית:



ביום שני מתקיים Tutorial בהדרכתה של Rebecca Wirfs-Brock (שהגיעה לארץ במיוחד, אני מניח), בנושאי ארכיטקטורה באג'ייל ו Quality Attributes בפרט (הנה פוסט שפירסמתי בנושא, אם אתם רוצים לקבל מושג במה מדובר).


האם כדאי לבוא?


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

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


  • אני נותן הרצאה על מיקרו שירותים (עדיין לא החלטתי בדיוק איך להעביר את הנושא...). אם אתם מגיעים - קפצו לומר שלום!
  • קצת אחרי ייתן הרצאה יונתן ממן, שכתב כאן פוסט אורח בנושא - ממש לאחרונה.
  • הנה אתר הכנס: http://conference70.wix.com/sw-architecture
  • בטופס ההרשמה שבאתר, ניתן לקבל הנחה אם מזינים את המילה "Presenter", בסעיף של קבוצת שיוך (אופס, אני מקווה שבאמת היה מותר לי לספר את זה...).


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

ליאור

 


2015-10-17

כל מה שרצית לדעת על ארכיטקטורת מיקרו-אפליקציות (Micro-Apps Architecture) - ולא העזת לשאול

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

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

Fox News, ה Rolling Stone, "הארץ", "בלוג ארכיטקטורת תוכנה" (בדיחה, בדיחה), ועוד. אאוטבריין מייצרת כ-+150 מיליארד המלצות תוכן בחודש - כמות Traffic יוצאת דופן! בזירת ההייטק הישראלית היא נחשבת מובילת-דרך בכל הנוגע ל Scale, Operations, וכו'. החברה גם פעילה למדי בקידום אירועים ושיתוף ידע בתעשייה (IL TechTalks, רברסים וכו').



---

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


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


ה tutorials שתמצאו, כנראה ידברו על שירותי רשת (web services) - ללא UI ובעיקר ללא ניהול session. אבל מה עם אפליקציות רשת (web applications), שם ה UI, וניהול ה session של המשתמש הם החלקים המרכזים ? האם גם אותם ניתן לפצל ל ״מיקרו-אפליקציות״ ? נניח שלקחת אפליקציה אחת ובדרך קסם פיצלת אותה לשניים, איך תתמודד עם:
  • שמירה על חווית כניסה (login) אחת? 
  • מעבר חלק, ושיתוף session בין האפליקציות? 
  • מניעת שכפול קוד ?



בוא ניקח לדוגמה אפליקציית רשת נפוצה: אתר של חברת סטארטאפ.

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

האתר לרוב יהיה מהמערכות הוותיקות בחברה - ולכן גם עם חוב טכנולוגי גבוה / Technology Stack ישן.
לכאורה מועמד מצוין לשדרוג ולמעבר לארכיטקטורת מיקרו services, אבל אליה וקוץ בה: דווקא אפליקציית רשת קשה מאוד לפצל ויש מעט מאוד מידע ברשת כיצד לעשות זאת.

פוסט זה יתאר ארכיטקטורת מיקרו-אפליקציות (Micro-Apps Architecture) שמטרתה (בדומה ל Micro-Services Architecture) לסייע בפיצול אפליקציית רשת למיקרו אפליקציות ובכך ליהנות מהיתרונות המדוברים.



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

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




כל אזור (אדום, צהוב או כתום) הוא מועמד להיות מיקרו אפליקציה עצמאית.

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


המיקרו אפליקציה בנויה מטכנולוגיה המתאימה לה ולצוות שלה ומותקנת על שרת ייעודי. האפליקציה יכולה להגיש גם את צד הלקוח (client side) וגם את צד השרת (server side). האפליקציה מותקנת בתוך ה firewall ולא מקבלת תעבורת רשת באופן ישיר.


micro-colored.png


נייצר service מארח (hosting service)  דרכו עוברת כל התעבורה למיקרו אפליקציות. ברוב המקרים ניתן להשתמש באפליקציה המקורית (המונוליטית) כ service מארח, כיון שאחרי שהוצאנו מתוכה את רוב הפונקציונליות כנראה שנותר שם רק התשתית של ניהול הsession, אימות המשתמש, אבטחה וכו'.
ה service המארח משמש כמעין נתב. ״נתב על סטרואידים״, אם תרצו.


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

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



מה עושה הפרוקסי?
  • הפרוקסי הוא יחידה לוגית בתוך ה service המארח. תפקידו להוות צינור ולהעביר בקשות מהלקוח למיקרו אפליקציה עליה הוא אחראי. התקשורת בין הפרוקסי למיקרו אפליקציה באמצעות פרוטוקול HTTP. לכל מיקרו אפליקציה קיים פרוקסי משרת אותה. 
  • כל פרוקסי נרשם על מסלול אחר של ה URL. לדוגמה כל בקשה למשאב תחת הכתובת www.yoursite.com/app1 תופנה לפרוקסי האדום. 
  • כשהבקשה מגיעה לפרוקסי כבר ידוע מיהו המשתמש, ולכן הוא יכול להעביר את שם המשתמש (או כל מידע אחר עליו) למיקרו אפליקציה באמצעות HTTP Header. 
  • הפרוקסי מנהל את כמות החיבורים הפתוחים לאפליקציה שלו, ומדווח על כמות החיבורים הפתוחים למערכת הניטור. 
  • הפרוקסי מקבל בחזרה את התשובה מהמיקרו אפליקציה ומעביר אותה חזרה למשתמש.



יתרונות - או מה יצא לנו מכל זה?
  • הפרדה ברורה בין אפליקציות: זליגת זכרון במיקרו אפליקציה אחת לא תביא לנפילה של מיקרו אפליקציה אחרת. 
  • ה service המארח הופך מהר מאוד לתשתית שבה אין הרבה פיצ'רים חדשים. הוא הופך לפשוט וקל יותר לתחזוקה. 
  • כל מיקרו service נכתב בטכנולוגיה שמתאימה לדרישותיו ולצוות שמפתח אותו. 
  • ניתן לעשות את המעבר מאפליקציית רשת מונוליטית למיקרו אפליקציות בשלבי, ובכך להקטין את הסיכון. לדוגמה: 
    • לייצר מיקרו אפליקציה שתגיש רק את קוד הלקוח שנכתב בטכנולוגיה חדשה, נניח ב AngualrJS, ולהשאיר את קוד השרת ב service המארח. 
    • לייצר מיקרו אפליקציה חדש שיגיש קוד שרת בלבד. ניתן להחליט שלא מעבירים קוד ישן אלא רק קוד חדש של פיצ'רים חדשים. וכך לבצע החלפה אטית אך שיטתית ומדורגת מתשתית שרת ישנה לתשתית שרת מודרנית יותר. 
  • ניטור -- כל פרוקסי מדווח על כמות הכישלונות, הצלחות, מספר החיבורים התפוסים וכו'. 
  • רישום בקבצים - ה service המארח אחראי על כתיבת קבצי הגישה access log עבור כל המיקרו אפליקציות. 
  • אבטחה -- ה service המארח מבצע את כל מנגנוני האימות של כניסת משתמשים ו CAPTCHA. 
  • אפליקציות חדשות -- נניח שאתה רוצה לפתח אפליקציית-רשת חדשה. תוכל לממש אותה כמיקרו אפליקציה בתוך ה service המארח. כך תחסוך בפיתוח מנגנוני אבטחה, רישום וכניסה, כתיבת קבצי גישה וכו', כל מה שתדרש זה להוסיף עוד חוק ניתוב service המארח. בנוסף לא תצטרך את עזרת אנשי ה OPS. גישה זו היא מיושרת עם רעיון ה DEVOPS.



כמובן שאין ארוחות חינם. אז מה אנחנו מפסידים?
  • עוד תחנה במסלול -- טיפול בבקשה של משתמש לוקחת זמן ארוך יותר. 
  • שיתוף קוד לקוח -- כבר לא תוכל לשנות בקלות את ה JS/CSS שמשותפים עבור כל המיקרו אפליקציות. בכדי לשתף קוד לקוח (למשל ניווט בתוך האפליקציה), תצטרך להתייחס אל הקוד כאל ספרית צד שלישי. תוכל להשתמש ב bower בכדי לנהל את הגרסאות ולהעלות את הקוד ל repository. דרך נוספת תהיה להעלות קבצים משותפים ל CDN. 
  • הגדל הקושי בתחזוקה ובמציאת תקלות - כל תוספת של חלק נע במערכת מקשה על מציאת באגים. עוד קבצי לוג שצריך לבדוק כמשנסים לאתר תקלה. נניח שמשתמש מנסה לגשת למיקרו אפליקציה ומקבל HTTP CODE 403. יהיה עליכם לבדוק האם ה service המארח חסם את הגישה, או אולי האבטחה של המיקרו אפליקציה חסמה את הבקשה. 
  • הפרוקסים הם פשוטים ולא משוכללים כמו HAProxy






הניסיון שלנו באאוטברין:
  • פתחנו את הארכיטקטורה הזו בתחילת 2015, כאשר הבנו שאנחנו הולכים לחשוף הרבה יכולות של המוצר שלנו למשתמשי הקצה באמצעות שירות עצמי דרך אפליקציית רשת. אפליקציית הרשת שלנו הייתה מבוססת על תשתיות ישנות והבנו שמבחינה טכנולוגית אנחנו צריכים לבצע שדרג משמעותי. 
  • המטרה הייתה לפתח יישומים חדשים בטכנולוגיות מודרניות באופן בלתי תלוי ע"י צוותים שונים עם סט בדיקות שונה תוך כדי שיתוף session בין האפליקציות, כל זאת בלי לכתוב מחדש את כל התשתית של אפליקציית הרשת הקיימת. 
  • התחלנו עם מיקרו אפליקציה אחת, ועכשיו יש לנו 4 מיקרו אפליקציות שמגישות גם צד שרת וגם צד לקוח. הנה דוגמה ל Stack הטכנולוגי של 3 מארבעת מיקרו אפליקציות בתשתית של אאוטברין:


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



2015-09-06

Fault-Tolerance בארכיטקטורת Microservices, ובכלל

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

למה בלתי צפויה?

ובכן, אתחיל במעט רקע: לאחרונה פירקנו מתוך המערכת הראשית שלנו ("המונוליט") כ 7 מיקרו-שירותים חדשים, חלקם קריטיים לפעולה תקינה של המערכת. כל שירות הותקן בסביבה של High Availability עם שניים או שלושה שרתים ב Availability Zones שונים באמזון. חיברנו Monitoring לשירותים - פעולות סטנדרטיות. בהמשך תכננו לפתח את היציבות אפילו יותר, ולהוסיף Circuit Breakers (דפוס עיצוב עליו כתבתי בפוסט קודם) - בכדי להתמודד בצורה יפה יותר עם כשלים חלקיים במערכת.

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

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

- "נראה כמו" ?

כן. אנחנו עדיין בודקים את זה עם אמזון. לא הצלחנו לשים אצבע בדיוק על מקור התופעה. אחת לכמה זמן, שירותים מסוימים במערכת חווים תנאי-רשת דיי קיצוניים:
  • Latency בתוך ה Data Center (בין AZs) שקופץ מ 2 מילי-שניות בממוצע לכ 80 מילי-שניות בממוצע. 80 מילי-שניות הוא הממוצע, אבל גם לא נדיר להיתקל ב latency של 500 מילי-שניות - כאילו השרת נמצא ביבשת אפריקה (ולא ממש ב Data Center צמוד, עם קווי תקשורת dedicated).
  • גלים של אי-יציבות, בהם אנו חווים אחוז גבוה מאוד של timeouts (קריאות שלא נענות בזמן סביר), במקרים הקיצוניים: יותר מ 1% מניסיונות ה tcp connections שאנו מבצעים - נכשלים (בצורת timeout - לאחר זמן מה).
אנחנו רגילים לתקשורת-לא מושלמת בסביבה של אמזון - אבל זה הרבה יותר ממה שהיינו רגילים אליו עד עתה. בניגוד לעבר - אנו מתנסים לראשונה בכמות גדולה של קריאות סינכרוניות שהן גם קריטיות למערכת (כמה אלפי קריאות בדקה - סה"כ).


זה נקרא "ענן" - אבל ההתנהגות שאנו חווינו דומה הרבה יותר ללב-ים: לעתים שקט ונוח - אבל כשיש סערה, הטלטלה היא גדולה.
מקור: http://wallpoper.com/


סימני הסערה


החוויה החריגה הראשונה שנתקלנו בה - היא starvation: בקשות בשירותים השונים הממתינות בתור לקבל CPU (ליתר דיוק: להיות מתוזמנות ל process של ה Application Server - אולי אספר עוד בפוסט נפרד), וממתינות זמן רב - שניות. אותן קריאות שבד"כ מטופלות בעשרות מילי-שניות.

כאשר מוסיפים nodes ל cluster, למשל 50%, 100% או אפילו 200% יותר חומרה - המצב לא משתפר: זמני ההמתנה הארוכים (שניות ארוכות) נותרים, ויש אחוז גבוה של כישלונות.

בדיקה מעמיקה יותר גילתה את הסיבה ל starvation: שירות A מבקש משהו משירות B, אך כ 3% מהקריאות לשירות B לא נענות תוך 10 שניות (להזכיר: זמן תגובה ממוצע הוא עשרות בודדות של מילי-שניות).
תוך זמן קצר, כמעט כל התהליכים עסוקים ב"המתנות ארוכות" לשירות B - והם אינם פנויים לטיפול בעוד בקשות.
כאשר כל טרנזקציה בשירות B מבצעת כ 3 קריאות לשירות A ("מה זה משנה? - הן כ"כ מהירות"), הסבירות ל"תקיעת" הטרנזקציה על timeout עולה מ 1% לכ 3% - כאשר יש כ 1% התנתקויות של connections.

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

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


הפעולה המידית הייתה:
  • הפחתת ה timeouts ל-2 שניות, במקום 10 שניות. הקשר: השירות הכי אטי שלנו עונה ב 95% מהקריאות, גם בשעות עומס - בפחות מ 500 מילי-שניות.
  • צמצום מספר הקריאות בין השירותים: בעזרת caches קצרים מאוד (כ 15 שניות) או בעזרת קריאות bulk. למשל: היה לנו שירות D שבכל טרנזקציה ביצע 12 קריאות לשירות E, למה? - חוסר תשומת לב. שינוי הקוד לקריאת bulk לא הפך את הקוד למסורבל.
    בעצם ריבוי הקריאות, השירות בעצם הכפיל את הסיכוי שלו בפי-12, להיתקע על timeout כלשהו. קריאה אחת שמטפלת ב 12 הבקשות היא יעילה יותר באופן כללי, אך גם מצמצת את סבירות ההתקלויות ב timeouts.
    בכל מקרה: קריאה שלא נענתה בזמן סביר - היא כבר לא רלוונטית.


כששטים במים עמוקים - חשוב לבנות את כלי-השיט כך שיהיה יציב.
מקור: http://www.tutorvista.com/


המשך הפתרון


הסיפור הוא עוד ארוך ומעניין, אך אתמקד בעיקרי הדברים:

  • באופן פרדוקסלי, השימוש ב timeouts (כישלון מהיר) - מעלה את רמת היציבות של המערכת.
  • השימוש ב timeouts + צמצום מספר הקריאות המרוחקות, כמו שהיה במקרה שלנו, בעצם מקריב כ 1% מהבקשות (קיצרנו את ההמתנה ל-2 שניות, אך לא סיפקנו תשובה מלבד הודעת שגיאה) - על מנת להציל את 99% הבקשות האחרות, בזמני סערה (אין ל timeout השפעה כאשר הכל מתנהג כרגיל).

הקרבה של כ 1% מהבקשות הוא עדיין קשה מנשוא, ולכן אפרט את המנגנון שהתאמנו לבעיה.
זו דוגמה נפלאה כיצד דפוס העיצוב המקובל (למשל: Circuit Breaker) הוא כמעט חסר חשיבות - למקרה ספציפי שלנו (הכישלונות הם לא של השרת המרוחק - אלא בדרך הגישה אליו). אם היינו מחברים circuit breakers בכל נקודה במערכת - לא היינו פותרים את הבעיה, על אף השימוש ב "דפוס עיצוב מקובל ל Fault-Tolerance".


המנגנון שהרכבנו, בנוי מכמה שכבות:
  1. Timeouts - על מנת להגן על המערכת בפני starvation (ומשם: cascading failures).
  2. Retries - ביצוע ניסיון תקשורת נוסף לשירות מרוחק, במידה וה connection התנתק.
  3. Fallback (פונקציה נקודתית לכל endpoint מרוחק) - המספקת התנהגות ברירת מחדל במידה ולא הצלחנו, גם ב retry - לקבל תשובה מהשירות המרוחק.
  4. Logging and Monitoring - שיעזרו לנו לעשות fine-tune לכל הפרמטרים של הפתרון. ה fine-tuning מוכיח את עצמו כחשוב ביותר.
  5. Circuit Breakers - המנגנון שיעזור לנו להגיע ל Fallbacks מהר יותר, במידה ושרת מרוחק כשל כליל (לא המצב שכרגע מפריע לנו).

אני מבהיר זאת שוב: Timeouts ללא Fallback או Retries זו בעצם הקרבה של ה traffic. היא יותר טובה מכלום, במצבים מסוימים - אך זו איננה התנהגות רצויה כאשר מדובר בקריאות שחשובות למערכת.


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

===================================================
  1. בצע קריאה מרוחקת (עם timeout של עד 2000 מילי-שניות, בד"כ פחות).
    1. אם הקריאה הצליחה - שמור אותה ב fallback cache (הסבר - מיד).
  2. אם היה timeout - ספק תשובה מתוך ה fallback cache (פשרה).
===================================================

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

ע"פ המדידות שלנו, בזמנים טובים רק כ 1 מ 20,000 או 25,000 קריאות תגיע ל fallback - כלומר משתמש אחד מקבל תשובה שהיא פשרה (degraded service).
בזמני סערה, אחוז הקריאות שמגיעת מתוך ה fallback מגיע ל 1 מ 500 עד 1 ל 80 (די גרוע!) - ואז הפשרה היא התנהגות משמעותית.

באחוזים שכאלו - הסיכוי שבקשה לא תהיה ב fallback cache היא סבירה (מאוד תלוי בשירות, אבל 10% הוא לא מספר מופרך). למקרים כאלו יש לנו גם התנהגות fallback סינתטית - שאינה תלויה ב cache.

מצב ברור שבו אין cache - כאשר אנו מעלים מכונה חדשה. ה caches שלנו, כיום, הם per-מכונה - ולא per-cluster ע"מ לא להסתמך על קריאות רשת בזמן סערה. למשל: יש לנו שירות אחד שניסינו cache מבוזר של רדיס. זה עובד מצוין (גם ביצועים, ו hit ratio מוצלח יותר) - עד שפעם אחת זה לא עבד מצוין.... (והמבין יבין).


כלומר, המנגנון בפועל נראה דומה יותר לכך:

===================================================
  1. בצע קריאה מרוחקת (עם timeout של עד 2000 מילי-שניות, בד"כ פחות).
    1. אם הקריאה הצליחה - שמור אותה ב fallback cache (הסבר מייד).
  2. אם היה timeout - ספק תשובה מתוך ה fallback cache (פשרה), או שתספק fallback סינתטי.
===================================================

איך מגדירים fallback סינתטי? זה לא-פשוט, ותלוי מאוד בתסריט הספציפי. כקו-מנחה יש לחשוב על:
  • ערכי ברירת-מחדל טובים.
  • התנהגות שתמנע מפיצ'רים פחות חשובים לעבוד (למשל: לא להציג pop-up פרסומי למשתמש - אם אין מספיק נתונים להציג אותו יפה)
  • להניח שהמצב שאנו לא יודעים לגביו - אכן מתרחש (כן... המונית עדיין בדרך).
  • להניח שהמצב שאנו לא יודעים לגביו - התרחש בצורה הרעה ביותר (למשל: לא הצלחנו לחייב על הנסיעה)
  • אפשרות: הציגו הודעת שגיאה נעימה למשתמש ובקשו ממנו לנסות שוב. המשתמש לרוב מגיב בטווח של כמה שניות - זמן ארוך כ"כ (בזמני-מחשב), שמספר דברים עשויים להשתנות לטובה במצב ה caches / הידע שלנו בפרק זמן שכזה.


תמונה מלאה יותר


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

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

עוד דבר שגילינו, הוא שלמרות שהצלחות בפתיחת connection מתרחשות ב 99.85% בפחות מ-3 מילי-שניות, הניסיון לעשות retry לפתיחת connection מחדש לאחר כ 5 מילי-שניות - כמעט ולא שיפר דבר. לעומת-זאת, ניסיון retry לפתיחת connection מחדש לאחר כ 30 מילי-שניות עשה פלאים - וברוב הגדול של הפעמים הסתיים ב connection "בריא".

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

עניין חשוב לשים לב אליו הוא שעושים קריאות חוזרות (retry) רק ל APIs שהם Idempotent, כלומר: לא יהיה שום נזק אם נקרא להם פעמיים. (שליפת נתונים - כן, חיוב כרטיס אשראי - לא).


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

מכאן התוצאה היא:

===================================================
  1. בצע קריאה מרוחקת
    1. נסה קודם לקחת מה cache הרגיל
    2. אם אין - בצע קריאה מרוחקת (עם timeout של עד 2000 מילי-שניות לקריאה, בד"כ פחות + timeout של 30ms ליצירת connection).
      1. אם היה timeout + במידת האפשר - בצע קריאה שנייה = retry.
      2. אם הקריאה הצליחה - שמור אותה ב fallback cache.
  2. אם לא הצלחנו לספק תשובה - ספק תשובה מתוך ה fallback cache (פשרה), או שתספק fallback סינטתי.
    1. ספק למשתמש חווית-שימוש הטובה ביותר למצב הנתון. חוויה זו ספציפית לכל מקרה.
===================================================

תהליך זה מתרחש לכל Endpoint משמעותי, כאשר יש לעשות fine-tune ל endpoints השונים.

שימו לב להדרגה שנדרשת ב timeouts: אם שירות A קורא לשירות B שקורא לשירות C - ולכל הקריאות יש timeout של 2000 מילי-שניות, כל timeout בין B ל C --> יגרור בהכרח timeout בין A ו B, אפילו אם ל B היה fallback מוצלח לחוסר התשובה של C.

בגלל שכל קריאה ברשת מוסיפה כ 1-3 מילי-שניות, ה timeouts צריכים ללכת ולהתקצר ככל שאנו מתקדמים בקריאות.
ה retry - מסבך שוב את העניין.

כרגע אנחנו משחקים עם קונפיגורציה ידנית שהיא הדרגתית (cascading), קרי: ה timeout בקריאה C <-- B תמיד יהיה קצר מה timeout בקריאה בין B <-- A.

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

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


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


כך נראתה השמדה-עצמית בשנות השמונים.
מקור: http://tvtropes.org/


סיכום


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

מה שחשוב הוא להבין מה הבעיה - ולפתור אותה. לא לפתור את הבעיות שהיו ל Amazon, נטפליקס או SoundCloud.

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

אם למישהו מכם, יש ניסיון בהתמודדות דומה - אשמח לשוחח על כך. אנא כתבו לי הודעה או שלחו לי מייל (liorb [at] gett.com) ואשמח להחליף תובנות.

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


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


---


לינקים רלוונטיים:

AWS Noiseness




2015-02-15

מיקרו-שירותים: 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 מקל על הבעיה, אך גם יוצר פיתוי מסוים ליצור "גוש מונוליטי של פונקציונליות, מלא תלויות" ממנו כדאי להיזהר...


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


----


מקורות:





2014-12-15

דפוסי ארכיטקטורה: מיקרו-שירותים (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/