ch
Feedback
ToCode

ToCode

前往频道在 Telegram

טיפים קצרים למתכנתים מאת ינון פרק

显示更多
1 420
订阅者
无数据24 小时
+27
-230
帖子存档
ToCode
1 420
# היום למדתי: פרופילים ב docker compose מה קורה אם אתם צריכים להפעיל גם משימה חד פעמית בפרויקט שמשתמש ב docker compose? טוב דרך אחת היא להתחבר לקונטיינר שכבר מריץ את היישום ולהפעיל את המשימה מתוכו. לדוגמה נניח שיש לי את התוכן הבא ב docker-compose.yml:
version: "3.9"
services:
  backend:
    image: backend

  db:
    image: mysql
ויש לי סקריפט בשם myapp migrate שאני יכול להפעיל רק מתוך האימג' של backend, אז אני יכול להעלות את הסרביסים וכשצריך להפעיל את הסקריפט:
$ docker compose up -d
$ docker compose run backend bash -c "myapp migrate"
אבל זה עקום כי אולי הפעלה של backend לוקחת הרבה זמן או שאני צריך שהסקריפט ירוץ כשהיישום שלי למטה או לפני שהיישום עולה. הייתי מעדיף לכתוב סרביס עבור הסקריפט ב docker-compose.yml שיכלול גם את הפקודה ויאפשר להריץ את הסקריפט עם הפרמטרים שאני צריך והאימג' הרלוונטי:
version: "3.9"
services:
  backend:
    image: backend

  db:
    image: mysql
    
  migrate:
    image: backend
    command: myapp migrate
אבל עכשיו יש לי בעיה חדשה - הסקריפט ירוץ כל פעם שאני כותב docker compose up, ובשביל למנוע ממנו לרוץ אני צריך להעביר במיוחד את רשימת כל הסרביסים האחרים ל docker compose up. מסתבר שיש פיתרון פשוט ומובנה ב docker compose שנקרא פרופילים: אנחנו מציינים לכל סרביס לאיזה פרופיל הוא שייך. סרביסים שלא שייכים לאף פרופיל יעלו כשאני כותב docker compose up, וסרביסים אחרים יופעלו כשאני מפעיל את הפרופיל או כשאני מפעיל אותם בצורה יזומה. במקרה של הסקריפט שלי זה אומר לעבור ל docker-compose.yml הבא:
version: "3.9"
services:
  backend:
    image: backend

  db:
    image: mysql

  migrate:
    image: backend
    command: myapp migrate
    profiles: ["tools"]
עכשיו אני יכול להפעיל docker compose up כדי להפעיל את המערכת הרגילה, או docker compose run migrate כדי להפעיל את סקריפט המיגרציה.

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

ToCode
1 420
2. קבצי Slice תלויים בקבצי Action. 3. קובץ ה Store תלוי בקבצי ה Slice ואולי בקבצי ה Action. 4. קבצי Selectors תלויים בקובץ ה Store. 4. קבצי ה Thunk תלויים בקובץ ה Store וגם הרבה פעמים ב Selectors. בשביל זה רוב הזמן לא נרצה להגדיר את ה Actions שלנו בתוך ה Reducers, אלא להשתמש במפתח extraReducers לדוגמה:
import addTodo from "./addTodo";

extraReducers: (build) => {
  build.addCase(addTodo, (state, action) => {
    state.push({ id: nanoid(), message: action.payload, completed: false });
  });
}
והתלות עכשיו היא לפי ההיררכיה שקבענו - סלייס מייבא Action Creator. סלייסים נוספים או Async Thunks יוכלו גם לייבא את addTodo בלי ליצור מעגליות. לסיכום אם אתם עובדים עם Redux Toolkit אלה הנקודות שצריך לשים אליהן לב במיוחד: 1. תלויות בין סלייסים - כשסלייסים מתחילים לייבא Actions שנוצרו בסלייסים אחרים מהר מאוד נגיע למעגלים של סלייסים. הפיתרון יהיה תמיד להוציא את ה Action Creators לקבצים נפרדים ולוודא שהם לא תלויים בשום רכיב אחר. 2. תלויות בין Thunks ל Action Creators - הקפידו להפריד את ה Thunks וה Action Creators לקבצים שונים. ה Action Creators הרגילים לא תלויים בכלום, אבל Async Thunks תלויים בהכל. 3. הרבה פעמים משתלם להוציא הגדרות Types לקבצי הגדרות נפרדים, כי יבוא של הגדרות טיפוסים גם עלול ליצור מעגלים. עבודה עם Redux Toolkit דורשת משמעת, אבל כל עוד אנחנו מקפידים על חלוקה היררכית ויבוא רק של דברים מסוגים בסיסיים יותר אפשר לבנות גם פרויקטים גדולים ללא מעגלים ולחסוך הרבה כאבי ראש.

ToCode
1 420
# יבוא מעגלי ו Redux Tolkit אם תתחילו לעבוד עם Redux Toolkit לפרויקט משמעותי ותכתבו את הקוד לפי הדוגמאות שבאתר התיעוד, סיכוי טוב שתסיימו עם הצבעות מעגליות. בואו נראה למה ואיך להימנע מזה. ## מה הבעיה עם הצבעה מעגלית הצבעה מעגלית היא פשוט מצב בו מודול אחד ב JavaScript מייבא מודול שני, ואותו מודול שני גם הוא מייבא את המודול הראשון (לפעמים דרך יבוא מודולים נוספים). הנה דוגמה פשוטה עם שני מודולים:
// file a.js
import b from './b.js';

export const text = "hello world";

function go() {
  b();
}

go();
// file b.js

import { text } from './a.js';

export default function b() {
  console.log(text);
}
וזה עובד, אבל מוזר. כי עכשיו אם אני משנה את הקובץ b.js לקוד הבא:
import { text } from './a.js';

console.log(`using text - ${text}`);
export default function b() {
  console.log(text);
}
ומריץ את a.js אני כבר מקבל את השגיאה:
console.log(`using text - ${text}`);
                            ^

ReferenceError: Cannot access 'text' before initialization
    at file:///Users/ynonp/tmp/blog/circular/b.js:3:29
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)
בשביל להבין מה קרה שם צריך להתחיל עם a - הוא מתחיל בייבוא הפונקציה b מתוך המודול b.js ורק אחרי היבוא הוא מייצא את הטקסט. לכן למרות ש b מדפיס את הטקסט אחרי היבוא, בעצם אנחנו במצב של יבוא חלקי. הצלחנו לייבא את השם text אבל המודול a עוד לא התחיל לרוץ ולכן אין לו עדיין ערך. ואם בדוגמה כל כך פשוטה אפשר להסתבך, רק דמיינו את השגיאות והנפילות שיקבלו את פניכם בדוגמאות יותר מסובכות מפרויקטים אמיתיים. ## מה הבעיה עם Redux Toolkit דוגמת הקוד הראשונה מ Redux Toolkit בדף ה Quickstart שלהם היא:
import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

export interface CounterState {
  value: number
}

const initialState: CounterState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  },
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer
הקוד מראה לנו איך ליצור Actions באמצעות הגדרת מפתחות באוביקט reducers, ומסביר שאנחנו מקבלים במתנה Action Creators אותם אפשר לייצא מהסלייס. כל עוד רק קומפוננטות ריאקט וה Store יהיו תלויים בסלייס הזה אין שום בעיה. עכשיו בואו נמשיך עם הדוגמה שלהם כדי לכתוב Async Thunk, למשל ארצה לכתוב פעולה אסינכרונית שאחרי שניה מעלה את ה counter. מעמוד הדוגמה של createAsyncThunk אני לומד שאני יכול לכתוב את הקוד הבא כדי לייצר async thunk:
export const incrementLater = createAsyncThunk(
  'todos/incLater',
  // Declare the type your function argument here:
  async (_, {dispatch, getState}) => {
    // PROBLEM - getState() depends on store
    
    await new Promise((resolve, reject) => setTimeout(resolve, 1000));
    dispatch(increment());
  }
)
וזה כבר מתחיל להיות מסוכן - כי קוד של Async Thunk תלוי ב State הראשי של ה Store (זה מה ש getState מחזיר), אבל ה Store תלוי בקובץ של הסלייס (כדי ליצור את ה Store), וכך אם אני כותב את הקוד באותו קובץ של הסלייס יצרתי מעגל. ## הפיתרון: שימו לב מה תלוי במה המפתח לשבירת מעגלי תלויות הוא לזהות מה צריך להיות תלוי במה, ולחלק את הקוד לקבצים בצורה היררכית. במקרה של Redux Toolkit אנחנו רוצים לייצר את היררכיית התלויות הבאה: 1. קבצי Action (מכילים קריאות ל createAction) - לא תלויים בכלום.

ToCode
1 420
# טיפ פייתון: שימו לב ל b המודול base64 בפייתון מציע דרך פשוטה לפענח מחרוזת מקודדת ל base64. לדוגמה:
>>> import base64
>>> base64.b64decode("bmluamE=")
b'ninja'
>>> base64.b64decode("bW9ua2V5")
b'monkey'
>>> base64.b64decode("bG92ZQ==")
b'love'
>>>
אבל שימו לב ל b הקטן בתחילת התוצאה. פיענוח מחרוזת base64 מחזיר מערך של בתים ולא מחרוזת, וזה יכול להיות מבלבל אם מנסים להשוות את התוצאה למשהו ששמור אצלכם בבסיס נתונים או בזיכרון. הקוד הבא למשל לא מדפיס את ההודעה שהיינו מצפים:
import base64
import sys

enc = sys.argv[1]

users = ["monkey", "love", "ninja"]

if base64.b64decode(enc) in users:
    print("welcome")
כשאני מפעיל אותו עם המחרוזת bmluamE= בתור הארגומנט הראשון אני לא מקבל את הודעת ההדפסה, בגלל שהפיענוח מחזיר מערך של בתים אבל users מחזיק מערך של מחרוזות. בשביל לתקן את הקוד אני יכול לשמור מערך של בתים ב users, או לפענח את מערך הבתים למחרוזת. אופציה 1:
import base64
import sys

enc = sys.argv[1]

users = [b"monkey", b"love", b"ninja"]

if base64.b64decode(enc) in users:
    print("welcome")
אופציה 2:
import base64
import sys

enc = sys.argv[1]

users = ["monkey", "love", "ninja"]

if base64.b64decode(enc).decode("utf-8") in users:
    print("welcome")

ToCode
1 420
# למה לא נשלח מייל הבוקר (או: מה בא אחרי I) אלה מכם שרשומים לקבלת הפוסטים דרך המייל אולי הופתעו הבוקר כשהמייל היומי לא נשלח. האמת שאם אתם קוראים יותר מכמה חודשים אתם כבר יודעים שהמערכת מדלגת על שליחת מיילים בחגים. מה שאתם אולי לא יודעים זה שמנגנון הדילוג הוא בסך הכל סקריפט bash שמשתמש ב hebcal ו grep. בעיקרון hebcal מדפיס לנו את כל התאריכים החשובים בלוח השנה העברי:
hebcal | head -10
1/3/2023 Asara B'Tevet
1/23/2023 Rosh Chodesh Sh'vat
2/6/2023 Tu B'Shvat
2/18/2023 Shabbat Shekalim
2/21/2023 Rosh Chodesh Adar
2/22/2023 Rosh Chodesh Adar
3/4/2023 Shabbat Zachor
3/6/2023 Ta'anit Esther
3/7/2023 Purim
3/8/2023 Shushan Purim
הסקריפט מחפש רק את התאריך החשוב של היום, ומשתמש ב grep כדי להבין אם תאריך זה הוא אחד מרשימה של תאריכים שבהם לא צריך לשלוח מייל. הבלוק הרלוונטי כולו היה:
evenings_ok=(
"Pesach I"
"Pesach VII"
"Shavuot I"
"Tish'a B'Av"
"Rosh Hashana II"
"Yom Kippur"
"Sukkot I"
"Shmini Atzeret"
)

for send_in_evening in "${evenings_ok[@]}"
do
        # echo "^[0-9/]+ ${send_in_evening}"
        if hebcal $(date +"%m %d %Y") | egrep -v "CH''M" | egrep "^[0-9/]+ ${send_in_evening}" >& /dev/null
        then
                if (( $(date +%"_H") < 20 ))
                then
                        exit 1
                fi
        fi
done
אז מה שבור כאן? הפסח כמובן. המחרוזת Pesach I שמייצגת את חג הפסח באמת מתאימה לטקסט Pesach I שיודפס באותו יום, אבל היא מתאימה גם ל Pesach II:
hebcal | grep 'Pesach I'
4/6/2023 Pesach I
4/7/2023 Pesach II
4/8/2023 Pesach III (CH''M)
4/9/2023 Pesach IV (CH''M)
ולכן כשהסקריפט ניסה לשלוח מיילים הבוקר הוא חשב שזה חג וויתר על המייל. וכמו הרבה פעמים ברגע שרואים את הבעיה התיקון כבר כותב את עצמו. במקרה שלנו רק צריך לתקן את הביטוי הרגולארי:
$ hebcal | grep 'Pesach I$'
4/6/2023 Pesach I
נ.ב. זה הסקריפט אחרי תיקון. אם יש לכם רעיונות לבאגים נוספים שהוא מכיל שאולי פספסתי מוזמנים לשלוח, או אם אתם צריכים סקריפט לזיהוי חגים מוזמנים להשתמש בו לכל מטרה:
evenings_ok=(
"Pesach I$"
"Pesach VII"
"Shavuot I$"
"Tish'a B'Av"
"Rosh Hashana II"
"Yom Kippur"
"Sukkot I"
"Shmini Atzeret"
)

no_emails=(
"Rosh Hashana"
)

no_emails_erev=(
"Erev Pesach"
"Erev Shavuot"
"Erev Rosh Hashana"
"Erev Sukkot"
"Erev Yom Kippur"
)


for send_in_evening in "${evenings_ok[@]}"
do
        # echo "^[0-9/]+ ${send_in_evening}"
        if hebcal $(date +"%m %d %Y") | egrep -v "CH''M" | egrep "^[0-9/]+ ${send_in_evening}" >& /dev/null
        then
                if (( $(date +%"_H") < 20 ))
                then
                        exit 1
                fi
        fi
done

for holiday in "${no_emails[@]}"
do
        if hebcal $(date +"%m %d %Y") | egrep "^[0-9/]+ ${holiday}" >& /dev/null
        then
                exit 1
        fi
done



for erev_holiday in "${no_emails_erev[@]}"
do
        if hebcal $(date +"%m %d %Y") | egrep "^[0-9/]+ ${erev_holiday}" >& /dev/null && [$(date +"_H") > 19]
        then
                exit 1
        fi
done

ToCode
1 420
# טיפ פייתון: פיתוח עם docker compose דוקר קומפוז מספק דרך קלה להתחיל לכתוב יישומים בתוך קונטיינר. למרות שהרבה אנשים לא אוהבים להשתמש בו לסביבת פרודקשן (בגלל היעדר יכולות לניהול קונטיינרים רצים), בסביבת פיתוח עדיין מאוד נוח להשתמש בו במצבים שעובדים על כמה פרויקטים שונים ושכל פרויקט צריך את התלויות והתוכנות הנוספות שלו. בדוגמה של פייתון אפשר לדמיין שנרצה לעבוד על כמה פרויקטי פייתון שכל אחד מהם משתמש בגירסת פייתון אחרת וספריות אחרות, וכל אחד מהם גם משתמש בבסיס נתונים שונה או גירסה שונה של בסיס הנתונים. בעוד שקל לנהל סט חבילות ספציפי לפרויקט עם סביבות וירטואליות, כשמתחילים להוסיף את התוכנות מסביב דברים עלולים להסתבך. באמצעות דוקר קומפוז אפשר לבנות מערכת שמורכבת מקונטיינר של אפליקציה שמתחברת לבסיס נתונים מסוג מסוים, לבחור גירסת פייתון וסביבה וירטואלית וגירסה של בסיס הנתונים ולהריץ ולהוריד את כל המערכות בפקודה אחת. בשביל לפתח על המחשב שלנו קוד פייתון עם docker-compose נצטרך להתמודד עם האתגרים הבאים: 1. נרצה שהקוד בתוך הקונטיינר יהיה זהה לקוד בספריית הפיתוח. 2. נרצה לשמור את המודולים שהפרויקט משתמש בהם בסביבה וירטואלית ספציפית לפרויקט, שלא קשורה לגירסאות פייתון שמותקנות אצלי במחשב המארח. 3. נרצה לאפשר לקוד לתקשר עם בסיס נתונים שגם יעלה מתוך ה compose. 4. נעדיף לא לבנות אימג' בשביל הפיתוח כדי לחסוך פעולות build. ## תיאור הפיתרון עם docker compose אפשר לבנות פיתרון שעונה על כל האתגרים: 1. נמפה את הקוד מתיקיית הפיתוח לתיקייה בתוך הקונטיינר 2. נשתמש בסקריפט entrypoint שימופה לתוך הקונטיינר שיתקין את התלויות מקובץ requirements.txt לתוך סביבה וירטואלית בתוך הקונטיינר. 3. נשמור את הסביבה הוירטואלית על volume כדי שבהפעלות הבאות לא נצטרך להתקין מחדש את התלויות. ## מימוש קוד הפיתרון נראה דוגמה באמצעות תוכנית flask שמתחברת לבסיס נתונים Postgresql ושולפת משם מידע להחזיר ללקוח. אני יוצר תיקיה עבור הפרויקט ובתוכה קובץ בשם main.py עם התוכן הבא:
from flask import Flask
import psycopg2
import os

conn = psycopg2.connect(
   database="postgres",
   user=os.environ['DB_USER'],
   password=os.environ['DB_PASS'],
   host=os.environ['DB_HOST'],
   port= os.environ['DB_PORT']
)

app = Flask(__name__)

@app.route("/")
def hello_world():
    cursor = conn.cursor()
    cursor.execute("select version()")
    data = cursor.fetchone()

    return f"Using database version {data}"
וקובץ בשם requirements.txt עם התוכן הבא:
flask
psycopg2
עכשיו אני יוצר קובץ בשם docker-compose.yml עם התוכן הבא:
version: "3.9"
services:
  app:
    image: python:3.11-bullseye
    command: /bin/bash /usr/local/bin/entrypoint.sh
    ports:
      - 5000:5000
    volumes:
      - .:/app
      - ./entrypoint-dev.sh:/usr/local/bin/entrypoint.sh
      - venv:/venv

    environment:
      DB_USER: "postgres"
      DB_PASS: "monkey"
      DB_HOST: "db"
      DB_PORT: "5432"

  db:
    image: postgres:15.2
    environment:
      POSTGRES_PASSWORD: "monkey"

    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  venv:
  pgdata:
וקובץ בשם entrypoint-dev.sh עם התוכן הבא:
#!/bin/bash

cd /app

if [[ ! -d /venv/venv ]]
then
  python -m venv /venv/venv
fi
source /venv/venv/bin/activate
pip install -r requirements.txt

flask --app main run --host 0.0.0.0 --port 5000
אחרי כל אלה אפשר לצאת לדרך. מפעילים:
$ docker compose up
ויכולים לגלוש מהדפדפן לכתובת localhost:5000 כדי לראות את העמוד הראשי שידפיס את הגירסה של בסיס הנתונים אליה אנחנו מחוברים.

ToCode
1 420
# פתוחים לרעיונות חדשים מעבר לצורך להישאר רלוונטים, יש עוד שני יתרונות בגללם אנחנו חייבים להישאר פתוחים לרעיונות חדשים- 1. חלק מהרעיונות יכולים ללמד אותנו דרכי עבודה חדשות עם אותם כלים שאנחנו כבר מכירים. 2. חלק מהרעיונות יכולים ללמד אותנו כלי עבודה חדשים, שיהפכו את שיטת העבודה הנוכחית שלנו לקלה יותר. אני עדיין רואה מתכנתים ומתכנתות שמשתמשים ב Redux בלי Redux Toolkit, אחרי שבנו לבד חלקים גדולים ממה ש RTK נותן ובצורה פחות טובה. הסיבה היחידה להמשיך לעבוד כך היא מטעמי Legacy, וזו בדיוק האטימות שאנחנו מכירים לרעיונות חדשים. אני עדיין רואה מתכנתים ומתכנתות שמוותרים על TypeScript כי "לכתוב הגדרות טיפוסים רק מאט את העבודה", למרות שהם שמחים לקבל השלמות אוטומטיות ובדיקות מהירות מה IDE ומשתמשים ב ESLint בלי בעיה. אם הם יפתחו את הדלת ל TypeScript אין ספק שההתחלה תהיה קשה, אבל מהר מאוד הם יקבלו חווית פיתוח טובה בהרבה. מינון טוב של קבלת רעיונות חדשים עוזר לנו לבנות מערכות טובות יותר, ולא פחות חשוב משאיר את הכיף בפיתוח מערכות. הכיף של גילוי, כיף של יישום, כיף של להתווכח עם רעיון ולדבר איתו עד שמבינים את הכוונה וכיף כשבסוף דברים מתחברים ועובדים טוב יותר.

ToCode
1 420
אפשר לראות את הגירסה עם Client Component בקישור הזה: https://server-components-demo-2cr9zxka4-ynonp.vercel.app/ ## שליפת מידע מ API ב Server Component אחד היתרונות המעניינים של הרצת קוד צד-שרת ושליחת התוצאה בלבד לקלאיינט הוא היכולת לגשת ל APIs או לבסיס נתונים מתוך קומפוננטת ריאקט, גישה שתתבצע רק בצד השרת. ננסה את זה עם API. ניצור קומפוננטה להצגת מידע מ swapi.dev שתציג מידע על כוכבים ממלחמת הכוכבים:
export default async function PlanetInfo({id=3}) {
  const res = await fetch(`https://swapi.dev/api/planets/${id}/`);
  const data = await res.json();

  return (<div>
    <p>Welcome to the planet {data.name}. Its diameter is {data.diameter} and its population is {data.population} inhabitants</p>
  </div>)
}
אני מעדכן את קוד העמוד שיציג לי מידע על כמה כוכבים בקובץ page.tsx:
import PlanetInfo from './components/planet_info'

export default function Home() {
  return (
    <main>
      <PlanetInfo id={3} />
      <PlanetInfo id={4} />
    </main>
  )
}
וכבר במצב פיתוח אפשר לראות שקרה פה משהו מעניין - בטאב network אני לא רואה בכלל גישות ל swapi. המידע כולו הגיע מקומפוננטת צד השרת בטעינת העמוד, למרות שהקומפוננטה היא בכלל Promise ולא מתנהגת לפי מנגנון מחזור החיים הרגיל של קומפוננטת ריאקט. למעשה אנחנו רואים כאן את היכולת המלהיבה השניה של קומפוננטות צד שרת, והיא האפשרות "לברוח" מדרך החשיבה הריאקטית ולהשתמש בקוד async/await רגיל כדי למשוך מידע ממקורות חיצוניים ולהכין את הקומפוננטה. ## מגבלות - העברת מידע מ Client Component ל Server Component בשיחה על Server Components שווה לשים לב גם למגבלות שלהם, ובפרט למגבלה (היחסית צפויה) ש Server Components לא כוללים סטייט. בדוגמה של כוכבי הלכת ממלחמת הכוכבים, ייתכן ונרצה לאפשר למשתמש לבחור את מזהה הכוכב ואז להציג את המידע על אותו כוכב. זה הקוד שהייתי רוצה לכתוב בקובץ planet_picker.tsx:
"use client";
import { useState } from "react";
import PlanetInfo from "./planet_info";

export default function PlanetPicker() {
  const [id, setId] = useState(3);
  return (
    <div>
      <input type="text" value={id} onChange={(e) => setId(Number(e.target.value))} />
      <PlanetInfo id={id} />
    </div>
  )
}
אבל זה לא עובד - ה Server Component לא יודע עדיין מה המשתמש בחר בזמן יצירת העמוד (כי משתמש עדיין לא בחר כוכב) ולכן זה לא עובד. המנגנון הכי קרוב שיש ל next להציע הוא להטמיע את הסטייט, במקרה שלנו ה id של הכוכב, ב url של העמוד ואז כשמשתמש משנה את הערך להשתמש ב router.push כדי לעבור לעמוד חדש, שם הקומפוננטה תרונדר בצד השרת עם הערך העדכני שיילקח מה Route Param. ## סיכום סך הכל פיצ'ר Server Components פותר שתי בעיות מרכזיות בפיתוח עם ריאקט: 1. הוא מסדר את כל שליפות המידע הראשוניות שאנחנו צריכים כדי להציג את העמוד, וחוסך למשוך מידע מצד הלקוח אחרי שהעמוד עולה. 2. הוא מאפשר חיבור קל יותר בין קומפוננטות ריאקט למידע שמגיע מבסיס נתונים כשהמידע הזה דרוש רק לטעינה ראשונית של העמוד. הרבה אתרים סטטיים או סטטיים ברובם יוכלו להרוויח מהשינוי החדש, ובמיוחד מאחר וב next.js קומפוננטות צד שרת הן ברירת המחדל.

ToCode
1 420
# משחקים עם React Server Components פיצ'ר Server Components של ריאקט הוא חלק מריאקט 18 ומאפשר סוג של Server Side Rendering לקומפוננטות ספציפיות. בגירסה 13 של Next.JS התווספה תמיכה ב Server Components מה שהפך את העבודה איתם להרבה יותר פשוטה. בואו נראה איך זה עובד ומה היתרונות שלהם על פני פיתוח ריאקט רגיל או על פני Server Side Rendering. ## מהם React Server Components קומפוננטות צד שרת הן פשוט קומפוננטות ריאקט שמרונדרות לגמרי בצד שרת. המפתח כאן הוא השורה "קומפוננטות צד שרת לא משפיעות כלל על גודל הבאנדל שנשלח ללקוח" איתה נפתח ה RFC. המטרה של קומפוננטות צד שרת היא לאפשר לכתוב קוד צד שרת בסגנון קומפוננטות ריאקט, וכך לספק חווית פיתוח אוניברסלית טובה יותר. ## ה Server Component הראשון שלי בשביל להבין איך זה עובד נלך לבנות מערכת עם Server Components ב Next.JS. אני מתחיל פרויקט next חדש:
$ npx create-next-app@latest server-components-demo
לשאלות שלו אני עונה: 1. שימוש ב TypeScript - כן 2. שימוש ב ESLint - כן 3. שימוש בתיקיית src - כן 4. שימוש בתיקיית app ניסיונית - כן 5. שימוש ב import alias ברירת המחדל קיבלתי עץ תיקיות שנראה כך:
.
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── public
│   ├── next.svg
│   ├── thirteen.svg
│   └── vercel.svg
├── src
│   └── app
│       ├── api
│       │   └── hello
│       │       └── route.ts
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       ├── page.module.css
│       └── page.tsx
└── tsconfig.json

6 directories, 15 files
בשלב ראשון אני מוסיף תיקיה בשם components ובתוכה יוצר קומפוננטה בשם HelloWorld עם הקוד הבא:
// file: src/app/components/hello_world.tsx

export default function HelloWorld({name="Guest"}) {
  return <h1>Hello {name}</h1>
}
לאחר מכן אני מעדכן את הקובץ src/page.tsx לקוד הבא:
import HelloWorld from './components/hello_world'

export default function Home() {
  return (
    <main>
      <HelloWorld name="Dave" />
      <HelloWorld name="Dana" />
      <HelloWorld />
    </main>
  )
}

ואנחנו מוכנים לצאת לדרך. אני מפעיל את הקוד עם:
$ npm run dev
ויכול לגלוש ל loalhost:3000 כדי לראות את הודעות הברכה לשלושת המשתמשים. בשביל לראות את הקוד בפרודקשן אני מעלה אותו למאגר גיט בכתובת: https://github.com/ynonp/server-components-demo מחבר אותו ל vercel ויש לנו את המערכת באוויר בכתובת: https://server-components-demo-aabippwya-ynonp.vercel.app הפתעה ראשונה שתקבל את פנינו בכניסה לעמוד היא שאין צורך בהרצת JavaScript כדי לראות את הדף. גם אם אתם נכנסים עם תוסף NoScript או משהו דומה אתם עדיין תראו את אותן שלוש שורות של Hello, כי הרינדור של הקומפוננטות נעשה בצד שרת. נכון שספריית ריאקט עצמה עדיין נשלחת ללקוח, אבל קוד הקומפוננטות כבר לא. אפשר לראות ההבדל אם נעדכן את הקוד של Hello World ונהפוך אותו לקומפוננטת צד לקוח. בשביל זה יש להוסיף את הטקסט "use client" בראש הקובץ בצורה כזאת:
"use client";

export default function HelloWorld({name="Guest"}) {
  return <h1>Hello {name}</h1>
}
אחרי קומיט והעלאת הגירסה אני יכול לזהות בקובץ page.js בשרת את הקוד הבא:
    7126: function(e, r, t) {
        "use strict";
        t.r(r),
        t.d(r, {
            default: function() {
                return o
            }
        });
        var n = t(9268);
        function o(e) {
            let {name: r="Guest"} = e;
            return (0,
            n.jsxs)("h1", {
                children: ["Hello ", r]
            })
        }
    },
שמראה לי שהקוד של הקומפוננטה נשלח ללקוח. כן צריך להגיד שגם בגירסה הזו של ה Client Component עדיין אפשר לגשת לעמוד ולראות את כל התוכן גם בלי JavaScript מופעל, בזכות ה Server Side Rendering שאני מקבל מ next.js. במקרה הזה ההבדל המרכזי בין שתי הגישות היה גודל הבאנדל והתוכן שלו.

ToCode
1 420
# הסיבות בגללן בחרנו Micro Services יש לא מעט סיבות שיכולות לשכנע אנשים לבחור בארכיטקטורת Micro Services, ביניהן: יכולת גדילה אופקית - כשסרביס מסוים עמוס אפשר להוסיף עוד מופעים שלו התמודדות עם בעיות - כשסרביס נופל אפשר להרים מופעים חדשים שלו, ובנוסף אם יש בעיה בסרביס אחד זה לא חייב לעצור את כל המערכת. זריזות בפיתוח - אפשר לעדכן מיקרו סרביסים בלי לדאוג ששברת קוד במקומות אחרים ביישום. גמישות - אפשר לכתוב סרביסים בכל מיני שפות תכנות, כאשר לכל חלק במערכת נבחר את הטכנולוגיה המתאימה ביותר. דיפלוימנט קל יותר - כי אפשר להעלות רק סרביס אחד ולא לגעת בשאר המערכת. עלות פיתוח נמוכה יותר - כי אפשר להיעזר יותר בקלות בפרילאנסרים או צוותים קטנים לכתיבה ותחזוקה של מיקרו סרביסים. הבעיה שבהרבה מקרים הבחירה בארכיטקטורת Micro Services לא מביאה לתוצאה הרצויה: אי אפשר באמת להוסיף עוד עותקים של סרביס מסוים כי המקביליות גורמת לנעילות, כשסרביס נופל הסרביסים האחרים נכנסים ל Retry Loop ושוברים את כל המערכת כמו דומינו, סרביסים משתפים מידע דרך בסיס נתונים משותף ולכן אי אפשר באמת לשנות אחד בלי לשבור אחרים ואפילו ה Deployments נשארו מסובכים כי צריך להעלות גירסה של כמה סרביסים במקביל. כל זה לפני שדיברנו על הקושי להקים סביבת עבודה מקומית או היכולת לבדוק גירסה מסוימת של המערכת (או באופן כללי לדבר על "גירסה" של מערכת בעולם של מיקרו סרביסים). ארכיטקטורת Micro Services באה עם עלות. אם היא נותנת לכם את כל היתרונות שתכננתם לקבל היא שווה את המחיר, אבל אם אתם חצי שנה לתוך הפיתוח ומגלים שבעצם העלות גבוהה מהתמורה, אולי צריך לדבר על לחזור למונולית.