8 בספטמבר 2019
Edit on GitHub

משתני סביבה וצד-לקוח

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

Blog picture

סקירה

שימוש במשתני סביבה (environment variables) זו דרך נהדרת להוצאת קונפיגורציה דינמית מהקוד, כך שבזמן ריצה/קומפילציה המידע יהיה נגיש, אך לא נצטרך לשמור את ערכו בקוד המקור.

דוגמה והסבר

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

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

איפה הבעיה?

היות והשרת והבסנת מופרדים, בכדי לגשת לבסנת מהשרת צריך את כתובת הבסנת ופרטי אימות, מה עושים?

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

  1. יום אחד נרצה לשתף את קוד המקור עם העולם או סתם מישהו אחר, נעלה את הקוד ל-Github או כל שירות דומה, והופ, כל אנשים הטובים יותר והטובים פחות יודעים מהן המפתחות של הבסנת שלנו. גם אם נספר לעצמנו שלעולם לא נפיץ את הקוד, עדיין אולי בטעות כן נחשוף אותו או נתחרט, והיו דברים מעולם.
  2. אם תתחלף הכתובת או פרטי האימות של הבסנת, נצטרך לעשות שינויים בקוד המקור. זה פשוט לא הגיוני, כי צורת העבודה של השרת לא השתנתה במאומה.
  3. לא ניתן להשתמש באותו קוד מקור לשרת הבדיקות ושרת הפרודקשן (המוצר), כי שניהם יפנו לאותו הבסנת.
  4. זה לא קוד, וגם אין סיבה שזה יהיה בקוד. זה גם לא קונפיגורציה (הגדרה) של אופן ריצת השרת (כמו למשל באיזה תדירות לעדכן את המטמון וכדו'), לכן גם אין סיבה שזה יהיה אפילו בקונפיגורציה.

הפתרון

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

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

  1. אפשר לשתף את השרת בראש שקט, אין סודות בקוד, כל מי שרוצה להריץ, שיריץ ויזין את הסודות שלו.
  2. אם יתחלף הבסנת כל מה שצריך זה לשנות את ערכו של משתנה הסביבה, אבל הקוד נשאר בדיוק אותו דבר, שום דבר לא שונה.
  3. בסביבת הפרודקשן נזין במשתנה הסביבה ערך המפנה אל הבסנת של הפרודקשן ובסביבת הבדיקות אותו משתנה יפנה אל הבסנת של הבדיקות, ועדיין הקוד הוא אותו קוד בדיוק.
  4. הקוד רק קורא את הנתון לצרכיו ובאמת לא הכנסנו לקוד הארד קודד נתונים שונים.

מגניב! אז איך פרונט-אנד קשור?

נניח שאנחנו רוצים לבנות עבור אותו שרת פרחים מהולל ממשק משתמש מודרני ונוח, מה נקרא בעולמנו "לבנות לו אתר", הלכנו כתבנו HTML + CSS + JS, יופי של GUI, ועכשיו אנחנו רוצים לשים את הקבצים הללו ב-CDN, כך שכל מי שייגש ל-פרחים.קום יקבל את האתר המופלא שכתבנו.

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

מה נעשה? נכתוב בקובץ ה-JS בקשת AJAX לשרת הפרחים (לא לשכוח לפתוח בשרת CORS, כמובן), והכל יעבוד יחד.

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

מה רע?

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

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

מה הפתרון?

ראשית חייבים לציין, שאם אין תהליך בילד אלא מה שאנחנו כותבים ב- HTML + CSS + JS זה מה שנותנים ללקוח, אני לא רואה דרך יפה לפתור את הבעיה.

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

הפתרון של Vue.js

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

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

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

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

הפתרון של Angular

באנגולר אין תמיכה מובנית במשתני מערכת. מה כן יש? יש קובץ שנקרא environment.ts שבתוכו מחזיקים את משתני הסביבה, וניתן להגדיר בבילד להחליף את הקובץ בקובץ אחר שבתוכו יש את אותם משתנים אבל עם ערכים שונים.

כך ניתן להגדיר משתנה production ולשים בתוכו false, ובבילד של פרודקשן להחליף בקובץ שבו ערכו של production הוא true. לקריאת הדוקומנטציה במלואה.

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

אז מה עושים?

הפתרון שאני עשיתי (ייתכן ויש טובים יותר) היה ליצור סקריפט js שיוצר את הקובץ environment.final.ts ואת הערכים הוא מזין לפי משתני הסביבה. וב-package.json תחת prebuild הגדרתי שהסקריפט ירוץ לפני כל קריאה לבילד.

סקריפט לדוגמה:

/** This script use to allow set API URL by system environment at build process time */
const fse = require('fs-extra');

const { API_URL } = process.env;
const defaultApiUrl = 'http://127.0.0.1:3000';

if (API_URL) {
    console.log(`API URL set to be ${API_URL}`);
} else {
    console.warn(`There is no 'API_URL' environment var, using ${defaultApiUrl} as default...`);
}

fse.outputFileSync('./src/environments/environment.final.ts', `
export const environment = {
    production: true,
    baseUrl: '${API_URL || defaultApiUrl}/API'
};
`);

וקובץ ה-package.json:

{
  "name": "my-app-name",
  "version": "0.0.0",
  "scripts": {
    "prebuild": "node ./create-final-environment.js",
    "build" : "ng build --prod"
  },
  "dependencies": {
  },
  "devDependencies": {
  }
}

כך שכל פעם שמריצים npm run build יורץ קודם הסקריפט, וב-angular.json שיניתי שבפרודקשן הוא יחליף את הקובץ environment.ts ב-environment.final.ts.

וב-gitignore הוספתי את הקובץ environment.final.ts כך שהוא לעולם לא יכנס לקוד המקור.

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

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

נ.ב.

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

System environment variables in Angular


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

Photo by Sarah Pflug from Burst