ar
Feedback
ToCode

ToCode

الذهاب إلى القناة على Telegram

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

إظهار المزيد
1 418
المشتركون
-224 ساعات
-37 أيام
-630 أيام
أرشيف المشاركات
ToCode
1 418
לפני שנתיים בעולם של האינטרנט שנתיים זה המון זמן. קוד שנכתב לפני שנתיים הוא ישן, גרוע, לא מתוחזק, מכוער. אבל בעולם האמיתי? בעולם האמיתי יש מערכות שנכתבו גם לפני עשר שנים והן עדיין מתוחזקות. בעולם האמיתי מערכות אלה ממשיכות לקבל תיקונים, עדכונים ופיצ'רים חדשים. בעולם האמיתי אנחנו לא זורקים לפח קוד רק בגלל שמישהו החליט שצריך להפסיק לכתוב ריאקט עם class וצריך להשתמש ב use. בטווח הרחוק יש דברים אחרים שמפריעים לי בקוד: 1. קומפוננטה של מעל 100 שורות. 2. לוגיקה בתוך JSX. 3. קומפוננטה שמקבלת יותר מ-4 פרופס. 4. פרופ דרילינג - קומפוננטה שלוקחת פרופס רק בשביל להעביר אותם לילדים. 5. לוגיקה (פונקציות) בתוך קומפוננטה, במקום ב Custom Hook או קובץ נפרד. כל הדברים האלה נחשבו בעייתיים גם לפני שנתיים וגם לפני עשר שנים. בחשיבה קדימה כדי שהיישום שלנו יחזיק לאורך זמן לא קריטי להשתמש ב API הכי מודרני (הוא ממילא יראה ישן עוד שנתיים). כן חשוב להשתמש ב Best Practices של פיתוח תוכנה - אלה עקרונות שיישארו איתנו לנצח.

ToCode
1 418
1. קוד צד שרת מכיר את קבצי ה JSX או TSX של הפרויקט ומריץ אותם בתגובה לבקשה מדפדפן כדי להחזיר לדפדפן דף HTML מוכן להצגה. 2. קוד JavaScript יצורף לעמוד ה HTML שנשלח כדי להוסיף אינטרקטיביות לעמוד (קומפוננטות צד לקוח). קוד ה JavaScript מתחיל תמיד מ State ראשוני שנטען מהשרת. 3. פעולות בצד הלקוח יכולות להשפיע על השרת, ואז נשתמש במנגנונים של ריאקט (Server Actions) כדי לשלוח הודעה מהלקוח לשרת, הודעה שמבחינת הקוד נראית כמו קריאה רגילה לפונקציה. הפריימוורק אחראי על כתיבת קוד ה fetch ופיענוח התשובה בשבילנו. 4. אין כמעט הצדקה להשתמש ב Redux או ספריות ניהול סטייט צד לקוח מתוחכמות באפליקציות כאלה, כי אין כמעט סטייט. כל שינוי בצד לקוח מיד מדווח לשרת או מנוהל ברמת הקומפוננטה. היתרון המרכזי באפליקציות Full Stack הוא הפשטות. אפליקציה כזאת דורשת הרבה פחות חיבורים בשביל לעבוד וכוללת הרבה פחות קוד בהשוואה לאפליקציה בקוד ה Front End מופרד מקוד ה Back End. החיסרון הוא הנעילה לתוך הפריימוורק והקושי לשלב את הקוד עם Clients אחרים כמו אפליקציית מובייל.

ToCode
1 418
ארכיטקטורה ליישומי ריאקט יחד עם המעורבות של Vercel בליבה של ריאקט אנחנו רואים גם שינוי ארכיטקטורה בפיתוח יישומי ריאקט - מעבר מספריה לפיתוח קוד צד לקוח לפריימוורק לפיתוח Full Stack. בואו נראה מה האפשרויות שלנו היום ומתי כדאי לבחור כל אפשרות. פיתוח Front End בריאקט וקוד צד שרת בתור REST API ריאקט מסוגלת לעבוד בתור ספריית צד-לקוח ולהשתלב בכל פרויקט צד שרת שנבנה. האינטגרציה בין קוד צד השרת לקוד צד הלקוח מתבצעת במספר שלבים: 1. בשלב הבנייה יהיה לנו מנגנון שיבנה את קבצי המקור (JSX או TSX) וימיר אותם לקבצי JavaScript שדפדפן יכול לקבל. לרוב אחרי הבניה אותו מנגנון גם יעתיק את כל הקבצים שנוצרו לתיקייה להפצה כמו dist או public. 2. בשלב הפרודקשן לקוח יקבל אפליקציית ריאקט סטטית, האפליקציה תפנה לשרת כדי למשוך מידע בדרך כלל ב REST API ותשתמש במידע זה כדי להציג דברים על המסך. שינויים שקורים באפליקציה ינוהלו עם ספריות ניהול State בצד לקוח כמו רידאקס. 3. אופטימיזציה מקובלת בשלב זה היא לשים את המידע הראשוני שהאפליקציה צריכה בתוך ה JavaScript באמצעות הגדרת משתנה על window מתוך קובץ ה HTML, מה שאומר שקובץ ה HTML יוגש מהשרת ולא מקובץ סטטי. הנה דוגמה מ PHP לשתול מידע לתוך ה JavaScript
<?php
// Example PHP data - you can modify this according to your needs
$data = [
    'user' => [
        'id' => 1,
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'preferences' => [
            'theme' => 'dark',
            'notifications' => true
        ]
    ],
    'settings' => [
        'language' => 'en',
        'timezone' => 'UTC'
    ],
    'meta' => [
        'lastUpdated' => date('Y-m-d H:i:s'),
        'version' => '1.0.0'
    ]
];
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>State Management Example</title>
</head>
<body>
    <!-- Your HTML content here -->

    <script>
        // Initialize window.__state__ object
        window.__state__ = window.__state__ || {};
        
        // Safely encode PHP data as JSON and inject it into window.__state__
        window.__state__.data = <?php echo json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_PRETTY_PRINT); ?>;

        // Example of accessing the data
        console.log('User name:', window.__state__.data.user.name);
        console.log('Current theme:', window.__state__.data.user.preferences.theme);
    </script>
</body>
</html>
בצד של ריאקט או רידאקס, אנחנו לוקחים את המשתתנה window.__state__ בתור הערך הראשוני שממנו בונים את ה Store וממשיכים לנהל את השינויים כרגיל לפי פעולות משתמש באפליקציה. היתרונות בגישה זו: 1. אפשר לפתח ולבדוק קוד צד לקוח וקוד צד שרת בנפרד. 2. אפשר להשתמש באותו API בצד שרת גם לאפליקציית מובייל, אפילו שהיא כתובה בשפה אחרת לגמרי. פיתוח מערכת Full Stack בריאקט עם Next היתרונות בגישת ה API ברורים אבל מהם גם ברור לנו מה החסרונות: המערכת שלנו צריכה לכלול קוד שימשוך מידע מהשרת ויפענח אותו, נדרש תיאום בין קוד צד לקוח לקוד צד שרת בממשקים (מה שמשפיע על קצב הפיתוח ועל קצב העבודה ב Refactoring). בנוסף דפדפן שמגיע לאתר מקבל קבצי JavaScript ורק אחרי פיענוח והרצת ה JavaScript אפשר לראות את האתר. קוד ה JavaScript עשוי להיות ארוך בהרבה מה HTML שהוא יוצר וכך אנחנו מעבירים עודף מידע ברשת. בתיעוד של ריאקט הדף שמספר על יצירת פרויקט ממליץ על שימוש בפריימוורק - כלומר ממליץ ליצור פרויקט עם next, remix או expo. הבלוק האחרון בעמוד מזכיר את Tanstack Start ו Redwood, שני פריימוורקים נוספים לפיתוח Full Stack. בהתיחס ל vite, שזו האופציה הקלאסית לפיתוח קוד צד לקוח בלבד, מספרים לנו שם שהם ממליצים על vite רק כשמוסיפים את ריאקט לפרויקט קיים או אם בונים פריימוורק, מה שמראה לנו בצורה ברורה את הכיוון של צוות ריאקט. לכן האופציה השניה והיום לפחות לפי התיעוד של React היא גם המומלצת היא פיתוח מוצר Full Stack עם ריאקט כחלק מפריימוורק גדול יותר. פריימוורקים מציעים לנו את האופציה לבנות את הפרויקט בתור קוד צד לקוח בלבד, אבל לרוב הם יעודדו אותנו לבחור בארכיטקטורה מקיפה הכוללת קוד שרת וקוד לקוח, וזה אומר:

ToCode
1 418
הקוד לוקח את הערך למשתנה temp_dir, ואז מוחק את ה / מתחילת הנתיב ומחבר את זה לתיקייה בה נמצאת ספריית podcastfy עצמה. אני לא מבין למה הם עשו את זה אבל בפועל זה אומר שקבצים זמניים יישמרו לתת תיקייה בתוך תיקיית היישום, מה שלא עובד ב Lambda. בשביל לברוח בכל זאת לתיקיית /tmp בלי לכתוב את סימן / בתחילת שורת הנתיב השתמשתי בסימן .. שעובר תיקייה אחת למעלה, וכך נקבל נתיב שנראה בערך כך:
/var/task/app/podcastfy/../../../../../../../tmp/podcastfy-demo/tmp
והקבצים יישמרו בתיקיית /tmp דרך הנתיב היחסי. מה נשאר הפונקציה עובדת מכפתור Test בממשק של Lambda וזה טוב, עדיין נשארו שני דברים כדי שנוכל להשתמש בה בצורה נכונה: 1. יש לחבר את הפונקציה לאירוע חיצוני שיטריג אותה. בקוד הדוגמה היא מופעלת דרך API Gateway בלי אותנטיקציה, כך שכל אחד מכל האינטרנט יכול להפעיל את הפונקציה. צריך לעדכן את זה כדי שהיא תופעל או מתוך אירוע שמגיע מתור או לפחות בצורה מוגנת דרך API Gateway כך שרק השרת שלי יוכל להטריג את ההפעלה. 2. הפונקציה משתמשת בקובץ .env בתיקייה המקומית כדי להגדיר ערכים ל API Keys שהיא צריכה. ל AWS יש מנגנון יותר מאובטח בשם Secrets Manager. בעולם האמיתי נרצה להשתמש בו כדי שלא יגנבו לנו את מפתחות ה API.

ToCode
1 418
"iam:PutRolePolicy",
                "iam:DeleteRolePolicy",
                "iam:PassRole",
                "iam:TagRole",
                "iam:UntagRole",
                "iam:AttachRolePolicy",
                "iam:DetachRolePolicy"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudformation:CreateChangeSet",
                "cloudformation:CreateStack",
                "cloudformation:UpdateStack",
                "cloudformation:DeleteStack",
                "cloudformation:DescribeStacks",
                "cloudformation:ListStacks",
                "cloudformation:GetTemplateSummary",
                "cloudformation:ValidateTemplate",
                "s3:CreateBucket",
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject",
                "lambda:CreateFunction",
                "lambda:UpdateFunctionCode",
                "lambda:UpdateFunctionConfiguration",
                "lambda:DeleteFunction",
                "lambda:GetFunction",
                "lambda:ListFunctions",
                "lambda:PublishVersion",
                "lambda:CreateAlias",
                "lambda:DeleteAlias",
                "lambda:UpdateAlias",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:GetRole",
                "iam:PutRolePolicy",
                "iam:DeleteRolePolicy",
                "iam:PassRole"
            ],
            "Resource": "*"
        }
    ]
}
אחרי שיש משתמש נכנסים לטאב Security Credentials ויוצרים עבורו Access Key. משורת הפקודה נפעיל:
aws configure
ונכניס את ה Access Key ואת ה Secret כשהוא שואל אותנו. העלאה ל AWS והרצת בדיקה מהממשק שלהם אחרי חיבור AWS אפשר להפעיל:
sam deploy --guided
ולענות על השאלות כדי להעלות את הפונקציה ל AWS. אחרי סיום העלאה תוכלו למצוא את הפונקציה בדף הפונקציות בסרביס Lambda בממשק של AWS, ושם גם תמצאו כפתור Test שיפעיל את פונקציית ה Lambda החדשה שלנו. תיקון הקוד והעלאה מחדש ל Lambda יש מספר מגבלות חדשות שלא קיימות בהפעלה מקומית על SAM. הראשונה היא מגבלת הזיכרון המקסימלי (סאם מקומי יכול לעבוד עם עד 10 ג'יגה זיכרון, אבל בענן הם נתנו לי רק 3). השניה, יותר חשובה, היא שאי אפשר ליצור קבצים איפה שרוצים אלא רק בתיקיית /tmp. בעבודה עם podcastfy צריך להגיד לו ליצור את כל הקבצים שלו רק שם ואת זה עושים בשורה שיוצרת את הפודקסט עם פרמטר נוסף של הגדרות שיחה. זה נראה כך:
    audio_file = generate_podcast(
            urls=["https://en.wikipedia.org/wiki/Podcast"],
            conversation_config={
                "text_to_speech": {
                    "temp_audio_dir": "../../../../../../../../../../../../tmp/podcastify-demo/tmp",
                    "output_directories": {
                        "transcripts": "/tmp/podcastify-demo/transcripts",
                        "audio": "/tmp/podcastify-demo/audio"
                        }
                    }
                }
            )
את שני הערכים בתוך output_directories קל להבין, אבל מה הסיפור עם כל הנקודות ב temp_audio_dir ? נו, זה מעניין. בקוד הספריה podcastfy השורות הבאות מטפלות בערך שאני מעביר כאן:
def _setup_directories(self) -> None:
    """Setup required directories for audio processing."""
    self.output_directories = self.tts_config.get("output_directories", {})
    temp_dir = self.tts_config.get("temp_audio_dir", "data/audio/tmp/").rstrip("/").split("/")
    self.temp_audio_dir = os.path.join(*temp_dir)
    base_dir = os.path.abspath(os.path.dirname(__file__))
    self.temp_audio_dir = os.path.join(base_dir, self.temp_audio_dir)

ToCode
1 418
API Gateway Lambda Proxy Input Format

        Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format

    context: object, required
        Lambda Context runtime methods and attributes

        Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html

    Returns
    ------
    API Gateway Lambda Proxy Output Format: dict

        Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
    """
    audio_file = generate_podcast(urls=["https://en.wikipedia.org/wiki/Podcast"])
    print(audio_file)

    return {
        "statusCode": 200,
        "body": json.dumps(
            {
                "message": "Audio File Created",
            }
        ),
    }
ובעצם הוא מייצר פודקסט מדף ב wikipedia שומר אותו בתיקייה המקומית ויוצא. אני יודע לא מלהיב במיוחד אבל מספיק טוב בשביל הדוגמה שלנו. אחרי שבניתי את האימג' אני יכול להריץ את הפונקציה עם:
sam local invoke HelloWorldFunction --debug
ה --debug הוא אופציונאלי אבל מומלץ מאוד כי כשדברים נשברים אתם רוצים לדעת למה. אם לקחתם את פרויקט הדוגמה שלי זה כנראה יעבוד בזכות שתי הגדרות בקובץ ה template.yaml:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image
      Timeout: 600
      MemorySize: 3008
הגדרת ה Timeout חשובה כי ליצור פודקסטים לוקח זמן וברירת המחדל 10 שניות קצרה מדי. גודל הזיכרון בברירת המחדל הוא 128 מגה שזה גם קטן מדי, לפי התיעוד אפשר להקצות עד 10 ג'יגה אבל מניסויים שלי 3 ג'יגה היה המקסימום שהצלחתי לקבל ולכן זה הגודל שהשארתי. אם הפונקציה רצה מקומית והכל עובד אפשר להמשיך לשלב הבא. יצירת משתמש AWS בשביל להעלות את הקוד ל Lambda על AWS יש ליצור משתמש בסרביס IAM שלהם ולתת לו מספיק הרשאות כדי להעלות את הקוד. יכול להיות שאפשר להשתמש פה במשתמש הראשי שלכם (לא ניסיתי), ובכל מקרה אם אתם יוצרים משתמש ואין לו מספיק הרשאות תקבלו הודעת שגיאה ב deploy ואז אפשר להוסיף הרשאות לפי מה שהוא מבקש ויש גם כפתור בממשק שעושה את זה אוטומטית. ההרשאות שאני נתתי בסופו של דבר היו: 1. AmazonAPIGatewayAdministrator 2. AmazonS3FullAccess 3. AWSCloudFormationFullAccess 4. AWSLambda_FullAccess ובנוסף גם:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceLinkedRole"
            ],
            "Resource": "arn:aws:iam::*:role/aws-service-role/apigateway.amazonaws.com/AWSServiceRoleForAPIGateway",
            "Condition": {
                "StringLike": {
                    "iam:AWSServiceName": "apigateway.amazonaws.com"
                }
            }
        }
    ]
}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:CreateRepository",
                "ecr:GetAuthorizationToken",
                "ecr:InitiateLayerUpload",
                "ecr:UploadLayerPart",
                "ecr:CompleteLayerUpload",
                "ecr:PutImage",
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:TagResource",
                "ecr:DescribeRepositories",
                "ecr:DeleteRepository",
                "ecr:SetRepositoryPolicy",
                "ecr:GetRepositoryPolicy"
            ],
            "Resource": [
                "arn:aws:ecr:us-east-1:124355662604:repository/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "ecr:GetAuthorizationToken",
            "Resource": "*"
        }
    ]
}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceLinkedRole",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:GetRole",

ToCode
1 418
מדריך: הכנת סביבה והרצת קוד פייתון ב AWS Lambda אם יש משהו שמאפיין את AWS זה הפער בין התיאוריה לפרקטיקה. קל מאוד לתאר מה אני רוצה לעשות אבל בדרך להפוך את זה לקוד הרבה דברים מסתבכים. במדריך היום נראה איך להפעיל תוכנית פייתון על Lambda, מה יכול להשתבש בדרך ואיך לתקן. התקנת הכלים לצורך המדריך אני מניח שיש לכם על המכונה את הכלים הבאים מותקנים: 1. כלי שורת הפקודה של AWS. התקנה מכאן: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html. 2. כלי שורת הפקודה SAM. התקנה מכאן: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html 3. דוקר. התקנה מכאן: https://docs.docker.com/ 4. מאוד מומלץ להריץ את המדריך ממכונת לינוקס בארכיטקטורה x86_64 כי זו הארכיטקטורה עליה רץ ה Lambda. בעבודה על המק נתקלתי בהמון תקלות מוזרות תוך כדי שנעלמו לגמרי כשעברתי ללינוקס. וכמובן אני מניח שיש לכם משתמש על AWS. בפוסט נשתמש ב SAM שהוא הרחבה של CloudFormation, כלומר כלי לתיאור ארכיטקטורה באמצעות קובץ yml והרצתה מקומית מחוץ ל AWS כדי לבדוק שפונקציות ה Lambda שיצרנו עובדות. בהשוואה ל localstack עליו כתבתי בפוסטים קודמים, סאם מריץ רק פונקציות Lambda ולא כולל את שירותי AWS האחרים. תבנית הפרויקט בשביל הדוגמה אני בונה פרויקט שיוצר פודקסט עם ספריית podcastfy. יצרתי את תבנית הפרויקט דרך כלי שורת הפקודה SAM עם הפקודה:
$ sam init
אתם יכולים לעשות את זה ולבחור מהתפריט ליצור פרויקט פייתון על בסיס Image, או לקחת את הקבצים מתבנית הפרויקט של הפוסט בגיטהאב כאן: https://github.com/ynonp/lambda-podcastfy/tree/main לצורך הדוגמה אני אריץ תוכנית פייתון שמשתמשת בספריית podcastfy ליצירת פודקסטים. הספריה תתן לנו הזדמנות לגעת במספר נקודות רגישות של Lambda. אם אתם לוקחים את הקוד שלי מגיטהאב תצטרכו ליצור בתוך תיקייה hello_world קובץ .env במבנה הבא:
GEMINI_API_KEY=
OPENAI_API_KEY=
וכן יש למלא את המפתחות בפנים בשביל שזה יעבוד. בנייה והרצה מקומית בשביל לבנות את הפרויקט מקומית נוכל להריץ:
$ sam build --use-container
התוספת --use-container גורמת ל SAM להריץ את הבנייה. בנייה בהקשר הזה זה בנייה של אימג' שבתוכו תרוץ ה Lambda שלנו. אפשר לראות ולעדכן את ה Dockerfile בקובץ hello_world/Dockerfile והתוכן שלו בפרויקט הדוגמה הוא:
FROM public.ecr.aws/lambda/python:3.12

WORKDIR /var/task

RUN mkdir -p /var/task/ffmpeg && \
    cd /var/task/ffmpeg && \
    curl -O https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
    python -c "import tarfile; tarfile.open('ffmpeg-release-amd64-static.tar.xz').extractall()" && \
    mv ffmpeg-*-amd64-static/* . && \
    rm -rf ffmpeg-*-amd64-static && \
    rm ffmpeg-release-amd64-static.tar.xz

ENV PATH="/var/task/ffmpeg:${PATH}"
RUN chmod -R +x /var/task/ffmpeg

COPY app.py requirements.txt ./
COPY .env ./

RUN python3.12 -m pip install -r requirements.txt -t .

* Command can be overwritten by providing a different command in the template directly. *
CMD ["app.lambda_handler"]
בפרויקט הדוגמה אני רוצה להתקין את ספריית podcastfy והיא דורשת התקנה של ffmpeg על המכונה. אנחנו מתחילים עם אימג' של פייתון מאמזון ובתוכו אין כמעט כלום, הם כן שמו curl אבל אפילו tar לא מותקן על האימג' ולכן בשביל להתקין את ffmpeg אני מוריד את הבינארי שלו ומשתמש ב python -c כדי לפתוח את הארכיון. מוסיפים את ffmpeg לרשימת הנתיבים להרצה ומתקנים הרשאות ואז ממשיכים להעתקת קבצי התוכנית (בפרויקט שלי זה רק app.py) וקובץ משתני הסביבה. אחרי זה התקנת התלויות והפעלת הפונקציה המרכזית מתוך app. הקוד ב app.py בזמן הבניה המקומית הוא:
import json
from podcastfy.client import generate_podcast
import os

def lambda_handler(event, context):
    """Sample pure Lambda function

    Parameters
    ----------
    event: dict, required

ToCode
1 418
הבנה דורשת שינון הרבה התלהבות ברשת סביב ה AI מלווה בתחושה שאנחנו מתקרבים לעידן בו יהיה קל יותר לכתוב קוד (יש בזה משהו) ואולי אפילו שנוכל לכתוב קוד בלי להתעסק בפרטים הקטנים של העבודה. אנחנו נתאר בגדול את הארכיטקטורה ומה כל דבר צריך לעשות וה AI יהפוך את זה להוראות מסודרות. קצת כמו שאנחנו לא צריכים להכיר אסמבלי בשביל לכתוב C, ולא צריכים להכיר C בשביל לכתוב Java. אבסטרקציות נבנות על רעיונות בסיסיים יותר ואחרי שבנינו את האבסטרקציה אפשר להמשיך הלאה. ואולי כל מה שאנחנו יודעים על קוד הולך להשתנות ונוכל להמשיך הלאה לכתוב בלי להכיר את הפרטים? אולי נוכל לכתוב רק עם "הבנה"? הבעיה עם התיאוריה הזאת היא שהמילה "הבנה" בעצם מסתירה "אי הבנה". בואו ניקח את זה לדוגמה ספציפית: נניח שאני מתכנת ריאקט ואני רוצה ללמוד Vue. אני מבין מה זה פריימוורק מבוסס קומפוננטות ומה זה Virtual DOM, ואולי אני אפילו מעיף מבט זריז בתיעוד של Vue ורואה שאין שם הפתעות גדולות. האם עכשיו אני "מבין" את Vue? ברור שלא, מעולם לא עבדתי איתו. אני כן יכול ללכת ל AI ולכתוב תוכניות ב Vue על בסיס הידע וההבנה שיש לי על ריאקט. אני יכול להיעזר ב AI כדי לדבג קוד Vue שמישהו אחר כתב עבורי, וגם לכתוב בדיקות אוטומטיות. אבל אף אחת מהמשימות האלה לא תעזור לי להבין טוב יותר את Vue. עכשיו בואו ננסה משחק אחר, נניח שבמקום לתת ל AI לכתוב את הקוד אני מבקש ממנו שיציע לי 10 תוכניות מאתגרות לכתוב ב Vue. אני הולך לכתוב את כולן ובדרך צולל לתוך התיעוד כדי להבין את הפרטים, וממשיך לכתוב עוד 20-30 תוכניות מתוך רעיונות שעלו לי בזמן שכתבתי את ה 10 הראשונות. אחרי כל תוכנית אני פונה ל AI ומבקש שיאתגר אותי - שיסביר מה לדעתו עוד אפשר לשפר בקוד. אחרי זה אחפש ברשת כל הצעה שלו ואנסה לממש אותה בעצמי. אחרי כמה שבועות או חודשים כאלה ברור שאני מבין את Vue די טוב, אבל גם ברור שעבדתי קשה בשביל ההבנה הזאת. וזה לא במקרה. כן ברור שאם יש לכם הבנה טובה של נושא, כזאת שהתאמצתם לבנות לאורך חודשים או שנים ושעלתה לכם בדם, יזע ודמעות אז AI יוכל לעזור לכם לכתוב קוד יותר מהר, וממילא תוכלו לכוון אותו בדיוק איך לעבוד כדי לא לעשות שטויות. וברור שעם AI אפשר לכתוב דברים גם כשלא מבינים כלום ולפעמים זה עובד, כמו שפעם היה אפשר להעתיק קטעי קוד מ Stack Overflow ולפעמים זה עבד. שני המקרים לא יהפכו אתכם למתכנתים מבריקים. הבנה זה אחלה. תשקיעו בלהבין. לא ב"להבין בגדול איך זה עובד ולהפעיל AI". להבין באמת. זאת הדרך הטובה ביותר לקבל ערך מ AI

ToCode
1 418
טיפ ויו: לאן נעלמה הטמפלייט הקוד הבא ב Vue מראה על המסך את הטקסט Hello Vue:
<template>
    <template v-if="true">
        <p>Hello Vue</p>
    </template>
</template>
אבל זה כבר לא מראה כלום:
<template>
    <template>
        <p>Hello Vue</p>
    </template>
</template>
בואו נראה למה. אלמנט template ב HTML ב HTML יש אלמנט בשם template שמאפשר בניה של Web Components. הקוד שנמצא בתוך טמפלייט ב HTML אמור להיטען מתוך קוד JavaScript כדי ליצור אלמנט ב DOM בשלב מאוחר יותר. הקוד הזה לכן הוא HTML תקני לגמרי, שללא JavaScript לא מראה כלום על המסך:
<my-counter></my-counter>
<my-counter></my-counter>
<my-counter></my-counter>

<template id="counter">
  <p>I'm a counter. value = <span class="value">0</span>
    <button class="inc">+1</button>
    <button class="dec">-1</button>
  </p>
</template>
אלמנט template ב Vue ב Vue לאלמנט template יש מספר תפקידים, השניים שרלוונטיים לדוגמה שלנו הם: 1. אלמנט template מאפשר לחבר יחד כמה אלמנטים עבור Directive אחד, לדוגמה להציג או להסתיר מספר אלמנטים לפי תנאי ב v-if או לחזור על מספר אלמנטים בלולאה ב v-for. 2. אלמנט template מאפשר בנייה של תגית template ב HTML. ואיך ויו מבין לאיזה משני התפקידים התכוונו? לפי ה Directives: אם יש ב template פקודות כמו v-if או v-for הוא נחשב לטמפלייט שמחבר כמה דברים יחד, אם אין אז הוא יופיע בתור אלמנט template ב HTML. ב ESLint יש כלל בשם vue/no-lone-template שמזהה שימוש ב template בלי directives ומעיר עליו. אפשר לקרוא עליו כאן: https://eslint.vuejs.org/rules/no-lone-template

ToCode
1 418
subject: "[פוסט חדש] #{@post.title}",
  template_data: recipients_with_tokens.map { |email, token| { "email" => email, "token" => token } }.to_json
)
ה token בכל קטעי הקוד האלה הוא פרמטר שיישתל לכל אימייל ובעזרתו נמענים יכולים להסיר את עצמם מהרשימה, כאשר לכל נמען יש token שונה. אפשר להוסיף איזה פרמטרים שרוצים שם ובשביל לשתול אותם בגוף המייל אנחנו צריכים רק לציין את שמם בתוך סוגריים מסולסלים כפולים לדוגמה:
<%= subscriptions_edit_url %>?token={{token}}&remove=all
תוכניות לעתיד על הדרך ועם כל ההתעסקות עם קוד המיילים הוספתי גם מנגנון של אימות כפול למי שרוצה לקבל את הפוסטים למייל, כך שעכשיו אם תרשמו את המייל שלכם בתיבת הרישום תקבלו הודעה שאתם צריכים לאמת את כתובת המייל, המערכת תשלח לכם אימייל ורק אחרי לחיצה על הכפתור שבמייל באמת אוסיף אתכם לרשימת התפוצה. בצורה כזאת אני מקווה לשלוח את המיילים ליותר אנשים רלוונטים ולפחות בוטים. בנוסף כנראה שהמעבר ל AWS שינה חלק מהגדרות השולח שלי ולכן חלק מכם אולי רואים את המיילים האלה מכתובת אחרת או אפילו בתיבת הספאם. אם זה המצב בבקשה תסמנו לגוגל או למי שצריך שהמייל אינו ספאם ותגררו אותו לתיבת הדואר הנכנס. לסיום אם מאיזושהי סיבה כתוצאה מכל השינויים האחרונים המייל ממני מפסיק להגיע אל תהיו מודאגים, לכו לאתר לקרוא את הפוסט וכתבו לי (אפשר להגיב לכל אחד מהמיילים הישנים שכן יש לכם בתיבה) כדי שאבדוק מה קרה ואם צריך אוסיף אתכם מחדש לרשימה.

ToCode
1 418
שליחת מייל מ Rails דרך AWS SES בימים האחרונים אני עובד על שינוי מנגנון שליחת המיילים של האתר ולשלוח דרך AWS במקום דרך Mailgun. זה קורה קצת בגלל העלאות מחיר של מיילגאן והרבה בגלל שרציתי לשחק עם עוד סרביס של AWS. כמו תמיד אצלם דברים מסתבכים והפוסט הזה מסכם את ההסתבכויות המרכזיות וגם תוכניות לעתיד לגבי המיילים. שליחת מייל יחיד באמצעות SMTP נתחיל עם מה שפשוט: גם Mailgun וגם SES (שירות המיילים של AWS) תומכים בפרוטוקול SMTP ודרך כפתורים בממשק ווב אפשר לקבל שם משתמש וסיסמה לשרת SMTP שישלח את המיילים בשבילכם. זה אומר שבריילס כל מה שאני צריך בשביל לשלוח מייל יחיד הוא לשנות שתי פקודות בקובץ הקונפיגורציה של הסביבה ולכתוב משהו כמו:
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  address:         "email-smtp.eu-north-1.amazonaws.com",
  port:            587,
  user_name:       Rails.application.credentials.dig(:aws, :ses, :user_name),
  password:        Rails.application.credentials.dig(:aws, :ses, :password),
  authentication:  "plain",
  enable_starttls: true,
  open_timeout:    5,
  read_timeout:    5 
}
מכאן כל שליחת מייל דרך תשתית המיילים המובנית ב Rails תשתמש בפרטי המשתמש והסיסמה שהגדרתי בקובץ הסיסמאות ותעבור דרך השרת של אמזון. שליחת מייל להרבה אנשים עם פרמטרים הסיפור היותר מסובך היה לשלוח מה שהם קוראים Bulk Email. זה המנגנון איתו אני שולח לכם את הפוסט הזה לאימייל. למיילגאן יש מנגנון מובנה שמאפשר בקריאת API אחת להוציא אימייל למאות אנשים. ב SES זה קצת יותר מורכב כי קריאת ה API היחידה שלהם מוגבלת ל 50 נמענים (לפי התיעוד, לא ניסיתי יותר). ועדיין מכל מה שחיפשתי ברשת שליחת מייל לכמות גדולה של נמענים תהיה הרבה יותר מהירה ויעילה דרך ה API שלהם. בשביל לשלב את ה SDK של SES בתוך ריילס היה צריך ליצור משהו שנקרא Delivery Method, שזה המחלקה שאומרת לריילס איך לשלוח מיילים. אם אתם זוכרים מהפיסקה הקודמת בשביל לשלוח מייל באמצעות SMTP כתבתי:
config.action_mailer.delivery_method = :smtp
זה אומר שבשביל לשלוח מייל בשיטה חדשה שלי אצטרך לבנות משהו שעושה את מה שמחלקת ה SMTP של ריילס עושה. בעזרת תיעוד ו AI כתבתי את הקוד הבא בתוך קובץ בשם ses_delivery_method בתיקיית lib:
class SesDeliveryMethod
  attr_accessor :settings

  def initialize(options = {})
    @settings = options.merge(
      access_key_id: Rails.application.credentials.dig(:aws, :ses, :access_key_id),
      secret_access_key: Rails.application.credentials.dig(:aws, :ses, :access_key_secret),
      region: 'eu-north-1'
    )
    @client = Aws::SESV2::Client.new(region: @settings[:region], credentials: aws_credentials)
  end

  def deliver!(mail)
    template_data = JSON.parse(mail['template-data'].value)


    options = {
      from_email_address: mail.from.first,
      # default_email_tags: tags,
      default_content: {
        template: {
          template_data: {"token" => ""}.to_json,
          template_content: {
            subject: mail.subject,
            text: mail.text_part.body.decoded,
            html: mail.html_part.body.decoded
          }
        }
      },
      bulk_email_entries: template_data.map do |recipient|
        {
          destination: { to_addresses: Array.wrap(recipient["email"]) },
          replacement_email_content: {
            replacement_template: {
              replacement_template_data: {"token" => recipient["token"]}.to_json
            }
          }
        }
      end
    }

    response = @client.send_bulk_email(options)
  rescue => err
    Rails.logger.info("SES Error")
    Rails.logger.error(err)
    raise err
  end

  private

  def aws_credentials
    Aws::Credentials.new(@settings[:access_key_id], @settings[:secret_access_key])
  end

  def template_data(mail)
    { "subject" => mail.subject, "body" => mail.body.raw_source }.to_json
  end
end

* Register with ActionMailer *
ActionMailer::Base.add_delivery_method :ses, SesDeliveryMethod
וכך בשביל לשלוח מייל דרך SES כל מה שצריך לעשות בקוד היישום הוא קריאת פונקציה שנראית בערך כך:
mail(
  to:,
  from: 'Ynon Perek <ynon@tocode.co.il>',
  delivery_method: :ses,

ToCode
1 418
גדילה אופקית היא לא הפיתרון (עדיין) כשכח מיחשוב התחיל להיות זול בזכות ירידת מחירים ווירטואליזציה גילינו שלהוסיף עוד מכונה יכול להיות הרבה יותר זול מאשר לחזק את השרת הקיים. חברות ענן איפשרו לנו להריץ קוד על עשרות או מאות מכונות במקביל, בלי שנצטרך לדעת כמה מכונות יש לנו או מה כל מכונה עושה. גן העדן של מיחשוב אוטומטי נראה קרוב מתמיד. רק שני דברים הפריעו לחגיגה: 1. ב Scale גדול שרתי ענן יוצאים מאוד יקרים בהשוואה לעבודה על שרתים פיזיים. 2. פיתוח מערכות מבוזרות הוא יותר מורכב מפיתוח מערכות על שרת יחיד. המעבר מארכיטקטורת שרת יחיד לארכיטקטורת ענן מסובך הרבה יותר מ"בואו נוסיף שרתים". עלינו לייצר מערכות ניהול, מערכות מעקב, לטפל בבעיות סינכרון ובכישלונות, לנהל טרנזאקציות או לבטל אותן. מהר מאוד תוספת שרתים דורשת תוספת של עוד ועוד שירותי ניהול וממשקים על אותם שרתים. קוד גרוע לא משתפר רק כי אנחנו מריצים אותו על יותר מכונות או על מכונות יותר חזקות. רוב הזמן אפילו קורה ההיפך. לפני שעוברים לענן בואו נוודא שעשינו כל מה שאפשר כדי לשפר את הביצועים של המערכת שלנו ברמת הקוד ואנחנו מנצלים את המשאבים שיש לנו באופן הטוב ביותר. אולי אחרי אופטימיזציה בקוד ובניהול משאבים נגלה שאנחנו בעצם מהירים מספיק ולא צריכים את הענן.