-->

יום שישי, 16 ביוני 2017

מבוא ל Domain Driven Design

Domain Driven Design, או בקיצור DDD היא מתודולוגיה שהוצגה בשנת 2003, בספר בשם דומה.





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

מחבר הספר, אריק אוונס (Eric Evans), לא היה דמות מוכרת ו/או מקושרת בעולם התוכנה.
הוא לא עבד בחברה גדולה או בעלת פרופיל תקשורתי מוכר.

בעצם - הוא היה דיי אלמוני.

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

פאדיחה!

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

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


את WikiWikiWeb תחזקו Kent Beck ו Ward Cunningham, וברגע שדמות מוכרת כמו Kent Beck התלהבה מהספר - הדלתות נפתחו, וכמעט כל עולם התוכנה שמע על הספר.

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

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


--- fast forward ---


בערך ב 2014, כשאנשים כמו Martin Fowler, ו James Lewis ניסו להסביר לעולם את רעיונות הארכיטקטורה של MSA - הם השתמשו במונחים כמו Bounded Context וחזרו להזכיר את DDD. רעיון משפיע אחר הוא הרעיון של Event Sourcing (המופיע בספר פשוט כ "Domain Events") - שיש לו קשר חזק לבאזז אחר: CQRS.


אריק חזר להופיע בכנסים חשובים, ואפילו יש כמה כנסים חדשים שמוקדשים ל DDD (למשל: DDD Europe). במשך השנים יצאו כמה ספרים נוספים על DDD (אריק הוציא תקציר "reference" לספר המקורי) וניתן היום למצוא מקורות כמו DDD Weekly - שהחל את דרכו רק בשנה שעברה. כמות הכתבות ב Medium והדיונים ב Hacker News לגבי DDD (ע"פ תחושה אישית ולא מדויקת שלי) - והלכים וגדלים.

DDD חזר למרכז הבמה!


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


הנה מתחילים!


אז הנה זה לפניכם, רשימת ה Patterns שמציג הספר והקשרים ביניהם:


מה... אתם צריכים דקה בכדי לעכל את התרשים? ... שעה?........ יותר?!


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

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


רואים בתרשים למעלה את ארבעת המעגלים הגדולים ביותר? - אלו הם גם הרעיונות החשובים ביותר במתודולוגית ה DDD.
אמנם שלושה מהם מכוסים בספר בשלב מאוחר (פרקים 14 ו 15 !!) - אולם אריק בעצמו מספר שזו הייתה טעות, שהוא לא נתן לרעיונות הללו את הדגש הנכון. שאם היה כותב את הספר מחדש, אלו ככל הנראה היו פרקים 2 ו 3.

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

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

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

הספר מציג Framework למימוש המודל הקונספטואלי כתוכנה, ומשתמש בעקרונות Object-Oriented בשילוב של רעיונות שאינם Object-Oriented אך הוכיחו את עצמם לאורך שנים. מבנים כמו Aggregate ו/או Repository - הם לא "Object Oriented טהור". שווה אולי לציין שספרי מידול קודמים נהגו לחתור למודל טהור של "אובייקטים בזיכרון" ולא התייחסו כ"כ לבעיות הארציות כמו - שמירה לבסיס נתונים רלציוני. זה מלכלך את המודל.
למשל:  1.0 EJB היה אמור להיות Framework שמאפשר למתכנת-הממדל לחשוב על אובייקטים ולא לדאוג לבעיות כמו אכסון ושמירה. זה עבד טוב כל עוד היו פחות מ 10 משתמשים במערכת ;-)

רעיון כמו Event Sourcing (אותה סקרתי בפוסט קודם) - הוא לא ממש Object-Oriented - אך הוא רעיון מוצלח ושימושי.

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


עוד כמה מילים על "המודל"


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

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

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


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


מודל ראשון:


מקור: https://martinfowler.com/apsupp/accountability.pdf

או אולי המודל השני?

מקור: https://martinfowler.com/apsupp/accountability.pdf

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

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

כלל מנחה: "!Make the model as simple as possible - but no simpler"


אני אוהב את הדוגמה של אריק על המפה (העתיקה) של חבל ארץ בסין:



מבחינה גאוגרפית המפה היא מעוותת, חסרה פרטים רבים, ומלאת שגיאות!

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

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




על אומנות ה Modeling


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

ב SCRUM, יש תפקיד בשם Product Owner שהוא המתורגמן בין אנשי הביזנס / הלקוחות - למתכנתים. כשיש PO אין צורך לדבר עם לקוחות: ה PO מדבר עם כל הלקוחות, מרכז את התובנות שלהם לתמונה אחידה וברורה - שקל למפתחים לצרוך.

DDD מציגה רעיון שנקרא Hands-On Modeler - המתכנת (להלן "Hands-On") צריך להיות גם ה Modeler - האדם שמגדיר את המודל, מבין אותו, ומכאן מבין גם את הביזנס ונמצא בקשר עם לקוחות המערכת. זה לא צריך להיות כל מפתח (Every) אלא ייצוג הולם של אנשים מצוות הפיתוח (Any).

שנייה אחת. יש כבר רעיון שכל מפתח צריך להיות איש ה QA של עצמו (TDD?), או הרעיון שכל מפתח צריך להיות איש ה Operations של עצמו (Continuous Deployment?).

האם באמת מפתח יכול להיות גם QA, גם איש Operations, וגם איש Product - ועוד להספיק לכתוב קוד משמעותי?!
כנראה שלא ממש. הסיבה שכל אחת המתודולוגיות הללו "מלבישה" למתכנת "כובע" נוסף חדש - היא כי ממשקי עבודה ("Seams") הם מקור לתקשורת נחותה יותר ("טלפון שבור") ואופטימיזציה לוקאלית בכל צד - מתכון מוכך לבעיות.
  • ארגונים שאיכות היא עניין מאתגר ומורכב במיוחד עבורם - כנראה ידרשו מהמפתחים גם לעשות QA.
  • ארגונים שסביבת הפרודקשיין שלהם מאתגרת - כנראה יידרשו מהמפתחים לעשות גם Operations.
  • ארגונים שהתאמת המוצר ללקוח היא חלק מאתגר במיוחד בעיסוק שלהם - יאמצו DDD ו/או Lean Startup.
    • נחדד: אם הלקוח הוא consumer אחד מני רבים - כנראה שגישת ה Lean Startup ושורה של ניסויים - היא עדיפה. אז נרצה שמפתחים יציעו ויבצעו ניסויים.
    • אם הלקוח הוא Domain Expert שיש מעטים כאלו שאפשר לגשת ולתחקר אותם - זה ה Sweet Spot של DDD.

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


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

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

אם זו הביזנס הוא תחנת מוניות מוניות אז ה Domain Experts חושבים על "היצע" (נהגים) ו"ביקוש" (נוסעים) ועל אחוז ההיענות של נהגים לנסיעות ו/או אחוז ההזמנות שסופקו. הדרישות החדשות שלהם לתוכנה שנכתבת - יגיעו בעולם המושגים הזה.

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


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

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

יותר גרוע: ככל שבאות עוד ועוד דרישות - מודל התוכנה "יתעקם" ויתרחק מהמודל של ה Domain Experts. התהליך הזה נקרא ב DDD בשם Corruption.


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

"למה מורכב? תצרו נסיעה עם נוסע אחר. נשים שדה ב UI להזין את זה"
- "אתם לא מבינים. זה ממש מסובך! צריך שה HiddenOrder עכשיו יהיה ExpandedHiddenOrder שנשמר בטבלה אחרת וגם מכיל נוסע שיכול להיות אחר, והטיפוס הזה מדלג על ה Validations כבר יש לנו במערכת עבור הסיפור ההוא של נסיעת חזרה פשוטה.... ו..... ואז...."

"??? ....  אנחנו לא ממש מבינים למה זה הפעם מורכב. תוכנה זה תחום בלתי-צפוי - מסתבר..."

בקיצור: הסתבכנו!


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



כדי לבנות תוכנה שממופה 1:1 למודל הקונספטואלי - אנחנו צריכים להבין את המודל (הנפלא!) הזה שנמצא במוחותיהם של ה Domain Experts. הבנה שלו תאפשר לנו לקבל דרישות חדשות בהרמוניה.

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

זהו תהליך ה Modeling.


אם המודל מוכר רק ל PO או לארכיטקט - Corruption הוא בלתי-נמנע.

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

כמו כן ה Modelers לא יכולים להיות מנותקים מקוד. הרי המודל בא לשרת תוכנה - ולא להיות מודל "כללי". תהליך המידול דורש גם הבנה בתוכנה וגם בביזנס.


ברוכים הבאים ל DDD :-)





העקרונות הראשיים של DDD (בקצרה)


המודל

דיברנו.
איך ממדלים בצורה טובה? - הייתי ממליץ להתחיל עם הספר Analysis Patterns.



Ubiquitous Language (שפה משותפת-אחידה)

פירוש מילולי יותר של Ubiquitous הוא "נמצא בכל מקום בו-זמנית". דומה מאוד ל Universal.

כיצד יוצרים מודל משותף, ומתוקשר היטב? - מדקדקים בשפה!
הרעיון של Ubiquitous Language הוא שנקודת מפתח לגיבוש, שימור, ועדכון המודל הוא הקפדה על שפה משותפת ואחידה - בין כל ה Domain Experts וכל המפתחים.
  • המודל הוא עמוד השדרה של השפה המשותפת. הוא אמור להכיל את המונחים החשובים.
  • אנו מקפידים על שמות נכונים של אובייקטים, טבלאות בבסיס הנתונים, משתנים וכו' - ע"פ השפה המשותפת.
    • זה גם נכון למסמכים, מיילים, ודיונים. שימוש בשפה "תקנית" מצופה מכולם.
    • כאשר הקוד יוצא מסנכרון עם השפה - מיישרים אותו חזרה בעזרת Refactoring.
  • אם לקוחות משתמשים במונח הרבה - כדאי שזה יהיה אובייקט מרכזי במערכת. אם לא - זהו smell לטעות מידול.
  • אם לקוחות משתמשים במונחים דומים לחלופין - חשוב להבהיר את ההבדל! או שיש פה תובנה משמעותית - או שיתיישרו למונח יחיד.
  • בגדול, הלקוחות (ליתר דיוק: ה Domain Experts מהביזנס) הם אלו שמכתיבים את השפה. הם מעבירים את הידע שברשותם לפיתוח.
ללא שימוש פעיל ב Ubiquitous Language - המודל הופך לעוד מסמך Design שרק בדדים מכירים - ומושפעים ממנו.



Bounded Context

מה קורה כאשר Domain Experts בקבוצה אחת לא משתפים אותה שפה משותפת עם Domain Experts מקבוצה אחרת? למשל: אנשי הכספים לא שותפים לאותה שפה ותפיסת עולם עם אנשי המכירות? (בהנחה ששניהם לקוחות של המערכת)

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

Bounded Context (להלן BC) הוא כלי שמומלץ לפרויקטים גדולים ומורכבים. להלן "Tackling complexity in the heat of software" - הסיסמה המתנוססת על ספר ה DDD.

הבחירה באימוץ כמה Bounded Contexts היא נכונות להשקיע עוד עבודה (ייתכן גם כפילות קוד / נתונים) על מנת ליצור לכל סוג לקוח את המודל שיניע את המערכת שהוא זקוק לה.
אם גם אנשי הכספים וגם אנשי המכירות מדברים על "נוסע", אבל לכל אחד יש תכונות והתנהגויות שונות שלא ממש מסתדרות - אנו ניצור שני אובייקטים: 
  • "נוסע" בהקשר התחום של אנשי הכספים.
  • "נוסע" בהקשר התחום של אנשי המכירות.
כמובן שגישה זו מזמינה בעיות Consistency במערכת (לא דבר טוב) - ולכן חשוב על כל סוג נתון מרכזי (שם, אימייל, מספר טלפון) - להגדיר Single Source of Truth, קרי איזה אובייקט ("נוסע-כספים" או "נוסע-מכירות") הוא מקור האמת במידה של חוסר התאמה.

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

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


Bounded Context ועולם ה Micro-services

גישה (פשטנית) מסוימת קובעת שכל מיקרו-שירות מגדיר Bounded Context משלו. 
זה נכון: בעולם המיקרו-שירותים לכל שירות יש בסיס נתונים משלו, ויש כפילויות נתונים מסוימות - בהגדרה, וגם single source of truth.

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

יש לי שם למצב בו כל שירות מגדיר שפה משותפת-אחידה משלו: "כאוס".



Context Map

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


Core Domain

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

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

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



כמה קשיים ביישום DDD


אם DDD היה כרוך רק בהתקנה של תוסף (נוסח apt-get install) - בוודאי רובנו כבר היינו משתמשים בו.

בפועל, זה קצת יותר מורכב.
  • קבוצת ה R&D לעתים נתפסת ע"י ההנהלה כמרכז עלות ולא כמרכז רווח: נכון, החברה אולי מרוויחה מהתוכנה שכבר כתובה, אבל הדגש כעת הוא על צמצום בעלויות הפיתוח (פחות עלויות תחזוקה, פיתוח פיצ'רים מהר / זול יותר) ולא בהשקעה להגיע ל"פיצ'ר שתופס בצורה מעמיקה את הביזנס".
  • מפתחים שקועים במידה רבה בטכנולוגיה ובמחשבה כיצד להתנסות / לשלב טכנולוגיה חדשה במערכת - יותר מאשר איך ליצור תוכנה נכונה יותר לביזנס.
    טכנולוגיות חדשות הן מפתח להתחדשות והתייעלות, אך כאשר המיקוד הוא "אימוץ טכנולוגיה החדשה" ולא החדשנות וההתייעלות - התוצאה היא בהתאם.
  • ה DB כבר כמה עשורים מקבל מרכז כובד מוגזם בתכנוני המערכת. נכון: הוא כלי רב יכולות, וקל לפשט בעיות ועסקיות ולחשוב עליהן כבעיות של data ("כיצד לוקחים את הנתון הזה, מבצעים עליו עיבוד ומעבירים אותו לשם") - אבל ההתייחסות ל DB כמרכז המערכת מסתירה מאיתנו תובנות עמוקות יותר על המערכת והביזנס, ולרוב מונעת מ "תכנונים יעילים במיוחד" של המערכת מבחינת DDD - מלהתפתח.
  • מבנה המערכת לא מתמפה בצורה פשוטה למודלים העסקיים. 
    • הסיבה: המפתחים בוחרים במידול / מבנה מערכת מתוך חשיבה טכנית גרידא. הם בוחרים בשמות לאובייקטים ולמודולים, כך שיהיו נוחים להם עם הידע הנוכחי שהם - "ידע" שלרוב הוא בעצם בורות עמוקה (סליחה סליחה!) ברזי הביזנס.
    • התוצאה: חלק מהדרישות העתידיות שיגיעו למפתחים יהיו "הפתעת יום כיפור": הם תכננו את המערכת עם גמישויות X - אבל הדרישות סובבות מסביב לגמישויות Y. השינוי הנדרש הוא ארוך ויקר והמפתחים יעמדו על הרגליים האחוריות להסביר לביזנס ש"הדרישה שלהם לא סבירה", וש"יאלצו להסתדר בלי". הם עלולים להיות כמו כלב תוקפני שיש להיזהר ממנו.
      מותר להעמיד את אנשי הביזנס בפני אי נוחות - מלבד הפעמים שאסור.
    • תוצאה בגרסה "משופרת": המפתחים עובדים בחשיבת Lean. הם לא יצרו גמישויות X כלשהן - ולכן "תסריט יום כיפור" הוא פחות מזעזע עבורם. הם פשוט יבצעו את השינויים הנדרשים במערכת על מנת ליצור גמישות Y. הם אדישים לשינויי הדרישות והם לא יעצרו או יילחמו בביזנס - אבל הם גם יכולים לבזבז זמן רב ב Refactorings הלוך ושוב - כי הם לא מצליחים "להריח" להיכן הביזנס לוקח אותם. הם כמו כלב ידידותי אך עיוור.

ה "System Flexibility Challenge" בין R&D לביזנס, מודגם במטאפורה של מכאניקה.
המפתחים מספקים גמישויות x1 ו x2 - ושמחים שענו לצרכי הביזנס, אבל הביזנס בעצם זקוק ל y4 ו y2.



קשיים בעבודה משותפת: פיתוח ו Domain Experts


Communications usually fails, except by accident"  -- Osmo Wiio"



למעלה: איך שהפיתוח עשוי להיתפס לפעמים ע"י הביזנס - מקור מקור: nytimes
למטה: איך שהביזנס עשוי להיתפס לפעמים ע"י ה R&D - מקור: spiritscienceandmetaphysics

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

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

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

רק שזה לא עובד כך.

פערי השפה / תפיסה בין איש פיתוח ל Domain Experts הם גדולים - ולוקח זמן מה להתחיל ולגשר עליהם.

"למד אותי את הדומיין!" היא קריאה שתעורר לרוב השתאות ממש כמו הבקשה "צייר לי כבשה!"

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

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

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

יש גם סיכוי טוב שיש פער אמון שצריך לגשר עליו. במיוחד אם ה Domain Expert הוא משתמש מתוסכל של מערכת מחשוב כלשהי שפותחה ב R&D...

הכל יכול להיות.


פעם אמרו ש eXtreme Programming לא מתאים לכולם.

אז גם DDD לא מתאים לכולם. הוא מתאים לגוף R&D שזו האג'נדה שלו, ולאנשי תוכנה בעלי כישורי תקשורת טובים, והרבה מוטיבציה להיכנס לעולם הביזנס - בהשקעה הנדרשת.

איך מכשירים אנשי תוכנה לעבוד עם Domain Experts? למדל?

אני בהחלט לא מסוגל לענות על השאלה הזו על רגל אחת, ואולי גם לא מסוגל בכלל :-)




סיכום


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

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

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

אציין משהו לא מגניב: אני נתקל בפעילות רבה במיוחד בכל הנוגע ל DDD דווקא בעולם הדוטנט / #C (למשל: naked objects - אבל גם הרבה מעבר לזה). אני מניח שמפתחים הרבה יותר "תוכנה עסקית" על סביבת NET. של מייקרוסופט מאשר על סביבות אחרת - או לפחות הם מזהים את עצמם ככאלה.

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

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


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



יום שלישי, 16 במאי 2017

כשאתה אומר "Event-Driven Architecture", למה אתה מתכוון?

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

ה Keynote ב Goto Chicago שהתקיים לפני מספר ימים היה של מרטין פאוולר, בו הוא ביצע סיווג של סגנונות של "Event-Driven Architecture" (או בקיצור: EDA).


יש לי באופן אישי בעיה עם ההגדרה "Event-Driven Architecture".
  • האם הארכיטקטורה עצמה מונעת על ידי אירועים?
  • האם באמת הפרט החשוב ביותר בארכיטקטורה הוא האירועים או כיצד הם מטופלים?

לומר ש "יש לנו ארכיטקטורה שהיא Event-Driven" זה כמעט כמו לומר "יש לנו ארכיטקטורה שהיא Database Driven" או "ארכיטקטורה שהיא Java Driven". כלומר:

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

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

ובכן... מדוע אנשים מציינים שיש להם "Event Driven Architecture"? כנראה כי:
א. זה נשמע "טוב". באזז מרשים.
ב. כי אותם אנשים מרוצים מהשימוש ב events - ונתונים לתכונה הזו של המערכת דגש מיוחד.

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

הייתי קורא למערכת כזו "Event Based", ומנסה להבין מה מניע את המערכת והארכיטקטורה שלה. האם מדובר ב Scalability? האם חלוקה של תהליך עיבוד ליחידות קטנות ופשוטות יותר?


אז למה באמת מתכוונים ב "Event-Driven Architecture"?


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

מתוך הספר Event-Driven Architecture: How SOA Enables the Real-Time Enterprise

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




בספרון של מארק ריצ'רדס בשם Software Architecture Patterns הוא מתאר שני סוגים עיקריים של EDA:
  • Mediator Topology - בה יש "מח" מרכזי שיודע איזה שלבים יש לכל event, אלו ניתן למקבל וכו'- והוא זה שמנהל אותם
  • Broker Topology - בה החוכמה היא "מבוזרת" וכל רכיב שמטפל ב event יודע מה היעד הבא של ה Event / או אילו events חדשים יש לשלוח.
הוא בוחן את נקודות החוזק והחולשה בכל גישה - וזה נחמד, אך הוא עדיין לא ממש מספק תמונה שמכסה את השימושיים העיקריים של events במערכות תוכנה.


קצת עזרה??


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

פאוולר מגדיר ארבעה צורות עיקריות לשימוש ב Events במערכת. אם נשתמש במונחים הללו, ולא במונח הכללי "Event-Driven Architecture" - הרי נוכל להבין טוב יותר אחד את השני.


Event Notification


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

המודל של Event Notification הוא פשוט ושימושי.
  • הוא מתאים כאשר מישהו רוצה לשלוח הודעה והוא לא מצפה לתשובה.
  • הוא מתאים כאשר הצד מרוחק יבצע את הפעולה בקצב שונה. למשל: בשל תקלה בחברת כרטיסי האשראי ייתכן שהחיוב יתבצע רק לאחר שעה (דוגמה קיצונית).
  • הוא מאפשר למערכת ניהול הנסיעות להיות בלתי תלויה במערכת החיוב: אם יום אחד רכיב אחר יטפל בחיוב / הטיפול יחולק בין כמה רכיבים - מערכת ניהול הנסיעות לא תשתנה כתוצאה מכך.
    • חוסר התלות הזו היא לא מושלמת: מה קורה כאשר מערכת החיוב זקוקה לנתון נוסף לצורך החיוב? (על כך בהמשך)
    • כלל טוב לצמצום התלות הוא לשגר אירוע "כללי" שמתאר את מה שקרה במערכת "נסיעה הסתיימה" ולא פקודה כמו "חייב נסיעה!". שימוש במינוחים של פקודה גורם לנו לקבל בצורה עמוקה יותר את התלות בין המודולים - ולהעצים אותה לאורך הזמן.
  • כאשר יש ריבוי של Events Notifications במערכת - קשה יותר לעקוב אחרי ה flow, במיוחד כאשר events מסוימים מתרחשים רק לפעמים ו/או במקביל.
    Mitigation אפשרי הוא מערכת לוגים מרכזית ופעפוע "request id" (ואולי גם hop counter) על גבי ה events. כל כתיבה ללוג תציין את ה request id - וכך יהיה אפשר לפלטר את כל מה שהתרחש במערכת במערכת הלוגים ולראות תמונה שלמה. בערך.

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


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

בשני המקרים, יש לנו תלות מסוימת במערכת החיוב: שינוי במערכת החיוב עלול לדרוש שינוי במערכת ניהול הנסיעות - בגרסה אחת להוסיף שדה ב API של מערכת ניהול הנסיעות, בגרסה שניה - נתון על ה event.

איזו גישה עדיפה? - אין לי תשובה חד משמעית.

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



Event-Carried State Transfer

גישה זו היא וריאציה של גישת ה Event Notification, אבל שינוי אחד בה - משנה בצורה משמעותית את כללי המשחק:

  1. אפליקציית הנהג מודיעה שהנסיעה הסתיימה.
  2. מערכת ניהול הנסיעות שולחת את כל הנתונים שיש לה על ה event.
    איך שולחים את כל הנתונים? בד"כ לוקחים את אובייקט המודל של ה ORM - ועושים לו serialization. 
  3. מערכת החיוב בודקת כל הזמן אחר הודעות. כאשר יש הודעה - היא קוראת ממנה רק את הנתונים שהיא זקוקה להם. היא עשויה לשמור עותק מקומי שלהם.
בעצם במקום להעביר הודעה, אנחנו מעבירים את ה state השלם של האובייקט בין המערכות השונות.

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

לגישה יש גם כמה חסרונות:
  • שברנו את ה encapsulation: מערכת החיוב מכירה את מבנה הנתונים הפנימי של מערכת ניהול הנסיעות. מעכשיו יהיה קשה הרבה יותר לבצע שינויים במבנה הנתונים, ויש גם סכנה שהמתכנת של מערכת החיוב לא יבין את השדות נכון - ויפעל ע"פ נתונים מוטעים.
  • העברנו הרבה נתונים מיותרים ברשת - בד"כ זו בעיה משנית.
  • יצרנו עותקים שונים של הנתונים ברשת, מה שפוטנציאלית יוצר בעיה של Consistency בין הנתונים. נתונים שכן צריכים להיות up-to-date לצורך הפעולה - יהיו לא מעודכנים ויתכן שיהיה צורך בליישם מנגנון של eventual consistency.

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


מה קורה כאשר מידע נוסף נמצא על מערכת שלישית? למשל, את הפרט אם נסיעה התחילה בשדה תעופה (ואז יש לתת 30% הנחה ;-)) ניתן להסיק רק כאשר יש נתונים נוספים ממערכת האזורים?

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




Event Sourcing


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



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

מה התועלת בגישה הזו?
  • אם אנחנו מקבלים עדכוני state מכמה מקורות - אין לנו race condition בו מקור א' מוחק את השינוי של מקור ב'.
    זה יכול להתרחש אם מקור א' קרא עותק ישן של האובייקט, לפני השינוי שמקור ב' ביצוע - אבל ההודעה ממנו הגיעה אחריו.
  • אנחנו שומרים היסטוריה של האובייקט ושינויי המצב שלו. אם נרצה - נוכל להרכיב את מצב האובייקט בכל רגע נתון (בהנחה ששמרנו את הזמן שבו קיבלנו את ההודעה).
    • זה יכול להיות שימושי לצורך debugging / הסבר התנהגות המערכת.
  • ההודעות שנשלחות הן קטנות (רלוונטי כאשר האובייקט השלם הוא גדול)
יש גם כמה חסרונות:
  • הרכבה של האובייקט, במיוחד אם קיבל רשימה ארוכה של עדכונים - היא פעולה יקרה יחסית.
    • החיסרון הזה הוא בעייתי במיוחד אם הרכבה של האובייקט דורשת נתונים נוספים ממערכות אחרות.
    • החיסרון הזה מתמתן אם אנחנו מחזיקים עותק של הנתונים בזיכרון.
  • מה קורה כאשר הסכמה משתנה? כלומר: מבנה הנתונים?

פה עשויה לעלות שאלה פילוסופית: אם אני מקבל את העדכונים כ delta, אבל אז בכל עדכון עושה merge עם האובייקט שאני מחזיק אצלי - האם זה עדיין Event Sourcing?

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



Command Query Responsibility Segregation (בקיצור: CQRS)

מספרים שאם הייתם מחפשים בגוגל "CQRS" לפני עשור, הוא היה שואל: "`?did you mean `Cars"
הרעיון הוא דיי ישן, ומקורו בשנות השמונים, אבל רק בשנים האחרונות הוא הפך למאוד-מוכר.

אני מניח שהרוב הגדול של הקוראים מכיר את השם, אבל לא בהכרח מכיר את הרעיון מאחוריו. לרוב האנשים CQRS מתקשר ל "high performance".

האמת שהרעיון של CQRS אינו קשור קשר ישיר ל events, אבל פעמים רבות - משתמשים בו כך.

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




מתי זה שימושי?

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

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

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

במקום זה, לאחר שכתבתי את ה tweet יש Background Processor שמעתיק את הטוויט שלי לפיד של כל העוקבים.
כלומר:
  • מודל "הכתיבה" הוא רשימה של טוויטים ע"פ מחבר.
  • מודל "הקריאה" הוא הפיד של כל משתמש בנפרד.
זה אומר שיש הרבה שכפול נתונים במערכת, ושטח האחסון הנדרש הוא אדיר. אם יש למישהו מיליון עוקבים - כל טוויט ישוכפל מיליון פעמים.
מצד שני, זה גם אומר שגם אם אני עוקב אחרי 1000 פרופילים ויותר - הפיד שלי ייטען ב (O(1.
במקרה של טוויטר סביר שמודל הכתיבה ומודל הקריאה הן בכלל מערכות שונות - כאשר כל הוספה של טוויט למודל הכתיבה - שולחת אירוע של עדכון (event notification) למודל הקריאה - שם נמצא ה Background processor.

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

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


שימוש אחר של CQRS הוא לא דווקא מבחינת ביצועים גבוהים, אלא פשטות של הקוד. אם המודלים סבוכים למדי - החלוקה ל"מודל כתיבה" ו"מודל קריאה" יקטינו את ה code base בחצי, בערך.

כאן שווה לציין ש Event Sourcing ו CQRS הולכים יד ביד זה עם זה:
מודל הכתיבה הוא ה State Log - אבל יש מודל קריאה שהוא המצב העדכני. זה יכול להיות בסיס נתונים או טבלה אחרת בה שומרים את המצב העדכני, וזה יכול להיות מודל שעובד מעל אותם נתונים - ורק מכיל את הקוד של "השטחת" העדכונים בזמן ה Query.




סיכום


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

הייתי מעורב בבניית מערכת שמבצעים בה רפליקציה של נתונים לשירותים אחרים, כמו בגישת ה Event-Carried State Transfer - בכדי להשיג High Availability. מצד שני, כמות הנתונים שמועתקת היא קטנה ומדודה מאוד, והנתונים הם ברמת הפשטה של ממשק ולא מבנה נתונים פנימי - כך שאין פגיעה בהכמסה של המערכת.

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

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


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


---

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

ההרצאה של פאוולר ב Goto; Chicago
פוסט של פאוולר בנושא

יום שלישי, 2 במאי 2017

על אימות זהות בווב, וקצת על OAuth 2.0 ו OpenID Connect

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

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

אנחנו ניגשים לאנשי השרת - וגם הם לא ממש בטוחים מה לעשות:
  • האם יש משהו לא טוב ב Basic Authentication? הרי "Basic is beautiful" (ע"פ פוקס).
  • אולי עדיף להוסיף כל מיני "טריקים שמקשים"? אולי לחפש ב StackOverflow?

הנה מדריך יפה שמצאתי בגוגל, "The Ultimate Guide Of Mobile Security" של חברת StormPath (לארונה נרכשה ע"י OKTA).
בטוח שיש בחברה הזו מומחי אבטחה גדולים ממני. אין ספק!
מצד שני... זה המדריך האולטימטיבי? האם הוא באמת משרת את צורכי האבטחה של הקורא, או בעיקר עושה On-Boarding מהיר ספציפית לפתרון של StormPath?

גם כשאתם ניגשים למדריך מוכן, כדאי שיהיה קצת מושג - ועל כן הפוסט הבא.
אדבר על אימות שרת-לשרת, מובייל ועל אפליקציה שרצה בדפדפן, על Basic Authentication אבל גם על OAuth 2.0 וקצת על OpenID Connect.


נתחיל מההתחלה: Basic Authentication (בקיצור: BA)


נתחיל במקרה הפשוט ביותר (מבחינת האבטחה): תקשורת שרת-לשרת.

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

הנה אופן הפעולה:

1. המתכנת של שחקים מייצר מפתח-זיהוי ייחודי וקשה מאוד לניחוש, שיזהה את הלקוח הספציפי - שוקה:
jsa7arpZ8sPZ60YZyZwfD97gf5cHbEBj77VF6nF4
מחרוזת אקראית באורך 40 תווים נחשבת כיום למפתח מספיק חזק.

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

3. כאשר השרת של שוקה פונה לשרת של שחקים, על גבי HTTPS, הוא מוסיף על הבקשות את ה header הבא:
Authorization: Basic anNhN2FycFo4c1BaNjBZWnlad2ZEOTdnZjVjSGJFQmo3N1ZGNm5GNA==
הפרמטר הראשון אומר איזו סוג זיהוי (Authentication) מדובר. במקרה הזה: BA.
הפרמטר השני הוא ה credentials ("אישור ההרשאות"), במקרה הזה: מפתח הזיהוי מקודד ב Base64.

Base64 הוא קידוד של binary-to-text הנדרש על מנת להעביר את המידע על פרוטוקול HTTP בצורה תקינה. הפרוטוקול מצפה לטקסט, ותווים מסוימים יכולים להתפרש אחרת לחלוטין (למשל: שורה ריקה משמעה שהסתיימו ה headers ומתחיל ה body של בקשת ה HTTP).

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


חוזקות:
  • פרוטוקול פשוט שקל ליישום.

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


סה״כ, עבור תקשורת שרת-לשרת, בסביבה שאיננה מרובת איומים - ה BA הוא סביר.
נקודת מפתח היא הקושי ליירט תקשורת שרת לשרת (בהנחה שהשרת לא יושב במקום ציבורי ומשתמש ב Wifi / תא סלולארי)



גרסאת ה Web Client

כאשר התקשורת היא בין דפדפן לשרת, ה flow עובד מעט אחרת:
  • אין תקשורת מוקדמת עם המשתמש, ולא שלוחים לו מפתח-זיהוי.
  • המשתמש פונה ל URL הרצוי.
  • השרת שאינו מזהה את המשתמש, מחזיר HTTP Status 401, כלומר: Unauthorized - "אני לא יודע מי אתה". כמו כן הוא שולח header המסביר באיזו סוג authentication הוא תומך:
WWW-Authenticate: Basic
  • הדפדפן יקפיץ למשתמש חלון להקלדת שם-משתמש וסיסמה.
  • הדפדפן ייצור מחרוזת credentials כ ״שם משתמש:סיסמה״ - יקודד אותה ב Base64 ויעביר אותה על ה Authorization Header בקריאה הבאה:

Authorization: Basic <credentials>
  • השרת יפתח את הקידוד ויזהה אם יש לו משתמש עם סיסמה שכזו. אם כן - הוא יניח שהבקשה הגיעה מהמשתמש.
  • שם המשתמש והסיסמה ישארו ב cache של הדפדפן לזמן מוגדר (15 דקות?). בכל בקשה לשרת יישלח ה Authorization Header עם הסיסמה.


חוזקות:
  • פרוטוקול פשוט שקל ליישום.

חולשות:
  • שם המשתמש והסיסמה עוברים כ clear text על כל בקשה (Base64 שקול ל clear text). תקשורת בין endpoint (לצורך פוסט זה: ״תחנת קצה״ -כמו מחשבים ניידים או סמארטפונים) לשרת - הרבה יותר קל ליירט מאשר תקשורת שרת לשרת. למשל: התוקף ותחנת הקצה נמצאים על אותה רשת Wifi.
    • אם התקשורת נעשית על גבי HTTP (רחמנא ליצלן!) - אזי גניבת הסיסמה היא פעולת sniffing פשוטה.
  • שם המשתמש והסיסמה נשארים cached בדפדפן. תוקף יכול לעשות שימוש גם הוא ב credentials מבלי שאף אחד יידע (התקפת CSRF).

סה״כ, BA בדפדפן היא שיטה שנחשבת כחלשה למדי לזיהוי מאובטח של המשתמש, והיא איננה מומלצת לשימוש.



גרסאת המובייל

במובייל יש כמה אפשרויות:
  • אפשר לנסות ולהיצמד לגרסת השרת של ה BA: ולשלוח מפתח-זיהוי לכל משתמש - אך זה אפשרי רק עם התקנה ידנית של Certificate (תהליך סזיפי למשתמש) או מערכות ששותלות Agent במכשיר ועושות זאת - בעיקרון מערכות Enterprise שלא ישימות בסביבת Consumers.
  • אפשר לנסות ולהיצמד לגרסת הדפדפן של ה BA: ולשמור את שם המשתמש והסיסמה באחסון המוגן של המכשיר (shared preferences / key chain) - ואז לשלוח אותם כ credentials בכל בקשה.
    שליחת ה credentials, בכל בקשה, ובמיוחד מתוך endpoints היא חולשה משמעותית - ולכן לא ניתן להמליץ על הגישה הזו.


ל HTTP יש עוד שיטת Authentication שנקראת HTTP Digest Access Authentication, שמנסה להקשות על גילוי הסיסמה - אך בפועל כיום היא נחשבת שיטה פחות בטוחה מ BA.



הבו לנו OAuth 2.0!


רוב הסיכויים שאתם שמעתם על פרוטוקול 2.0 OAuth ל Authentication. יש לו שם של פרוטוקול מכובד "Enterprise Level", אולי הנפוץ ביותר לשימוש היום - ולכן אפשר לסמוך עליו!

המחשבה ש"אני משתמש ב OAuth 2.0 - ולכן אני מאובטח", היא אולי נעימה - אך לא ממש נכונה. ל OAuth 2.0 יש יתרונות - אך גם חסרונות.

ראשית כל OAuth 2.0 הוא פרוטוקול ל Delegated Access ("ייפוי כח") ולא ל Authentication ("אימות זהות). הפרוטוקול נבנה על מנת לאפשר "ייפוי כח" למישהו אחר לגשת למידע שבבעלותי אך נשמר על ידי צד שלישי. בתוך ה Protocol מוגדרת "מסגרת ל Authentication" שהיא כללית, ואיננה נחשבת כחזקה במיוחד.

החלק הזה "מפיל" אנשים, ולכן אני הולך לתת חיזוק לטענה:

מתוך האתר הרשמי של OAuth, קישור: https://oauth.net/articles/authentication 

והנה הגדרה פורמאלית מה OAuth כן עושה:

The OAuth 2.0 authorization framework enables a third-party application to obtain limited
access to an HTTP service, either on behalf of a resource owner by orchestrating an
approval interaction between the resource owner and the HTTP service, or by allowing the
third-party application to obtain access on its own behalf.

שימו לב לקשר החזק ל HTTP.

מכיוון Authentication ב OAuth היא רק Framework ולא פרוטוקול - אין הגדרה חד-משמעית ומדויקת כיצד ה Authentication אמורה להתבצע. זה אומר ש:
  • ה Framework משאיר נושאים מסוימים לבחירת המימוש: איך להשתמש ב scopes, איך לבצע endpoint discovery, רישום דינאמי של לקוחות, וכו'.
  • אין בהכרח תאימות בין מימוש א' למימוש ב'. אם גוגל ופייסבוק מאפשרים חיבור ב OAuth - לא בטוח שאותו מימוש (של Client) יוכל לעבוד מול שניהם.
  • יש מימושים מאובטחים יותר, ומימושים מאובטחים פחות. תו התקן "OAuth" מציין בעיקר את סדר ההתקשרות - אבל מבטיח אבטחה רק במידה מסוימת.
    • למשל: לא נאמר כיצד להעביר את מפתח ההזדהות. ב URL או כ Header? (עדיף Header כי פחות סביר שהמידע הרגיש הזה יצוץ אח"כ בלוגים)
    • כיצד להעביר את המידע בין השחקנים השונים? לכאורה עדיף להצפין את נתוני ה token, אך יש כאלו שמשאירים אותם גלויים כי נוח שה Client יכול לקרוא גם הוא נתונים על המשתמש....
    • הנה רשימת עשרת החולשות הנפוצות ביותר במימושים של OAuth 2.0.


כיצד OAuth 2.0 עובד (בקצרה)

OAuth בעצם מגדיר ארבע אופנים שונים (נקראים Grants) לקבל Token ולהשתמש בו.

ה Token הוא תחליף ל Password, מכיוון שיצירת password במערכות רבות היא סכנת אבטחה ממשית: אנשים נוטים לעשות שימוש חוזר ב passwords שלהם במערכות שונות, ומערכת אחת שנפרצת - מאפשרת גישה למערכות נוספות, לעתים חשובות יותר.

כדי להבין את OAuth כדאי להשקיע דקה ולהכיר את ה"שחקנים" (Roles) המדוברים:

Resource Owner 
זה בעצם המשתמש, וכך גם אקרא לו בפוסט.

Resource Server (נקרא גם בשם המבלבל "protected resource")
אקרא לו בפוסט גם "שירות B" - השרת שמאכסן את הנתונים של המשתמש, למשל פייסבוק.

Authentication Server 
השרת שמולו המשתמש מזדהה, כלומר שם מתבצע תהליך ה Authentication והוא מנפיק Token שמאמת את זהות המשתמש. ברוב המקרים זהו יהיה ה Resource Server עצמו (או שירות B) - אך התקן לא מחייב זאת.

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


Authorization code grant

זהו כנראה אופן השימוש הנפוץ ביותר ב OAuth. אתחיל בלתאר אותו בצורה "סיפורית":

עכשיו המשתמש רוצה להתחבר לשירות A (למשל: ל Comeet), שם מופיעה לו אופציה להתחבר בעזרת החשבון הקיים שלו בשירות B, למשל: "Connect with Linkedin".


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

שירות B מציג אלו פריטי מידע (resource) הוא התבקש לחשוף (לעתים זהו רק ה email, לעתים רשימת חברים ויותר) וכאשר המשתמש מאשר - הוא חוזר לאתר המקורי (שירות A) ומקבל אליו גישה.

התוצאה: המשתמש קיבל גישה לשירות A מבלי שהיה צריך להזין בו שם משתמש / סיסמה. לעתים חושבים שלא נוצר שם חשבון - אך לרוב הוא נוצר על בסיס מזהה ייחודי שסיפק שירות B.


עכשיו נחזור על ה flow בצורה יותר טכנית:

לפני שהכל מתחיל, ה Client (למשל "Site") נרשם אצל Resource Server (למשל: Facebook). כתוצאה מתהליך הרישום ה Client מקבל client_secret ("מפתח זיהוי") - אותו הוא שומר בצורה מאובטחת.


כעת המשתמש רוצה להתחבר לשירות A:
  • שירות A מבקש מהמשתמש להזדהות, ומאפשר לו לעשות את זה בעזרת שירות B.
  • המשתמש בוחר בשירות B, ומופנה ל Authentication Server המתאים.
    • שירות A סומך על ה Authentication Server לבצע אימות מאובטח מספיק, ולנפק access-token  מתאים.
    • בד"כ מוצג למשתמש אילו נתונים הוא הולך למסור, ולעתים הוא יכול להחליט לא למסור חלק מהנתונים. כל קבוצת נתונים מוגדרת כ scope. על ה redirect URL ה Client ציין אלו scopes הוא מבקש.
    • המשתמש מזדהה בעזרת שם משתמש וסיסמה (או כל אימות אחר) ומאשר את ה scopes.
  • ישנו Redirect חזרה ל Client עליו מופיעים פרטים של הבקשה המקורית ל Authentication - שבהם ישתמש ה Client על מנת לאמת שזו אכן תשובה לבקשה שנשלחה, authorization code.
  • ה Client עכשיו פונה ל Auth. Server בעצמו,
    •  ושולח לו:
      • את ה authorization code שקיבל
      • client_id - זיהוי של ה client (לא של המשתמש), אינו סודי.
      • client_secret - קוד סודי שרק ה client מכיר (אותו הוא קיבל ברישום). 
    • בתמורה הוא מקבל אובייקט JSON המכיל:
      • תאריך תפוגה - ה token טוב לזמן מוגבל (באיזור השעה, בד"כ).
      • access_token - איתו הוא יכול לגשת לנתונים.
      • refresh_token - המאפשר לבקש גישה נוספת, במידה וה access_token פג תוקף.
    • הערה: התקן של OAuth לא מציין מה מבנה ה token, אך מקובל להשתמש ב JWT (קיצור של: JSON Web Token), פורמט הכולל 3 חלקים: Claims, Header, וחתימה.

העיקרון מאחורי ה tokens נקרא TOFU (קיצור של Trust On First Use), והוא אומר שבמקום לשלוח את מפתח הזיהוי בכל בקשה - תוך כדי שאנחנו חשופים ל sniffing, נצמצם את החשיפה לתקשורת הראשונה. בפעולה הראשונה אנו סומכים (יותר) על הצד השני, תחת ההנחה שפחות סביר להיות חשופים ל sniffing ברגע ההתקשרות הראשונית. אם ה access token, למשל, נחשף ב sniffing - הפגיעה תהיה מוגבלת לזמן הנתון, ועדיין ה refresh token וה client_secret לא נחשפו והם יכולים להמשיך לנפק בצורה מאובטחת יחסית access_tokens אחרים.

  • בשלב האחרון ה Client ניגש לשירות B בעזרת ה access_token - ושולף ממנו את הנתונים שביקש.
    לכאורה בתרשים זו קריאה בודדת, אך בעצם יכולות להיות עוד קריאות לאורך זמן.
    • אם פג התוקף של ה access token, ה Client לא יורשה לקבל את הנתונים. הוא יצטרך לפנות עם ה refresh_token ל Auth. Server ולקבל access_token ו refresh_token חדשים.


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



בקצרה:

  • Authorization Code Grant - הוא ה flow שעברנו עליו. אם המשאב הוא זיהוי של המשתמש (למשל: email), אזי יישמנו סוג של מנגנון Authentication על גבי OAuth.
    • ה Flow מתבסס על המצאות דפדפן והיכולת לבצע Re-direct (כלומר: Multi-page app, לפחות לשלב ה login).
    • ה Client הוא צד-השרת של האפליקציה, בו ניתן לשמור את ה client_secret בצורה מאובטחת.
  • Client Credentials Grant - הוגדר עבור חלקים שונים של אותה האפליקציה (הגדרה: "ה Client עצמו הוא ה Resource Owner"), שיש בין החלקים אמון גבוה. האימות הוא שרת-לשרת, ולא דורש התערבות של המשתמש. זהו בעצם מן גרסה משופרת של Basic Authentication בין שרת לשרת שמיישם את עקרון ה TOFU. מיותר לציין שנעשה שימוש נרחב ב Flow זה גם בין אפליקציות זרות, וזה לא דבר רע בהכרח (יותר טוב מ Basic Auth).
    • Flow זה גם נקרא two-legged OAuth (כי יש בו רק שני שחקנים מעורבים), בניגוד לכל שאר ה flows הנקראים three-legged OAuth.
  • Implicit Grant - הוגדר עבור אפליקציות ווב או מובייל שבהן לא ניתן לסמוך ברמה גבוהה על ה client_secret. לאפליקציית mobile ניתן לעשות reverse engineering ולשלוף את הקוד, בווב - ...זה אפילו יותר קל.
    • יש כאן פשרה של אבטחה על מנת לאפשר כן סוג של Delegation סביר של הרשאות.
    • ב Flow הזה אין refresh Token - ולאחר שפג תוקפו של ה access_token יש לבצע Authentication מחדש.
    • לא מעבירים ל Client את ה Auth. Code (כי לא סומכים עליו - הוא לא מספק אבטחה מוגברת), אלא ישר שולחים לו את ה access-token.
  • Password Credentials Grant (נקרא גם Resource Owner Credentials Grant) - הוגדר עבור אפליקציות בעלות Trust גבוה. ב Flow הזה המשתמש מספק את שם המשתמש וההרשאות שלו לאפליקציה (שלא שומרת אותן!), והיא משתמשת בהן על מנת לקבל access_token בשמו. את ה access_token אפשר לשמור בצורה מאובטחת(!) באפליקציה. 
    • אם ה access_token נגנב אז אפשר לגשת איתו רק לנתונים מסוימים של משתמש יחיד - נזק מוגבל.
    • את ה access_token יש לחדש מדי פעם, על ידי זיהוי מחדש של המשתמש. באפליקציות מובייל לא מקובל לבקש מהמשתמש סיסמה מחדש יותר מפעם בזמן מה.... חודש, נניח?


באיזה Flow כדאי להשתמש באפליקציית מובייל?

ב Spec המקורי ההגדרה הייתה להשתמש ב Implicit Grant: מעט אמון, ומעט אבטחה.
מאז יש מעבר לשימוש ב Authorization Code Grant, כאשר משתמשים להרשאות ב Native Browser ולא WebView. למשל: SFSafariViewController ולא WKWebView.

האם אפליקציית מובייל יכולה לשמור Client Secret ב Source Code שלה בצורה אמינה? באנדרואיד, למשל, קל מאוד להוריד apk ולבצע לו Reverse Engineering. ההמלצה אם כן היא להחזיק את הקוד ויישום ה OAuth בקוד ++C (קרי שימוש ב NDK) ולא ב Java - שם ה reverse engineering הוא קשה משמעותית.

Stormpath (שנרכשו לאחרונה ע"י OKTA) בכלל לא דנים בשאלה - ושולחים את המתכנתים ליישם Password Grant. אני לא בטוח שלא מעורבים פה שיקולים עסקיים (לספק Flow שקל ללקוחות ליישם, ולא להטריד אותם ב"זוטות").

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


לסיכום:

בתקשורת שרת לשרת

חוזקות:
  • עדיין פשוט ליישום.
  • TOFU עדיף על העברה של המפתח בכל פעם.

חולשות:
  • לא תוכנן במקור לשימוש בין שרתים עם Trust מוגבל. 

סה"כ טוב מספיק עבור מגוון שימושים, ועדיף על פני Basic Authentication.



בתקשורת Web 

חוזקות:
  • עדיף על Basic Authentication, בכמה מובנים (בעיקר Authorization Code Grant).
  • הפך כמעט לקונצנזוס - יש מעט מאוד מתחרים, לכל אחד את הבעיות שלו.

חולשות:
  • לא תוכנן כפרוטוקול Authentication.
  • מימוש יחסית מורכב בצד-השרת (בצד-הלקוח המימוש טריוויאלי)
  • Room For Error: ה Specification שלו מבלבל, ומגוון האפשרויות - מובילים למימושים / יישומים רבים שאינם באמת מאובטחים דיים. מספק "תחושת אבטחה מוגזמת".


בתקשורת מובייל

חוזקות:
  • עדיף על Basic Authentication.

חולשות:
  • Mobile App נחשבת לעתים קרובות כ Trusted - למרות שזה שיקוף לא נכון של המציאות.
  • חלק ממנגנוני האבטחה של OAuth מסתמכים על המצאות דפדפן שאפשר לסמוך עליו: אין יכולת ל redirect. שימוש ב WebView פותח פתח ל Phishing / Clickjacking.
  • Implicit Grant טוב מ Basic Authentication רק במעט.



OpenID Connect 


מעט היסטוריה שתעזור להבין את השתלשלות האירועים:
  • 2006- יצא תקן לאימות זהות בשם OpenID 1.0.  הוא עובד בערך כך: "אני ליאור", שואלים צד שלישי שסומכים עליו: "האם זה ליאור?", התשובה היא "זה ליאור" - ואז מאשרים את זהותי.
  • 2007 - יצא OpenID 2.0 עם כמה שיפורים.
  • 2007 - יצא 1.0 OpenID Attribute Exchange (בקיצור OIDAE) - הרחבה של OpenID 2.0 המאפשרת גם למשוך פרטים נוספים על המשתמש "זה ליאור, הוא בן 40, ויש לו 3 ילדים".
  • 2010 - יצא OAuth 1.0
  • 2012 - יצא OAuth 2.0
  • 2014 - יצא פורטוקול OpenID Connect (בקיצור: OIDC) שמשלב בין OpenID 2.0 + OIDAE על גבי ה Framework של OAuth 2.0 בצירוף של JWT (וחברים). 

לא נכון להשוות בין OpenID ל OpenID Connect. זה כמו להשוות בין Ext2 ללינוקס (בהגזמה).
OIDC אינו תואם ל OpenID בגרסאותיו השונות - הוא רק שואב מהם רעיונות.



OIDC הוא פרוטוקול, והוא סוגר הרבה מהפינות הפתוחות של OAuth 2.0:
  • הוא מחייב כללים רבים שהם בגדר המלצה ב OAuth 2.0.
  • הוא מגדיר את מבנה ה tokens (על בסיס JWT) - שב OAuth 2.0 פתוחים לפרשנות.
  • הוא מגדיר scopes סטנדרטיים (email, phone, וכו') - המאפשרים התנהגות סטנדרטית מסביב למידע בסיסי של המשתמש. הפרוטוקול מגדיר endpoint חדש הנקרא userInfo Endpoint ממנו ניתן לשאוב מידע בסיסי על המשתמש.
  • הוא גם פותח בתקופה בה אפליקציות מובייל הן מאוד משמעותיות, ויש התייחסות רחבה יותר למובייל בהגדרת הפרוטוקול.
  • החל מ 2015 יש הסמכה של OIDC, ויש רשימה של מימושים שהם Certified, אפשר כבר לצפות ל Compatibility.
  • OIDC מגדיר סוג נוסף של token הנקרא ID Token המאפשר ל Client לדעת כמה פרטים על המשתמש ועל מצב ב Authentication שלו. עיוות נפוץ של מימושי OAuth 2.0 הוא לאפשר ל Client לקרוא את ה Access Token - מה שפוגע ב Segregation of duties.
  • הפרוטוקול מוסיף הגנה בפני התקפת replay: עשיתי sniffing לתקשורת ולא הבנתי מה נאמר - אבל אבקש מהשרת לעשות זאת עוד פעם (מה שלעתים יגרום נזק).

הנה ה Flow הבסיסי של OIDC, שהוא בעצם התאמה של ה Flow המקביל שתואר עבור OAuth 2.0:

ה Flow מעט פשוט יותר, לא מתבסס בהכרח על דפדפן, והיעד הוא להשיג מידע על המשתמש - ולא "משאב כללי".



האם OpenID Connect הוא הטוב מכל העולמות?

אם אתם מעוניינים ב Flow של authentication - אז הוא אופציה מבטיחה. קשה לי לחשוב על מקרה שבו עדיף לעשות Authentication על גבי OAuth 2.0 ולא על גבי OCID.

OIDC הוא פרוטוקול צעיר יחסית, שעדיין לא זוכה לאימוץ דומה לזה של OAuth 2.0. יש אולי עשר מימושים ויותר של OAuth 2.0 על כל מימוש OIDC.

בשנים הראשונות, מימושים של OIDC זכו לכמה "פאדיחות". במימוש של Facebook התגלה באג חמור, שזיכה את המגלה שלו ב Bug Bounty הגדול ביותר בהיסטוריה של פייסבוק. מימוש ה reference של ארגון ה OpenID עצמו סבל מכמה בעיות, וגם מוזילה נכוותה.

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

OpenID מסומן היום כמחליף הטבעי של SAML 2.0 (פרוטוקול אימות זהות / SSO) מסורבל ועם תיעוד לקוי - אבל שמילא את מטרתו במשך תקופה נכבדת.

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

בתחום המובייל (כלומר: native app) פרוטוקול ה OIDC עדיין לא נחשב טוב מספיק. שתי תקנים מרכזיים Proof Of Possession ו PKCE בפיתוח - ואמורים להעלות מדרגה בתחום.

OpenID Connect הוא לא מושלם. למשל: קיימת עדיין הדילמה של החזקת גישה למערכות רבות ע"י Identity Provider אחד. יש סכנה שפריצה למערכת הזו תאפשר גישה לתוכן רב - אך היא ככל הנראה פחותה מהסכנה שבפיזור ססמאות. מבין האופציות הקיימות, OpenID Connect היא האופציה העדיפה.


השוואה מהירה בין SAML 2.0 ל OCID


סיכום


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

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


----

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

ה OAuth 2.0 Threat Model המגדיר את הסיכונים השונים ביישום OAuth.
ה OAuth Grants מוסברים על ידי Alex Bilbie.
עוד קישור טוב על OAuth 2.0
"OAuth has ruined everything"
מדריך טוב ל OpenID Connect



יום שני, 3 באפריל 2017

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

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

ישנה מצגת מפורסמת של טובי קלמסון מ 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.



יום שני, 27 במרץ 2017

מחפש ארכיטקט מערכת לגטט (Gett)

היי,

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

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


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

גטט היא חברה ישראלית, גם הפרודקט נמצא בישראל וגם רוב ההנהלה. זו עדיין חברה פרטית, שגייסה קצת יותר מ 620 מיליון דולר מאז הקמתה לפני 6 וחצי שנים (נראה לי שזה שיא ישראלי). היא כנראה אחת מחברות ה Consumer הגדולות בארץ: מה שיוצא ל production מקבל תגובה מיידית. לטוב, ולרע. אנו יושבים בת"א, ברחוב הברזל.

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

בגדול מה שעל הארכיטקט אצלנו לעשות הוא:
  • ארכיטקטורה 
    • להיות חלק מהמאמץ של צוות האכיטקטים לתחזק ארכיטקטורה טובה של המערכת: מודולריזציה טובה, flows מערכתיים טובים, טכנולוגיה מתאימה, ורמה מספיקה של תכונות אכות מסוג 3S, כלומר: Stability, Scalability, ו Security.
    • המערכת, כמובן, משתנה כל הזמן. הפיצ'רים משתנים - אך גם הצרכים משתנים.
    • איך עושים את זה?
      • ע"י Reviews (יושבים עם צוותים ולומדים מה קורה במערכת), מחקר (לפעמים צריך לחפור יותר ו/או ללמוד בצד), ותהליכים שונים נוספים שעוזרים להבין כיצד עדיף לבנות את המערכת.
      • התובנות הללו הופכות למשימות: 
        • חלקן גדולות, למשל: לשפר את מערכת זיהוי המתשמשים, ליישם תהליך של בדיקות יצביות ("chaos testing"), או להוביל מאמץ לשיפור Scalability בחלקים מסוימים של המערכת.
        • חלקן קטנות: לעבוד עם דבאופס על שינוי X, לעשות רביו לפיצ'ר Y, או להגדיר תהליך קטן Z.
      • את המשימות הללו יש לבצע. לגרום לדברים להתקדם, ולא רק להתקדם - אלא גם ברמה גבוהה.
      • חשוב גם לתקשר כל הזמן בצורה פרואקטיבית ל R&D את התמונה המלאה, את הארכיטקטורה - רק כך יוכלו להיצמד אליה ולהמשיך אותה (ולא לסתור אותה). זה אומר לכתוב מסמכים (קצרים ויעילים!), לעשות sessions, ולחשוב כל הזמן היכן יש פערים.
    • חשוב לציין שכיום, כארכיטקטים אנו מעורבים רק בפיצ'רים הקריטיים לכלל המערכת. שאר הפיצ'רים הם של הצוותים, ואנחנו עוזרים לייצר את הכללים כיצד משלבים את הפיצ'רים במערכת בצורה טובה. איך יתוכנן הפיצ'ר - זה עניין של הצוות, כל עוד הוא משתלב בצורה נכונה במערכת.
  • מקצועיות של R&D
    • קבוצת הפיתוח צמחה בחברה בקצב מהיר מאוד. כמעט גדלה פי 4 בשנתיים האחרונות, מאז אני הצטרפתי. 
    • הדרך היחידה לשרוד כזאת צמיחה היא לשמור על רמה גבוהה לכל האורך.
    • אנו עוזרים להגדיר את תהליכי הגיוס (בצד הטכני. לוודא שהמבחנים מספיקים והמראיינים יעילים), תהליכי ה On-Boarding, וכמובן שיפור מקצועי תמידי בתוך ה R&D. יש לכך היבטים רבים: בניית בסיס ידע, בניית קהילות טכנולוגיות בתוך החברה, תהליכי למידה (למשל: Code Review), ועוד... 
    • אנחנו רוצים להתחיל גם להיות פעילים בקהילת ה Startup הישראלית, ולשתף חלק מהידע החוצה / להביא יותר ידע מבחוץ פנימה.
  • שירותים טכניים כאלו או אחרים
    • ניתוחים טכניים כאלו או אחרים שנדרשים בחברה נעשים על ידנו. זה החלק הקטן של התפקיד.

דרישות:

כמקובל, הגדרנו דרישות שיעזרו לנו למקסמם את סיכויי ההתאמה של מועמד. אני רציני לגביהן ומנסה לבחון אותם ברצינות כחלק מהתהליך:
  • חמש שנות ניסיון בפיתוח של מערכות צד-שרת/ווב. זה לא המון - אבל זה חשוב!
  • לפחות שנה אחת בסביבת SaaS/Production משמעותית. חשוב מאוד "לחיות" פרודקשיין. 
  • ניסיון של שנתיים כארכיטקט בתפקיד דומה. אני זקוק למישהו שיכול להתחיל לתפקד תוך כמה חודשים. 
    • בזמנו קיבלתי הרבה קו"ח של מתכנתים מנוסים / מובילים טכניים - ודחיתי אותם. המעבר לתפקיד ארכיטקט (היבטים לדוגמה: hands-off - eyes on, השפעה ללא סמכות, חשיבה ברמת המערכת) הוא לא קל, ולצערי כרגע אין לי את הפריווילגיה לגדל מישהו לתפקיד - מוכשר ככל שיהיה. אני צריך מישהו שהוא כבר שם.
  • 10% Hands-On. ידיד הציע לי לא לכתוב את זה, כי אולי זה לא נשמע אטרקטיבי - אבל חשוב לי לשקף את המציאות:
    • אין כתיבת קוד כחלק מהתפקיד. אולי ייצא מדי פעם קצת ב POC, או איזה כלי, או לבצע איזה Pull request - אך זה חלק קטן למדי בפעילות.
    • אני מאמין בכל לבי שאדם שלא ילמד את שפות התכנות שאנחנו עובדים בהן (Go ורובי) ולא יוכל להתעמק בקריאת קוד בעת הצורך - לא יוכל לפרוח בתפקיד. הרבה פעמים התיאור מהמפתחים כיצד משהו עובד נשמע משהו אחד - ורק נבירה בקוד יכולה להסביר מה באמת מתרחש שם. הבנה עמוקה של שפת התכנות וסביבת הריצה הן לפעמים ההבדל בין עצה מעמיקה, לעצה לא-רלוונטית.
    • כלומר: נבירה בקוד היא כלי חשוב, ובהחלט חלק מהתפקיד. חשוב לי שהארכיטקטים בצוות ישארו ברמה הזו ולא יאבדו את היכולת הזו - ואנחנו משקיעים בזה.
  • מקצועיות טכנולוגיות גבוהה: רוחב אופקים (מכיר הרבה שיטות וטכנולוגיות), אך לא פחות נדרש עומק: יכולת לצלול לנושאים לעומק, גם נושאים סבוכים, ולהביא תובנות משמעותיות.
  • יכולת לעבוד בסביבה דינאמית ובשוק תחרותי מאוד. זה נשמע קלישאתי, אבל קצב ההתרחשויות בחברה הוא באמת גבוה מאוד. זו לא סביבה "קלה".
  • היכולת לפשט נושאים / רעיונות סבוכים לתיאור פשוט וממוקד - שקל לתקשר הלאה. יש לנו הרבה פרטים וידע במערכת - הדורשים זיקוק תמידי על מנת שהדמויות הרבות בפיתוח יוכלו לעקוב אחריהן.


זהו, פחות או יותר.

אם אתם מתאימים - אנא שלחו לי קורות חיים ל liorb[@]gett.com    (הפורמט נועד להקשות על ספאממרים הסורקים אחר כתובות אימייל)
אם אתם מכירים מישהו שעשוי להיות מתאים - אודה לכם אם תשתפו איתו את הפוסט!

נ.ב. - אם יש שאלות, אפשר לפרסם - כמובן.

תודה!