2020-05-16

הפילוסופיה של יוניקס

ראשית כל, בואו ונשאל: למה שתעניין אותנו הפילוסופיה של יוניקס? - פילוסופיה של חבורת אנשים מזוקנים משנות ה 60 וה 70 של המאה הקודמת?!

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

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

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

אז מה היא בדיוק הפילוסופיה של יוניקס?

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



קיצור (אגרסיבי) של הפילוסופיה של יוניקס


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

בכל מקרה, ממגוון הגרסאות, יש שני כללים שבולטים וזכורים יותר מכולם. נבחן אותם אחד אחרי השני:
Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new “features.”

מילות המפתח שצצות כאן הן: מודולריות, פשטות, Single Responsibility, העקרון הפתוח-סגור, ו Lean.

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

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

אבל....

מכיוון שכל אפליקציה היא קטנה פשוטה ועושה בדיוק דבר אחד - לא ניתן לבנות פונקציוליות עשירה ומורכבת, ללא הכלל השני:
Expect the output of every program to become the input to another, as yet unknown, program.
פונקציונליות עשירה יותר, מורכבת ע״י ״הרכבה״ של אפליקציות פשוטות בזו על זו.

cat foo.txt | wc -l

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

אנו משתמשים מכאן ב Pipe (אם אתם לא יודעים מה זה pipe - לכו מיד וקראו!), על מנת להעביר את הפלט לאפליקציה אחרת, wc (קיצור של word count [א]) ורק היא תשלח את התוצאה שלה ל standard output.

תכנון ה pipe ע״י Doug McIlroy בשנת 1964. קצר, פשוט, ענייני.


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

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

ה Pipe מאפשר הרכבה גמישה בין מרחב גדול כל-כך של אפליציות, ומאפשר ל Shell של יוניקס לבצע מגוון אדיר של פעולות פשוטות ומורכבות. ההצלחה של ה Pipe נובעת מתוך הגדרה של ממשק כללי ופשוט להחריד:
  • שטף של בתים (לא ביטים), בד״כ ב ASCII. כלומר: טקסט.
  • אפשר שיהיה לו סוף - בדמות EOF.
  • n\ סימן להפרדה בין רשומות (או ״שורות״)
  • +[t\ ] (כמות כלשהי של טאבים או רווחים) - הפרדה אפשרית בין שדות.

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

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

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

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

פשטות אמיתית דורשת המון משמעת, כמו שנאמר: ״Simplicity is the ultimate sophistication״.



סיכום 


אז מה היא הפילוסופיה של יוניקס, ולמה היא חשובה?

הפילוסופיה מקדמת, מיישמת, ומדגימה סדרה ארוכה של עקרונות חשובים בהנדסת תוכנה. זה כבר ערך בפני עצמו. קראו את הפסקה הבאה:
״Build a prototype as soon as possible. Most users looking for a new piece of software won't know what they really want until they see it, so requirements documents are often misleading about the users' real needs. The Unix design philosophy makes prototyping a central part of the methodology: give the user something, anything, up-front to criticise, and work from there.״
אתם מאמינים שהיא נכתבה במיליניום הקודם? אז מי המציא את ה Lean-Startup? אריק ריס או מפתחי היוניקס?!

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

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

לפני כעשור ועוד עבדתי עם מודלים אחרים של רכיבי UI, בעיקר Java Portlets או כל מיני תקנים של widgets ו gadgets אחרים. הממשק היה כמעט תמיד מורכב בהרבה - והקשה מאוד על קהילה רחבה לתרום למודלים הללו. זה לא היה פשוט.

כאשר אני מתכנן רכיבים ב React/Angular אני עדיין יכול לעשות אותם גדולים ומורכבים (״רכיב Alpha 16 יספק את כל צורכי המערכת בטבלאות חיפוש, לכל האובייקטים, בשנים הקרובות״) או קטנים ופשוטים.

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


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


----

[א] הערת אגב: עם הכבוד הרב שאני רוחש לפילוסופיה של יוניקס - אני עדיין מאמין ששמות מאוד קצרים הם טעות גדולה של יוניקס שמחירה מודגם שוב ושוב. שמות מאוד קצרים קשה יותר לזכור, וקל מאוד לבלבל בינהם / לפספס את המשמעות.
יוניקס הייתה טיפה מוצלחת יותר אם ל cat היו קוראים concat, ול wc היו קוראים wordcount (או פשוט count - מכיוון שיש לה הרבה אופציות ספירה שונות), ול man היו קוראים manual. הקנאות לקיצור הרגה את הקריאות ביוניקס.


----

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

סיכום החוקים של הפילוסופיה של יוניקס. אפשר לקרוא ולהנות.

כמה מילים על הפילוסופיה של יוניקס מספר מ 1995. מופיע בוויקי המקורי - תמציתי ומעניין.


2020-04-15

תכנון נכון של API

תכנון API הוא מלאכה עתיקה ולא-מוסדרת. כל מהנדסי-התוכנה מתכננים (או פשוט מיישמים) APIs. מחתימה של פונקציה, עד ל Public APIs בפרופיל גבוה ("Public APIs, like diamonds, are forever״).

אם הייתי קורא כזה פוסט, הייתי רוצה שלא יעסוק ב:
  • כללי ה REST. דיי נמאס! אחרי עשור ויותר, אפשר להניח שכל מפתח מכיר אותם היטב, או לפחות יש מספיק חומר סביר בווב (כולל הבלוג הזה) בכדי לכסות את הנושא לעייפה.
    • במקום העבודה הנוכחי שלי, ירדנו מ REST - לטובת Json over HTTP. זה היה לי קצת מוזר בהתחלה - אבל היום זה נראה לי כמו צעד נבון. אפרט קצת בסוף הפוסט.
  • GraphQL או Falcor = עוד באזז / טכניקה. כמו REST יש כאן שורה של כלים וכללים שאפשר ללמוד אותם - אבל האם זה באמת החלק החשוב ב APIs טובים יותר?!
  • כללים גנריים ובסיסיים ״צרו תעוד ל API״ , ״בדקו את ה API״, ״בחרו שמות טובים ל API״. אני מניח שאתם לא צריכים את הבלוג הזה בכדי למצוא את כללי היסוד של ההיגיון הבריא.

אז זהו. לא אעסוק בכל הנ״ל. הרשת גדולה - ואני אשאיר לאחרים לדוש בנושאים הנ״ל. עם כל החומר על ה API שיש ברשת (בעיקר: Rest, Rest, ו GraphQL) - נשארנו עם הרבה טכניקה, ומעט נשמה.

אם אתם גיקים אמיתיים - אתם בוודאי מאמינים בלב שלם שהטכניקה מספיקה. ש < שם של סגנון טכני של APIs [א] > הוא הפתרון השלם והטוב ביותר. אם תקפידו על הכללים הטכניים - יהיה לכם API מצוין! אולי אפילו תריצו כלי Lint שייתן לכם ציון 100. מצוין!

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


A Developer in the process of integrating a 3rd Party API -- Caravaggio, 1594


איך מתכננים API טוב?


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

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



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

טעות ראשונה שאני רואה שחוזרת על עצמה היא API שעולם המונחים שלו משרת את היצרן (System A) - ולא את הלקוח.

בואו נראה צמד פעולות לדוגמה:

SimulatePurchaseWithoutSaving(...) --> simulationId
ApplyAndSave(simulationId, ...) 

SimulatePurchaseWithoutSaving הוא דוגמה טובה לשם של פעולה ״בראש״ של היצרן.
את הלקוח לא מעניין מה אני שומר ומה לא.
האם מעניין אותו לבצע סימולציה?

מנקודת המבט של הלקוח כנראה שעדיף

PurchaseABC(...) --> purchaseId
ConfirmPurchase(purchaseId, ...)

חשוב מאוד שה API ישקף את האינטרס של הלקוח. מדוע הוא קורא ל API הזה בכלל? מה הוא רוצה להשיג?
חשוב להשתמש במונחים שרלוונטיים אליו, ולהימנע מכל מה שלא (למשל: האם אנחנו שומרים משהו או לא = אינו רלוונטי ללקוח).
מה יותר הגיוני? API בשם receiveEmailAndPassword או API בשם login?

כלל חשוב לזכור:
FOCUSING ON HOW THINGS WORK LEADS TO COMPLICATED INTERFACES

עדיין לא ראיתי מישהו שקרא ל Login ״שלח שם משתמש וסיסמה״ - אבל ראיתי דוגמאות רבות שקולות לה. קצת הגזמתי - בכדי להדגיש את העיקרון.
בעיה נוספת: "receive" הוא שם פעולה סבילה מצד היצרן של ה API. זה לא מונח או נקודת ייחוס שצריכה להיות חלק מ API.

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



Leaking Abstractions


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

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

כאן גם הבעיה. העולם משתנה, הביזנס משתנה --> ולכן גם המערכת שלנו צריכה להשתנות.
שינויים רבים הם extensions - להוסיף עוד למערכת, שינויים שלא ״שוברים״ APIs.

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

אבל מה עם API פנימי? האם גם שם צריך להיזהר? בוודאי שכן!

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

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

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

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

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

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

טכנולוגיות מגניבות כמו GraphQL לעתים מקלות על זליגת ההפשטות. המותג החזק ("GraphQL", ווהאו! פייסבוק! גוגל! זום!) מפחית את ההגנות שלנו בפני מה שעשוי להתהוות של ״סטטוס קוו״ שלא נצליח לעולם לשנות אותו.

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


הבה נתייחס לתסריט הייחוס הבא:


המערכת (או מיקרו-שירות) שלנו היא מערכת A, ואנו חושפים את API a ללקוח כלשהו.
על מנת לספק את הבקשה, עלינו לפנות ל API b של מערכת B.

  • אל תחשפו ב API a רכיבים / אובייקטים מ API b. אתם קושרים את הלקוחות של Service A גם ל Service B - מה שיקשה מאוד על Service B להשתנות לאורך הזמן.
    • שכפלו אובייקטים. אובייקט Customer של API a יכול להיות זהה לחלוטין לאובייקט Customer של API b - וגם לדרוש העתקה. זו תקורה - אבל היא משתלמת לאורך זמן. כאשר API b ירצה להשתנות - הוא יכול, ורק יהיה צריך לשנות את לוגיקת ההעתקה בין האובייקטים.
  • אל תחשפו אובייקטים שלמים / עשירים מדי. יש משהו מאוד נוח, אך הרסני, בחשיפת API של קריאה / עדכון של אובייקטים שלמים של המערכת. האחריות של המערכת על האובייקטים שלה - פשוט אובדת.
    כאשר המערכת שלכם תצטרך לשנות את האובייקטים הללו בכדי לתאר התפתחות במערכת - זה יהיה קשה מאוד, ואולי בלתי אפשרי: שימושים שונים ותלויות מול האובייקטים הללו התפתחו בשאר המערכת - סוג של ״עבודה לא מתוכננת״, ואולי מאוד משמעותית - שנוספה לשינוי פשוט של API.
    • מדוע גוף האדם לא חושף את האיברים הפנימיים לעולם החיצון, שלא דרך מנגנונים מבוקרים? אולי אפשר ללמוד משהו מהטבע על תכנון מערכות מוצלחות.
    • צרו אובייקט נתונים עבור ה API (מה שנקרא גם DTO) ובצעו העתקה פשוטה בין האובייקט הפנימי (שיוכל להשתנות) לזה שאתם כובלים את עצמכם אליו לזמן בלתי-נשלט. זו השקעה טובה לטווח ארוך.
      • העתקה גם חשובה לצורך Immutability. במיוחד ב API של קוד באותה המכונה - אתם לא רוצים שמישהו יקבל אובייקט פנימי ואז ישנה לכם אותו.
    • חשפו באובייקט של ה API רק מה שהלקוח צריך. אל תהיו ״נדיבים מדיי״. צריך בכלל הזה גם לא להגזים:
      • אפשר לשלוח אובייקט עם 6 שדות - גם אם הלקוח זקוק רק ל 2, כל עוד אלו 4 שדות שהיה הגיוני לשלוח, לו הלקוח היה מבקש.
      • אפשר לשתף אובייקטים בין APIs שונים בתוך Service A. פיצול אובייקטים בתוך אותו שירות - הוא לא שינוי קשה מדי.
      • הגזמה ברמת הדיוק של ״לשלוח בדיוק מה שהלקוח צריך״ - תגרום לעיכובים בהתפתחות המערכת, דווקא מתוך התגוננות יתר. גם זה לא מצב טוב.
  • זכרו ש API הוא חוזה מחייב -- אבל זה לא חוזה שמכסה את כל הפרטים. ה compiler יצעק עלינו אם נשנה טיפוס או נסיר שדה מה API. הוא לא יצעק אם נזרוק Exception במקום שלא זרקנו בעבר או נחזיר מרחב ערכים שונה (גדול?) מזה שהחזרנו בעבר. כלומר: יש שדה מסוג String - אבל התחלנו להחזיר ערכים חדשים שהלקוחות לא יודעים להתמודד איתם.
    • כדי לוודא שה API לא משתנה ופוגע בלקוחות שלכם - צרו Automated Tests לבדוק את האלמנטים ב״חוזה״ שהקומפיילר לא יודע לתפוס.
    • התאימו את כמות הבדיקות והיסודיות שלהם - לכמות הלקוחות / חשיבות ה API. בדיקה שלעולם לא תכשל - היא בזבוז זמן לכתיבה. אנו עוסקים בניהול סיכונים.



כמה דילמות נפוצות


API אחד המחזיר היררכיה גדולה של אובייקטים - מול API לכל אובייקט קטן בהיררכיה?

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

כמובן שיש פה Tradeoff: אם מדובר ב API לצורך monitoring או בדיקות - אז חלוקה ל API קטנים יהיה בזבוז זמן. חשוב להבין כמה שינוי אפשרי אתם מסכנים, תמורת כמה חסכון בזמן בפיצול ה API.

אם מדובר ב API בין Front-End ל Back-End ואתם מאחדים לאובייקטים גדולים כדי לחסוך latency של קריאות ברשת - אז עשו את החיבור ברמה של API Gateway - ובטח לא ברמת ה API של ה Back-End.



״למתוח״ API כדי להתאימו לצרכים, מול יצירה של גרסה חדשה?

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


מתי לזרוק Exception מ API? מתי להחזיר ״אובייקט כישלון״?

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


יש לי שני לקוחות ל API - כל אחד צריך מידע מעט אחר. האם להוסיף את הלקוח כפרמטר ל API או להגדיר שני APIs שונים?

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

אני לא אוהב את הפתרון של ״פרמטר עם מזהה הלקוח״ כי הוא קושר בין שני שימושים שונים זה-לזה. אני מעדיף 2 endpoints שונים, ואם הם עושים שימוש חוזר בקוד פנימי - אדרבא.

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


למה ב Next-Insurance בחרנו לא להשתמש ב REST?

ב Next-Insurance ניסינו (כמו רבים אחרים) להיצמד לכללי ה REST בכדי ליצור אחידות ב APIs הפנימיים, ולהפוך אותם לצפויים יותר לשימוש. ל REST יש כמה בעיות ידועות כמו המגבלה להעביר payload גדול על קריאת get, או ההגדרות הלא מדויקות והניתנות לפרשנות של REST כרעיון (מעולם לא היה תקן מסודר, ומעולם לא היו פיתוחים מוסכמים ל REST מעבר למסמך הראשוני). למשל: קשה מאוד להתיישר על סכמת URLs עקבית בקבוצה גדולה של אנשים. לאנשים רבים יש פרשנויות רבות כיצד URL שהוא RESTful צריך להיראות.

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

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

מפה לשם, עברנו ל JSON over HTTP בתצורה מסוימת. תמיד משתמשים ב HTTP POST ותמיד ה API מגדיר אובייקט (DTO) בקשה ואובייקט תשובה שעליו ישבו הפרמטרים. ה URL הוא רצף מילים שקשור לפעולה, לפעמים RESTFul - אבל לא בהכרח.

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

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


עוד כמה קלישאות (נכונות):
  • API צריך להיות קל לשימוש, וקשה לשימוש לא-נכון.
  • בכדי לעצב API לא צריך ניירת או תהליכי חשיבה ארוכים. צרו טיוטא מהירה של ה API וכתבו קוד שמשתמש ב API הזה. זו הדרך הטובה לחשוב וללטש את ה API.
  • צייתו ל Principle Of Least Astonishment (בקיצור: POLA). 
    • ה API לא צריך להפתיע את המשתמש. ככל שהמשתמש של ה API רחוק מכם יותר - הכלל הופך לחשוב אפילו יותר.
  • השתמשו ב APIs בתבניות (Format) הנוח ללקוח, ולא למערכת. יש מין טעות כזו ש API צריך להיות נוח ליצרן ולא ללקוח.
    • אולי אתם מייצגים זמן ב Java Epoch, אבל ללקוח יהיה הרבה יותר קל לעבוד בתאריכים קריאים, קרי 2020-04-19.
  • API הוא רכיב קריטי באבטחת המערכת. זה זמן טוב להזכיר שיש OWASP Top 10 ל APIs.


סיכום


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

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


----

[א] סגנונות API נפוצים הם Falcor, gRPC, SOAP, RPC, GraphQL, REST או סתם JSON over HTTP.

----

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

Public APIs, like diamonds, are forever