מסעותיו של מתכנת משועמם בעולם הפלא של JavaScript , ממלכתה של המלכה ECMA, בעקבות תובנות חדשות וכוס קפה אחד. או שלוש. עם עוגיה בצד.
מחילת הארנב – היכרות עם JavaScript Proxy
כמפתחי Front End אנחנו נדרשים לעמוד בקצב המשוגע של השנים האחרונות. JavaScript מתפוצצת מחידושים מכל הכיוונים: הסטנדרט עצמו קיבל בוסט ב 3 שנים האחרונות מאז שוחררה ECMAScript 2015 או בשמה הסקסי es6 (והמהדרין ירשמו 6ES), כששנים לפני כן נוצרו מלאנתלפים ספריות וסביבות עבודה כמו פיטריות אחרי הגשם, כשהפיטריה הגדולה שבהן היא כמובן Nodejs הגאונית, עליה יושב הזחל-מלך, מעלה עשן מהנרגילה בטעם npm. והעשן הזה ממלא כמעט את כל החלל בממלכת JavaScript, בואכה es9.
הגעתי למשרד מוקדם בבוקר בעשר, וכמו בכל תחילת יום עבודה, בנוהל, הפסקה ראשונה. ניגשתי לקפיטריה. קפה ונשנוש. חזרתי לקיוביק. יאללה עבודה!
אבל לפני כן הפסקה שנייה – הלכתי לשירותים.
משהו מוזר קרה לי באותו בוקר, כי איך שפתחתי את הדלת נשאבתי דרך מחילת ארנב.
הגעתי לעולם מלא קסם. אני אוהב הפסקות קסומות כאלה.
למרות שהקפה שלי מתקרר ולא סיימתי את העוגיה, התחלתי להלך בין העצים, משחק בשיער ומזמזם לי נעימה בינארית נחמדה. מבין שלל הדברים המוזרים שנתקלתי בהם צד את עיני ה Proxy. לידו היה רשום שיר קטן:
"הצמד אותי לאחר, ואתנהג בהתאם אליו,
למד אותי פעולות, ותוכל לשלוט בעולם"
אז בלהלהלנד, Proxy זה שיר קטן ומעצבן.
היה שם כפתור בצבע #0f0 שרשום עליו: "בוא תגיד שלום ל Proxy". איש ה QA שבי רצה לבדוק אם ה event handler תקין, אז לחצתי.
משומקום הגיע מאחורי קרון רכבת קטן. התיישבתי. כמו בסיור מודרך במוזיאון, הקרון התחיל לזוז ולרחף לו מעל הקרקע.
פתאום שמעתי סביבי קול נעים:
"שלום שחר גיבור. לי קוראים Sima, ואני החמות של Siri. אני רואה שמכל העולם הקסום שסביבך התעניינת דווקא ב Proxy. נו, שוין:
"Proxy זאת גישת עיצוב תוכנה המתארת מצב שבו לקוח מקבל גישה למשאב דרך מיופה כח, הפרוקסי. הפרוקסי אמור לחקות במדוייק את ממשק המשאב, ובכך הלקוח מתנהל מול הפרוקסי כאילו זה המשאב האמיתי."
הקרון נכנס ליער השרתים. Sima המשיכה:
"ישנם שימושים מוכרים בפרוקסי שהמפורסמים שבהם הם שרתי פרוקסי.
יש לא מעט דוגמאות לשימושים בשרת פרוקסי, הנה כמה מוכרות:
– כשרת ביניים מול השרת האמיתי שמוגן תחת חומת אש (Firewall)
– כמאזן עומס (Load Balancer), שבו שרת הפרוקסי מנתב בקשות ל 2 שרתים או יותר לפי העומס
– כשרת אחסון מהיר (Caching) לשיפור ביצועים והורדת עומס מהשרת
"אז אתה מבין? הלקוח אינו מודע שהוא מתנהל מול שרת הפרוקסי, ושרת הפרוקסי מוסיף שכבה אל מול השרת האמיתי.
"שימוש ראשוני בפרוקסי יכול להיות כזה שבו הפרוקסי פשוט מעביר את הבקשות מהלקוח ישירות למשאב."
מיד שאלתי את סימה: למה לעשות את זה בכלל? למה לטרוח וליצור העתק מדוייק של המשאב רק כדי לשמש כצינור בין הלקוח למשאב?
סימה ענתה:
"אתן לך דוגמא ופתרון אפשרי בעזרת פרוקסי:
נניח שבנית מודול שאחראי על הגישה לבסיס הנתונים. במודול חשפת ממשק שדרכו אתה יכול להוסיף רשומות חדשות, לעדכן קיימות, לבצע שאילתות וכו'."
עניתי אוקי, סבבה, נו?
סימה: "לאחר כמה זמן אתם נדרשים לשמור לוגים לפעולות מסויימות."
קפצתי: זה קל, פשוט אוסיף למודול קוד מתאים במקומות הנחוצים ו…
סימה התפרצה לדברי בחוסר סבלנות (שזה מוזר כשלעצמו): "לאחר כמה זמן אתה נדרש לשמור תוצאות מסויימות (Caching) ולהבא להחזיר אותן מהזיכרון, במידה וישנן."
גם זה לא בשמיים – חייכתי מתחת לשפם – רושם קוד בעצמי או משתמש בסיפריית עזר כמו Redis, ובמקומות הנחוצים במודול שלי מבצעים קאשינג.
וכך סימה התקילה ואני עניתי וזה המשיך והמשיך והמודול שלי גדל וגדל, וכבר מזמן הוא לא אחראי רק על בסיס הנתונים, אלא גם מתי ואיך לשמור לוגים, לבצע קאשינג ועוד.
בכיתי.
סימה ליטפה בקול אמהי כמו מלמדת ילד לקח:
"אחד מכללי האצבע בעיצוב תוכנה בריא הוא אחריות אחת (Single Responsibility) שממליץ לשמור על יחידות תוכנה כבעלות אחריות אחת בלבד.
יחידת תוכנה יכולה להיות פונקציה, מחלקה או מודול, שזה בעצם כמעט הכל .
"במקרה שלנו, למודול בסיס הנתונים המגניב שלך יש כמה תחומי אחריות ולא אחת."
יבבתי מבושה וסיננתי ש:
יותר מזה, מה יקרה (מקנח את האף) אם המודול הכבר לא כ"כ מגניב שלי נמצא בשימוש בכמה סביבות שונות, למשל סביבת פיתוח ופרודקשן?
קולי נשבר: ונגיד שבסביבת הפיתוח לא צריך קאשינג ושהלוגר בפרודקשן שולח גם אימיילים?
אז אצטרך להוסיף למודול, הכבר ממש לא ככ מגניב שלי, עוד לוגיקה שתטפל בקינפוגים הנ"ל??
סימה סיכמה:
"אז מה קיבלנו? מודול שהתחיל תמים ונהיה תמנון רב זרועות."
סימה העלתה הילוך בקול מאנפף:
"אולי אני לא מחדשת לך, כי יש מצב שכבר נתקלת בסיטואציה דומה, ומצאת פתרונות כאלה ואחרים. יש בהחלט מצב שעיצבת פתרון פרוקסי-לייק, מבלי להכיר מה זה פרוקסי!"
הקרון המוזר עצר ליד דלת שרשום עליה מעבדה. כמובן שהיא נפתחה מאליה, ואני ריחפתי ישירות דרכה והושבתי על כסא. שולחן נצמד אלי, קוסם IT הגיע והניח עליו מצע של SSD, פיזר המון דובדבני CPU כאלה למעלה וסגר הכל בעטיפה חדשנית שכזו.
בקיצור קיבלתי מחשב.
סימה אמרה שהמעבדה מתחילה, והיא, בכוח המחשבה בלבד תרשום לי על המסך (מטורף!).
ואז מחאה כף וירטואלית פעמיים.
המסך נדלק. גם סימה, שנכנסה לקצב:
מבין שלל החידושים ש es6 הביאה עימה נמצא אוצר קטן שנקרא, לא תאמין – פרוקסי!
והוא מתנהג בדיוק כמו ששמו מציין: חקיין/מעטפת של ממשק המשאב. אם נשתמש בו בצורתו הכי בסיסית הוא לא יעשה דבר אלא להעביר אחריות למשאב.
הבנאי של פרוקסי מקבל את אובייקט המטרה ואובייקט נוסף שנקרא handler, החקיין או המעטפת. על המסך הופיע:
סימה, תגידי, זוהי פעולת ברירת המחדל של הפרוקסי?
"יאפ, ענתה, אם לא מוגדרת פעולת חיקוי או 'מלכודת' (trap), הפעולה מתבצעת ישירות על אובייקט המטרה.
"זוכר את השיר ממקודם? השורה הראשונה אמרה 'הצמד אותי לאחר, ואתנהג בהתאם אליו…' שזה בדיוק מה שראינו."
סימה צהלה והמשיכה: "השורה השנייה היא המעניינת – '… למד אותי פעולות, ותוכל לשלוט בעולם' ".
אה סימה, סליחה, אני יודע שאת בהלתהבות, אבל אני צריך לצאת להפסקת צהריים, הסיבוס שלי לא יהיה תקף בקרוב.
סתם! למדי אותי סימה! למדי!
סימה היא מכונה, לכן לא הבינה את הקטע והמשיכה: "אז מה ניתן להגדיר בתוך ה handler? פעולות על אובייקט המטרה שניתן 'לתפוס' לפני כן בפרוקסי ולהגיב אחרת."
הנה הממשק של handler כפי שהועתק מ lib.es2015.proxy.d.ts:
אוקי סימה'לה, אלה האפשרויות, הבנתי בערך.
אז הנה לך דוגמה ראשונה: בא נייצר מלכודת בכל פעם שמופעל getter על אובייקט המטרה.
"אתה מבין, זו אופציה מצויינת לבצע עיבוד כראות עינך לפני שאתה מחזיר תשובה ללקוח."
אמרתי: נכון, אני כבר רואה מקום טוב להכניס בו את הלוגר. ומה הקטע של ה underscore? אני רואה אותו לא מעט ב node ובעוד מקומות.
סימה הסבירה לי ש: "ה _a משמש כמעין קונבנציה ל private member כי כרגע אין ברירת מחדל למשתנים פרטיים ב JS. לכן אתה, כמפתח שנתקל ב obj._prop, מבין שזה פרטי לאובייקט ואמור להעדיף שימוש ב public interface שלו.
כדוגמא זריזה, אתה מכיר ב node את Stream. ל Transform למשל ישנה תכונה שנקראת _transform. אינך אמור להשתמש בה, אלא אם כן אתה יורש/מממש Transform Stream
(https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback)"
הבנתי סימוש, אמרתי, ככה היכרתי Closures בזמנו.
סימה הוסיפה ואמרה שהמלכה ECMA יצרה בזמנו את Closure – הוא הוא הקוסם מהצפון, ושהמלכה מבשלת כרגע בטיגון עמוק יכולת מובנית שכזו (https://github.com/tc39/proposal-class-fields#private-fields).
"אז בא תראה איך עם שימוש בפרוקסי אני (כמעט) מונעת מלקבל property "פרטי":"
המממ… למה רק כמעט? שאלתי
סימה ענתה בשאלה: "האם יש עוד דרכים להגיע ל property?"
אה! רגע! מה עם Object.keys? או Object.has ונורה נדלקה מעל ראשי. נורה חסכונית.
עוד רגע! גם for-in יגלה את "הנסתר"…. שישמור אותנו האל!
סימה חיכתה שאנשום והשיבה:
"צודק, זהו לא הפתרון המלא שיסתיר את האפשרות להגיע ל _a ותצטרך להוסיף עוד כמה מלכודות לפי הצורך כדי להבטיח הסתרה מושלמת. למשל, אפשר להוסיף מלכודת ל has ככה:"
"וכך למנוע שימוש באופרטור in
אז כמו שאמרתי, היכולת להסתיר property שכזה, שבשפת אחרות זה מובן מאליו, כרגע בבישול בתנור, או על הגז, ובקרוב יהיה חלק מהסטנדרט."
היא מיד הראתה לי שגם ה setter די דומה, וגם כאן הדמיון שלי משתולל
סימה, יש קפה? (שמתי לב לעמדת הקפה בפינה של הכובען המוטרף).
וסימה בשלה: "אתה בכלל שמת לב לאפשרות האחרונה של ה handler? ה construct?"
הסתכלתי אחורה למסך כי בדיוק עירבבתי את המשקה. שחור בלי סוכר דרך אגב.
והיא בהתלהבות: "ניתן למלכד את יצירת אובייקט המטרה!"
"אה תשמע מר בחור, הביקור כמעט נגמר, מחילת הארנב נסגרת ב 16:00.
"בא אראה לך שימוש מעניין כדי שתחזור לכאן לביקור נוסף, כי אין כמו לקוח חוזר.
"מכיר את Factory Pattern? אולי נתקלת בו כשעברת ליד מפעל השוקולד של צ'ארלי.
"Factory Pattern משמש כמקום מרכזי ליצירת אובייקטים לפי דרישה. לבקשת הלקוח, תחזיר מופע קונקרטי ע"י שימוש בבנאי של מחלקה או מימוש קונקרטי של ממשק."
המממ… טבלתי לוטוס בקפה, אני מכיר את זה שהשליטה ביצירת אובייקטים נמצאת במקום אחר, ואני, בתור אחד שצריך מופע של מחלקה מסויימת, לא צריך להתעסק באיך לייצר את האובייקט, כי לפעמים זה יכול להיות עסק מורכב.
סימה מחאה כף וירטואלית ופלטה: "יאפ!" היא אמרה:
"כאן נוכל להשתמש בפרוקסי כמעטפת ליצירת אובייקטים. הלקוח לא ירגיש בהבדל, אך אתה יכול לייצר מלכודות לפי דרישה."
היא לחשה את קסם ה cls והמסך התנקה. התחילה הדוגמא אחרונה. היא הסבירה:
"בדוגמא הבאה יצרתי מודול פשוט שחושף 2 פונקציות: האחת ליצירת Person, השנייה ליצירת Car."
סימה כחכחה בגרונה כמו מתנצלת: "ניתן לרשום קוד נקי יותר, אך הדוגמא נועדה להתרשמות ראשונית, אז אל תעקם פרצוף ותתרכז בעיקר."
"מלכודת ראשונה נועדה לתפוס יצירת אובייקט, מלכודת שנייה לתפיסת getter."
בסוף הדוגמא כבר הייתי אחרי עוד כוס קפה. הלוטוס דורש את זה.
סימה סגרה את המסך ואמרה לי שאני צריך לזוז, היא עוד צריכה להרים כסאות ולנקות קצת. נתנה לי עוגיית מזל ועליתי בחזרה על הקרונית.
בדרך חזרה חשבתי על זה שהסקירה הראשונית הזו שפכה לי קצת אור על Proxy בפרט ועל אפשרויות השימוש בו בכלל.
פתחתי את עוגית המזל ובתוכה ראיתי דף קטן שעליו רשום: תוכל לקרוא על אפשרויות הhandler כאן
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler
יצאתי ממחילת הארנב, והופ אני בחזרה בשירותים. הידחתי את המים, והתיישבתי מול המסך בקיוביק.
טוב, יאללה, הפסקת קפה. אני צריך לחשוב על זה…