בתור מפתחי פרונט אנחנו מבצעים בקשות HTTP בתדירות יחסית גבוהה,
בקשות שמטרתן שליפת ועדכון בסיסי נתונים.
במקרים רבים, נבצע את אותה בקשה לשליפת נתונים שלא משתנים בתדירות גבוהה.
דרך זו פחות יעילה, מכבידה על האפליקציה ומגדילה את העומס על השרת בעקבות בקשות מרובות שלרוב שולפות את אותם הנתונים.
לכן, חיפשתי פתרונות יותר יעילים כמפתח פרונט ואנגולר בפרט.
מטמון / Cache, הוא נושא נפוץ מאוד בפיתוח Web.
אם מספר בקשות GET בתדירות גבוהה צפויות להחזיר את אותן התוצאות,
סביר להניח ששימוש בהליך של שמירת מטמון, caching, הוא מה שאתם צריכים לממש.
באנגולר אחת הדרכים למימוש data caching היא בעזרת ספריית RxJs.
ספריית Reactive Extensions for JavaScript או RxJs, היא ספריית JavaScript אשר משתמשת ב Observables בתכנות ריאקטיבי בעת עבודה עם קריאות דאטה אסינכרוניות, callbacks ותוכניות מבוססות אירועים.
ניתן לעבוד עם RxJs כאשר אנחנו מפתחים בפריימוורקס וספריות אחרות, היא לא מיועדת רק לאנגולר.
אנגולר החליטו לאמץ את הגישה הפונקציונלית לדאטה של RxJs.
ניתן להשתמש בפונקציות שRxJs מציעה לנו עבור;
- המרת קוד קיים עבור פעולות אסינכרוניות לObservables.
- איטרציה בין הערכים בזרם.
- מיפוי ערכים שמתקבלים לסוגים שונים.
- סינון זרמים, Streams.
- חיבור מספר זרמים.
שימו לב!
בדרך כלל, נעדיף לשמור מידע במטמון בבקאנד.
אחת הסיבות לכך, בפרונט המידע שנשמר יכול להשתנות על ידי המשתמש ולכן זוהי חולשת אבטחה פוטנציאלית.
עם זאת, ייתכן שלצוות פרונט כרגע אין גישה לשרת בבקאנד או שלמטמון יכול להיות פרק זמן קצר מדי.
במקרים כאלה, שמירה במטמון בפרונט היא הדרך המומלצת.
אז לענייננו,
בדרך כלל נבקש את אותה בקשת HTTP, בעת כניסה לקומפוננטה מסוימת באפליקציה, בין היתר לצורכי הצגת רשימות או מבני נתונים מסוימים בכדי לאפשר למשתמש לעבוד עם הנתונים באפליקציה.
פעולה זו מיותרת בעקבות העובדה שאנחנו יודעים שהתוצאה שמגיעה מהשרת לא תשתנה בקרוב וכך למעשה אני מעמיס על השרת ללא סיבה מוצדקת.
במקרה כזה אנחנו משתמשים בשרת בצורה לא יעילה.
בנוסף, אם הבקשה מחזירה תשובה כבדה הדורשת שליפה ממאגר נתונים גדול, היא עלולה להאיט משמעותית את ביצועי האפליקציה.
שמירת מידע במטמון עם shareReplay
עוד סיבה עבורנו לממש שמירת מידע מהשרת במטמון.
shareReplay הוא אחד מ- RxJs operators המחזיר את הערך האחרון שנפלט, emitted, כלומר אם ל Observable יש מנויים רבים, הערך הנפלט ישותף עם כל המאזינים לו.
ל- shareReplay כמה פרמטרים הניתנים לשינוי; configOrBufferSize, windowTime, scheduler.
Scheduler: מתזמן מתי יופעל האופרטור.
WindowTime: אורך הזמן המרבי שהפריט יוטמן ומוחזר באלפיות שניות, יח’ מילי שניות.
ConfigOrBufferSize: מספר הפריטים הטמונים והמוחזרים.
use case לדוגמא
יש לנו עמוד ראשי ו- עמוד המכיל רשימת משתמשים,
כאשר משתמש נכנס לקומפוננטת usersList , מתבצעת בקשת GET לכתובת מסוימת.
התוצאה הרצויה: שליפת 70 תוצאות המכילות פרטי משתמשים.
המתבקש הוא שנשמור את התוצאה במטמון לאחר הבקשה הראשונה ונשלוף אותה לאחר מכן על פי הצורך.
הסיבה לכך היא שאין צורך לבצע בקשת HTTP נוספת אם אנחנו משיגים את אותם הנתונים שוב ושוב.
בפעם הראשונה שמנוי נרשם ל- Observable המשתמש ב- shareReplay הקוד יתפקד כרגיל.
כלומר, הוא ייגש לסרביס המסוים ויבצע את בקשת HTTP הראשונה.
אך לאחר מכן לא יהיה צורך לקרוא שוב לסרביס ולבצע בקשה נוספת.
נבצע שימוש במידע שנשמר על ידי shareReplay ונשתף אותו עם החלקים המבקשים אותו.
@Injectable({providedIn: 'root')
export class UsersService {
private api = 'https://usersdata.com/';
private usersUrl = this.api + 'users';
public players$ = this.http.get<any[]>(this.usersUrl).pipe(
map((value: any) => {
return value?.data.map((user) => ({
...user,
userName: user.name,
}));
}),
shareReplay(1),
);
constructor(private http: HttpClient) {}
}
לאחר מכן, כאשר נעבור למסך הבית ונחזור לקומפוננטת usersList היא תקבל את הנתונים מהמטמון.
ניתן לבדוק שאכן בוצעה רק בקשה אחת בNetwork tab בדפדפן.
אבל מה עושים במקרה של שינוי כמו הוספה או מחיקת user?
כאשר נרצה לעדכן את המטמון נשתמש ב- BehaviorSubject כדי לעדכן נתונים שנשמרו במטמון.
@Injectable({ providedIn: 'root' })
export class UsersService {
private usersData$ = new BehaviorSubject<void>(undefined);
private api = 'https://usersdata.com/';
private usersUrl = this.api + 'users';
apiRequest$ = this.http.get<any[]>(this.usersUrl).pipe(
map((value: any) => {
return value?.data.map((user) => ({
...user,
userName: user.name,
date: Date.now().toFixed(),
}));
})
);
public users$ = this.usersData$.pipe(
mergeMap(() => this.apiRequest$),
shareReplay(1)
);
constructor(private http: HttpClient) {}
updateData() {
this.usersData$.next();
}
}
ואז, לייצר כפתור שקורא לפונקציה שמטרתה לעדכן את הנתונים והצגת נתונים מעודכנים.
לסיכום,
בעת קריאה לבקשות מרוחקות המחזירות נתונים שאינם משתנים או שמשתנים לעיתים לא תכופות, נשמור את אותם נתונים במטמון כך שנפחית קריאות מסוג זה.
כמובן שישנן עוד דרכים לממש שמירת נתונים במטמון ושליפתם.
במאמר זה כתבתי על דרך למימוש עם shareReplay operator של RxJs.
בדרך זו, אנו מייעלים משמעותית את מהירות התגובה של האפליקציה מה שמועיל גם לחוויית המשתמש.