16.01.2022 15 min read

البدء مع AWS Lambda وإطار عمل Serverless

بواسطة Mirza Leka

تعلّم كيفية تطوير واختبار ونشر دوال AWS Lambda باستخدام إطار عمل Serverless - يغطي IAM وCloudFormation وAPI Gateway وCloudWatch وTypeScript والوسطاء (middlewares) وخطوط أنابيب CI/CD.

AWS Lambda

AWS Lambda وإطار عمل Serverless هما أسرع طريقة للبدء في عالم Serverless!

سيعلّمك هذا الدليل كيفية تطوير واختبار ونشر دوال AWS Lambda باستخدام إطار عمل Serverless، وأيضًا التعرّف على إدارة هوية الوصول (IAM) وCloudFormation وS3 Bucket وAPI Gateway وCloudWatch وخدمات رائعة أخرى.

لهذا العرض التوضيحي، سنستخدم Node.js لبساطتها، وأوقات بدء تشغيل باردة (Cold Starts) منخفضة، ونظام بيئي ضخم (npm) من الأدوات المتاحة لنا. سنتعلّم كيفية إنشاء واجهات برمجة تطبيقات، وتفعيل CORS، وتشغيل Serverless دون اتصال، ومعالجة الأخطاء، واستخدام الوسطاء، والتكامل مع TypeScript، وإعداد النشر المستمر والمراقبة في بيئة الإنتاج. يمكنك الحصول على الكود الكامل على GitHub.

ما هو Serverless؟

الحوسبة اللاخادمية (Serverless) هي طريقة لتوفير خدمات الواجهة الخلفية على أساس الاستخدام الفعلي. يتيح مزوّد Serverless للمستخدمين كتابة الكود ونشره دون عناء القلق بشأن البنية التحتية الأساسية. كمطورين، نهتم فقط بالكود الذي نكتبه، بينما تُوسَّع الخوادم وتُدار من قبل مزوّدي الخدمة السحابية.

الإعداد المعتاد لدوال Lambda في AWS يدوي. ما يتيحه لنا إطار عمل Serverless هو كتابة كل الخطوات اليدوية كبنية تحتية كشيفرة (IaaC)، بالإضافة إلى إدارة الموارد ونشر دوالنا بسهولة.

بعض خصائص دوال Lambda:

  • تُدار الخوادم وتُوفَّر من قبل مزوّد الخدمة السحابية لديك (ومن هنا جاء الاسم، Serverless)
  • لا حاجة للدفع مسبقًا. تدفع مقابل كل استدعاء. بالإضافة إلى وجود مستويات مجانية.
  • صغيرة وبسيطة. رائعة لتفريغ بعض أجزاء العمل من خادمك الرئيسي (المدفوعات، إرسال البريد الإلكتروني، ضغط الصور، استخلاص بيانات الويب، إلخ.)
  • تتوسع تلقائيًا وبلا حدود.
  • يمكن دمجها بسهولة مع خدمات أخرى مثل S3 وDynamoDB وCognito وSQS، إلخ.
  • تُستدعى بالأحداث (HTTP، Sockets، S3، إلخ.)
  • تحتاج دوال Lambda وقتًا للتشغيل (الإحماء) عند أول استخدام - يُسمى هذا بدء التشغيل البارد (Cold Start).

جمال إطار عمل Serverless هو أنه غير حصري لـ AWS. يدعم عدة مزوّدي خدمة سحابية بما فيهم Azure وGCP وAWS، إلخ، بالإضافة إلى عدة لغات مثل Python وNode.js وC# وJava وغيرها.

المتطلبات الأساسية

تأكد من امتلاك حساب AWS، وAWS-CLI، وNode.js مثبَّتة على جهازك. يمكنك التحقق من الأخيرة بتشغيل aws --version وnode -v في الطرفية لديك.

AWS Services

إعداد دور IAM

للبدء، نحتاج لتوليد أذونات لمستخدم Lambda لدينا.

الخطوة 1: سجّل الدخول إلى حساب AWS لديك وابحث عن IAM.

IAM Search

الخطوة 2: انقر على Users (ضمن Access Management) في الشريط الجانبي على اليسار، ثم انقر على زر Add users على اليمين.

IAM Users

الخطوة 3: في الشاشة التالية، أدخل اسم المستخدم (أي اسم نريده) وانقر على خانة الاختيار Access key — Programmatic access. ثم التالي.

IAM User Setup

الخطوة 4: في شاشة الأذونات، انقر على Attach existing policies directly، ثم انقر على سياسة AdministratorAccess وانقر على التالي. لأغراض هذا العرض التوضيحي، سنستخدم سياسة Admin.

IAM Permissions

الخطوة 5: نصل إلى صفحة Tags التي يمكننا تخطيها. ثم نصل إلى الصفحة الأخيرة وننقر على زر Create user.

IAM Create User

الخطوة 6: في الشاشة الأخيرة، نحصل على مفتاح الوصول والمفتاح السري اللذين سنستخدمهما للمصادقة مع إطار عمل Serverless. هذان مهمان للغاية.

ستُظهر AWS بيانات الاعتماد هذه مرة واحدة فقط ولن تظهرها مرة أخرى أبدًا. إذا فقدناها، سنحتاج لتوليد بيانات جديدة. لا تشاركها مع أي شخص.

IAM Credentials

انتهينا من موقع AWS في الوقت الحالي. الآن بعد أن أصبح لدينا مستخدم AWS Admin، افتح طرفية وثبّت إطار عمل Serverless على جهازك:

$ npm i -g serverless

بعد ذلك، سجّل الدخول إلى Serverless عبر الطرفية باستخدام بيانات الاعتماد التي وَلَّدناها للتو. (sls اختصار لـ serverless)

$ sls config credentials --provider aws --key our_key --secret our_secret

أصبحت هذه ثابتة الآن على جهازنا. كل عملية نشر لـ AWS سنقوم بها ستذهب مباشرة إلى حساب AWS لدينا. إذا أردت تغيير بيانات الاعتماد هذه، انتقل إلى:

  • ~/.aws/credentials على Linux أو macOS
  • C:\Users\USERNAME\.aws\credentials على Windows

إذا كان لديك عدة حسابات AWS، يمكنك بسهولة التبديل من نشر Lambda من حساب إلى آخر فقط عن طريق تبديل بيانات الاعتماد هذه.

Serverless Credentials

إنشاء أول دالة Serverless لنا

للبدء، يمكننا تشغيل serverless --help لرؤية قائمة الأوامر المتاحة لنا.

Serverless Help

للحفاظ على البساطة، سنستخدم أمر sls (اختصار لـ serverless) من الآن فصاعدًا.

Serverless Startup Options

نختار بعدها أحد الخيارات ونضغط على Enter لتوليد مشروع. طريقة أخرى للقيام بذلك هي إنشاء قالب AWS Node.js وتحديد المسار (المجلد) الذي سيعيش فيه تطبيقنا اختياريًا.

$ sls create --template aws-nodejs --path my-first-serverless-app

إذا دخلنا إلى المجلد الذي وَلَّدناه للتو، سنجد ملفين مثيرين للاهتمام: handler.js وserverless.yml.

Generated Project
'use strict';

module.exports.hello = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'Go Serverless v1.0! Your function executed successfully!',
        input: event,
      },
      null,
      2
    ),
  };
  // Use this code if you don't use the http event with the LAMBDA-PROXY integration
  // return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};

الأجزاء الرئيسية هنا هي عبارة module.exports (التي تستخدم صياغة CommonJS) واسم دالة Lambda hello التي تعيد استجابة معينة. هذه دالة معالِج (handler) تستدعيها AWS، لذا يجب تصديرها. يجب أن يكون كل الكود المتعلق بالدالة بداخلها.

الملف الآخر لدينا هو ملف serverless.yml. هنا نكتب البنية التحتية لتطبيقنا.

serverless.yml Preview

لنفصّلها:

  • Services — اسم خدمتنا التي ستُولَّد على AWS.
  • Provider — حدد مزوّد الخدمة السحابية (AWS، Azure، إلخ.) بالإضافة إلى بيئة وقت التشغيل.
  • Functions — يمكن أن يكون لدينا دالة واحدة أو أكثر تستدعيها AWS. لكل دالة اسم ومعالِج. handler.hello يمثّل اسم الملف واسم الدالة المُصدَّرة.
  • Events — مُحفّزات Lambda التي ستشغّل الدالة. يمكن تفعيل Lambda عبر HTTP، أو حدث Socket، أو رفع S3، إلخ.
  • Resources — حدد الموارد الإضافية التي تستخدمها دالتك، مثل DynamoDB وS3 Bucket وCognito، إلخ.
  • Environment — أعدّ متغيرات البيئة (المفاتيح السرية) المستخدَمة من قبل الخدمة بأكملها أو لكل دالة.
  • Plugins — إضافات خاصة لبيئتك، مثل Serverless-Offline للاستدعاء المحلي أو إضافة Serverless TypeScript.

اقرأ المزيد عن ملف serverless.yml.

نشر واختبار دالتنا

بما أنني أعيش في أوروبا، سأغيّر المنطقة إلى الأقرب إليّ.

# you can overwrite defaults here
stage: dev
region: eu-central-1

ضع في اعتبارك أنه بمجرد توليد دالة Lambda في منطقة معينة، ستعيش في تلك المنطقة. يمكنك تبديل المناطق في شريط التنقل الرئيسي على AWS Console.

AWS Console Home

الآن لننشر (deploy).

$ sls deploy
Serverless Deployment

بما أن هذا أول نشر لنا، سيستغرق أطول وقت. الآن لننتقل إلى AWS لنرى ما لدينا.

عند النشر، يولّد ملف serverless.yml مكدّس CloudFormation ويربط جميع الموارد التي تستخدمها دالتنا.

إذا بحثنا عن CloudFormation في AWS Console وانتقلنا إليه، سنرى دالتنا (طالما أننا في المنطقة الصحيحة).

CloudFormation Search
CloudFormation Dashboard

إذا نقرنا على دالتنا في الجدول، ستفتح نافذة جديدة. ثم انقر على تبويب Resources لعرض جميع الموارد المرتبطة بدالتنا - أدوار IAM، وحاوية S3 التي نُشرت فيها Lambda لدينا، والرابط إلى الدالة نفسها.

CloudFormation Stack

النقر على الدالة (المستطيل الأزرق) سينقلنا إلى شاشة AWS Lambda.

Lambda Function Screen

بالتمرير للأسفل، يمكننا رؤية كود دالتنا. يمكننا اختبارها بالنقر على زر Test البرتقالي.

handler.js Code

سيُظهر هذا نافذة منبثقة لكتابة تفاصيل حول اختبارنا. ندخل Event name ونترك كل شيء آخر على الإعدادات الافتراضية.

Test Setup

الآن انقر Save في النافذة المنبثقة ثم على زر Test في شريط التنقل.

Test Button

اختر الحدث الذي أنشأناه للتو وانقر على زر Test البرتقالي مرة أخرى. نرى أن كل شيء سار على ما يرام والكود المُعاد هو ما كتبناه في الدالة.

Test Result

لنغيّر كود دالتنا قليلًا وننشرها مرة أخرى.

'use strict';
module.exports.hello = async (event) => {
  return 'Hello World!'
};

معامل event هو كائن يعيد معلومات عن الطلب، مثل طريقة الطلب، والمحتوى، ومعاملات الاستعلام، إلخ.

هذه المرة لم نغيّر بنيتنا التحتية، لذا يمكننا استخدام اختصار لنشر كود الدالة فقط:

sls deploy -f hello  // -f is shorthand for function
Faster Deployment

بعدها يمكننا التوجه إلى AWS Console واختبار كودنا مرة أخرى.

Latest Changes
Recently Deployed Code
Test Passed

الآن قد تتساءل هل يمكننا استخدام وحدات NPM في دوال Lambda لدينا؟ نعم، يمكننا! لكن، هناك أمور يجب مراعاتها:

  • لا يمكن لـ Lambda تشغيل بناء NPM نيابة عنا، لذا يجب علينا رفع (نشر) مجلد node_modules مع الدالة.
  • دوال Lambda مقيَّدة بـ 50 ميغابايت. إذا تجاوز مجلدنا هذا الحد، يمكننا عزل المشروع بأكمله في حاوية (Dockerize) ونشره على AWS ECS.

بدلًا من ذلك، يمكننا رفع كودنا إلى حاوية S3 وربط Lambda بـ S3 - وهو ما يقوم به إطار عمل Serverless بالفعل نيابة عنا خلف الكواليس.

S3 Manual Link

من هنا يمكننا إضافة محفّزات لدالة Lambda لدينا، وإعداد المهلة الزمنية (timeout)، وخيارات إعداد أخرى، لكننا لن نفعل ذلك بهذه الطريقة. نستخدم إطار عمل Serverless لإعداد البنية التحتية - حتى لا نحتاج للنقر يدويًا في كل مكان.

إنشاء واجهات برمجة تطبيقات باستخدام إطار عمل Serverless

لاستدعاء دالة Lambda لدينا من العميل لدينا (تطبيق ويب، تطبيق جوال، أو Postman)، نحتاج لإعداد نقطة دخول - API Gateway.

API Gateway

للقيام بذلك، نعدّل ملف serverless.yml ونضيف HTTP API في قسم الأحداث.

functions:
  hello:
    handler: handler.hello
    events: 
      - httpApi: 
          path: /
          method: get
$ sls deploy
Deployment with API Gateway

هذه المرة نحصل أيضًا على نقطة نهاية API كاستجابة. إذا زرنا هذا الرابط في المتصفح، يجب أن يعيد الاستجابة الصحيحة.

URL of Deployed Function

طريقة أخرى لاستدعاء هذه الواجهة البرمجية هي مباشرة من الطرفية:

$ sls invoke -f hello -l  // where -f stands for function and -l for logs
Invoking from Terminal

إذا انتقلنا إلى دالة Lambda لدينا في AWS Console، يمكننا أن نرى أن الدالة الآن لديها رابط إلى API Gateway.

Lambda with API Gateway

إضافة طلب POST

لنوسّع دالتنا بمسار آخر. يمكننا استرجاع طريقة HTTP من كائن الحدث ثم كتابة التنفيذ. هنا أسجّل أيضًا كائن الحدث في وحدة التحكم.

module.exports.hello = async (event) => {
  console.log('event :>> ', event);

  if (event.requestContext.http.method === 'POST') {
    return {
      statusCode: 201,
      body: JSON.stringify({
        message: 'Resource created!',
      }),
    };
  }

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Resource retrieved!',
    }),
  };
};

الآن لنعدّل ملف YAML:

functions:
  hello: 
    handler: handler.hello  
    events:
      - httpApi:
           path: /
           method: get
      - httpApi:
           path: /create
           method: post

ما فعلناه هنا هو إنشاء مسارين في نفس الملف. يمكننا أيضًا إنشاء دوال متعددة (ملفات معالِج) وفصل كل مسار إلى دالته الخاصة. اقرأ المزيد عن أنماط بنية Serverless.

سنسمح أيضًا بـ CORS في قسم المزوّد بحيث يمكن للجميع الوصول إلى مساراتنا:

provider:
  name: aws
  runtime: nodejs12.x
  httpApi:
    cors: true

لنشغّل أمر النشر مرة أخرى:

$ sls deploy

الآن لدينا نقطتا نهاية يمكننا التفاعل معهما.

Two Endpoints

لنختبر هذين المسارين في Postman:

Testing GET Route

خطوة مهمة واحدة لإرسال طلب POST هي ضبط ترويسة Content-Type على application/json.

Setting Headers
Testing POST Route

تطبيق الويب:

const URL = `https://nkipcuru5g.execute-api.eu-central-1.amazonaws.com`;

fetch(URL, options)
  .then((response) => {
    return response.json();
  })
  .then((jsonObject) => {
    console.log(jsonObject) // {message: 'Resource retrieved!'}
  })
  .catch((error) => {
    console.error(error);
  });
const options = {
  method: 'POST',
  data: {},
  headers: {
    'Content-Type': 'application/json'
  }
};

const URL = `https://nkipcuru5g.execute-api.eu-central-1.amazonaws.com/create`;

fetch(URL, options)
  .then((response) => {
    return response.json();
  })
  .then((jsonObject) => {
    console.log(jsonObject) // {message: 'Resource created!'}
  })
  .catch((error) => {
    console.error(error);
  });

المراقبة باستخدام CloudWatch وServerless Console

CloudWatch

لفهم ما يجري في دوالنا بشكل أفضل، يمكننا الانتقال إلى خدمة CloudWatch. أسهل طريقة لإيجاد سجلات دالتنا هي الانتقال إلى شاشة الدالة والنقر على تبويب Monitor.

Lambda Monitor Tab

يفتح هذا قائمة من الخيارات، لكن ما يهمنا هو خيار View logs in CloudWatch.

CloudWatch Logs List

ثم نصل إلى الصفحة حيث يمكننا رؤية السجلات بالترتيب والنقر على كل منها لمعاينة ما حدث في تلك اللحظة.

Log Events

هنا يمكننا رؤية قائمة الأحداث التي وقعت وحتى كائن event لدينا الذي سجّلناه سابقًا في وحدة التحكم.

Expanded Log

طريقة أخرى لمراقبة السجلات هي استخدام Serverless Console المقدَّم من إطار عمل Serverless. لتفعيله، ببساطة شغّل:

$ sls --console

أولًا، سيطلب منك تفعيل الوصول إلى AWS وإنشاء دور IAM.

Serverless Console Setup

بعدها نحتاج للتسجيل أو تسجيل الدخول إلى Serverless Dashboard المفتوح في المتصفح.

Serverless Console Login

بعد بضع دقائق من الإعداد، ننتقل إلى Serverless Console ونجد خدمتنا. نحتاج أيضًا لتفعيل Logs وTraces ووضع Dev.

Serverless Console Config

ثم نجري بضعة طلبات (GET أو POST) وستظهر المقاييس تقريبًا في الوقت الفعلي.

Serverless Console Metrics

تشغيل دوال Serverless دون اتصال

تطوير الميزات ثم النشر فقط لتتمكن من اختبارها في بيئة الإنتاج يستهلك وقتًا هائلًا. أنشأ فريق إطار عمل Serverless ميزة Serverless Offline التي تتيح لنا اختبار كودنا محليًا.

للبدء نحتاج لتهيئة مشروع NPM:

$ npm init -y
Initialized NPM Project

سيُنشئ هذا ملف package.json في مجلد المشروع.

Default package.json

الآن لنثبّت serverless-offline كاعتمادية تطوير:

$ npm i --save-dev serverless-offline

الخطوة التالية هي إضافة إضافة serverless-offline إلى ملف serverless.yml لدينا:

plugins:
  - serverless-offline

مع وجود هذا، يمكننا استخدام أمر المساعدة للتحقق من أن كل شيء مُعدّ بشكل صحيح:

$ serverless-offline --help
Serverless Offline Commands

الآن نشغّل Serverless محليًا:

$ sls offline start
Serverless Offline Running

الآن لنختبره باستخدام Postman:

Testing Locally

ويمكننا رؤية طلباتنا مسجَّلة في وحدة التحكم.

Terminal Logs

إذا احتجنا لإجراء تغيير في الكود، نوقف الخادم ببساطة (CTRL/CMD + C) ونشغّله مرة أخرى. لعشّاق NPM، يمكننا ربط هذا الأمر بسكريبت في ملف package.json:

{
  "name": "my-first-serverless-app",
  "version": "1.0.0",
  "description": "",
  "main": "handler.js",
  "scripts": {
    "dev": "serverless offline start"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "serverless-offline": "^12.0.3"
  }
}

الآن يمكننا تشغيل:

$ npm run dev
# or if you're using yarn
$ yarn dev

استخدام متغيرات البيئة

أحيانًا في تطبيقاتنا، نريد إضافة كلمات مفتاحية خاصة، مثل مفاتيح واجهة برمجة تطبيقات خارجية، وأسرار JWT، وسلاسل اتصال قاعدة البيانات، إلخ. يمكننا تحديد متغيرات البيئة في قسم المزوّد من ملف YAML لدينا:

provider:
  name: aws
  runtime: nodejs12.x
  httpApi:
    cors: true
  environment:
    DUMMY_API_KEY: 'Dummy value'

بعدها يمكننا الوصول إلى هذا المتغيّر باستخدام كائن process.env في handler.js:

module.exports.hello = async (event) => {
  console.log('Dummy env var :>> ', process.env.DUMMY_API_KEY);
  ...
}

إذا أجرينا طلبًا آخر إلى الخادم، يجب أن نرى هذا السجل مطبوعًا في الطرفية.

Environment Variable Log

كان متغيّر البيئة المضبوط سابقًا عامًا للخدمة بأكملها، لكن يمكننا أيضًا ضبط متغيرات لكل دالة ستتجاوز المتغيرات العامة (إذا كان لها نفس الاسم):

functions:
  hello:
    handler: handler.hello
    environment:
      DUMMY_API_KEY: 'New Dummy value' # will override the previous
    events:
      - httpApi:
          path: /
          method: get
      - httpApi:
          path: /create
          method: post

بمجرد ضبط المتغيرات، يمكننا إيجادها في شاشة AWS Lambda، تحت Configuration > Environment variables.

Lambda Env Vars

اقرأ المزيد عن متغيرات البيئة في إطار عمل Serverless.

معالجة الأخطاء

Error Handling

في مرحلة ما، قد نريد التحقق من صحة إدخال المستخدم. هنا نعدّ شرطًا للتحقق من صحة محتوى الطلب، وإذا كان غير صالح، نعيد خطأ.

module.exports.hello = async (event) => {
    
  if (event.requestContext.http.method === 'POST') {
      
    if (!event.body) {
      console.log('gonna throw error')
      throw new Error('Body field is required')
    }
      
    return {
      statusCode: 201,
      body: JSON.stringify({
        message: "Resource created!",
      }),
    };
  }
  ...
}

بعدها نشغّل هذا باستخدام Postman ونلقي نظرة على الطرفية.

Sending Request Without Body
Errors in Terminal

لم يسر ذلك كما هو متوقع. لدينا مشكلتان:

  • صنف Error المدمج في JavaScript لا يمتلك رموز حالة HTTP
  • هذا الخطأ غير مُعالَج (وتطبيقنا معطَّل بشكل محتمل)

لحل المشكلة الأولى، سنثبّت ونُعدّ حزمة NPM http-errors:

$ npm i http-errors
const createError = require('http-errors'); // importing package
      
module.exports.hello = async (event) => {
          
  if (event.requestContext.http.method === 'POST') {
    if (!event.body) {
      throw createError(400, `Field "body" is required!`); // using it
    }

الآن نحصل على خطأ أكثر قابلية للقراءة.

Better Error

لكن، لا يزال رمز الحالة 200. لإصلاح هذا، نحتاج لتقديم منطق معالجة الأخطاء.

Status Code Issue

الوسطاء (Middlewares)

بالانتقال إلى المشكلة الثانية - سنعزز دالة Lambda لدينا بالوسطاء. لهذا الغرض، لدينا حزمة NPM تُسمى Middy.

$ npm i @middy/core @middy/http-error-handler @middy/error-logger

ثبّتنا للتو ثلاث حزم:

  • غلاف دالة Middy لـ Lambda
  • معالِج الأخطاء
  • مسجّل الأخطاء (بحيث لا نحتاج لتسجيل الأخطاء يدويًا)

أولًا، نغلّف الدالة بأكملها بـ middy. بعدها نستفيد من دالة use()، ونرفقها في نهاية دالة middy ونمرر كل وسيط نريد استخدامه.

const middy = require('@middy/core');
const httpErrorHandler = require('@middy/http-error-handler');
const errorLogger = require('@middy/error-logger');

module.exports.hello = middy(async (event) => {
  ...
})
  .use(httpErrorHandler())
  .use(errorLogger())

لنرسل طلبًا غير صالح.

Middy Error

يُرمى الخطأ كما هو متوقع، لكن التطبيق لا يزال يعمل. إذا نظرنا في Postman، يمكننا أن نرى أن رمز حالة HTTP هو الآن 400 (Bad Request).

400 Status Code

وبالطبع، إذا أرسلنا المحتوى في الطلب (حتى لو كان فارغًا)، ستكون الاستجابة ناجحة.

Successful with Body

وسيط Middy أكثر من مجرد معالِج أخطاء. يمكننا استخدامه لتوحيد الطلبات، وإخفاء الترويسات غير المرغوبة، وتخزين الاستجابة مؤقتًا، وإعداد CORS، وSSM، إلخ. اقرأ المزيد عن Middy.

مهم: أحدث نسخة من Middy (4.0.9) غير متوافقة مع Node.js 12. إذا كنت تستخدم Node.js 14 أو أقل، استخدم نسخ الحزم هذه:

"@middy/core": "^2.5.7",
"@middy/error-logger": "^2.5.7",
"@middy/http-error-handler": "^2.5.7"

لاستخدام أحدث نسخة من Middy، انتقل إلى قسم المزوّد من ملف YAML وغيّر نسخة Node.js runtime إلى 16:

provider:
  name: aws
  runtime: nodejs16.x

بعدها انشر وكل شيء يجب أن يعمل كما هو متوقع:

$ sls deploy
Middy 4 Production Test

TypeScript

لنضف أيضًا أمان الأنواع (type safety) إلى دوالنا. سنبدأ بتثبيت بعض الحزم:

$ npm i aws-lambda
$ npm i --save-dev @types/aws-lambda @types/node @types/http-errors serverless-plugin-typescript typescript

لسنا بحاجة لإعادة تثبيت Middy لأن هذه الحزمة تدعم TypeScript جاهزة. نحتاج أيضًا لتهيئة TypeScript للحصول على ملف tsconfig.json:

tsc --init

وأضف الإضافة التي ثبّتناها للتو (serverless-plugin-typescript) إلى قسم plugins في ملف YAML لدينا:

plugins:
- serverless-offline
- serverless-plugin-typescript

جمال هذه الإضافة هو أنها ستقوم ببناء TypeScript نيابة عنا. عند التشغيل، ستنشئ مجلد .build محدَّدًا في ملف tsconfig.json. الآن يمكننا إعادة تسمية ملف handler.js إلى handler.ts والبدء بإعادة الهيكلة.

tsconfig.json

{
  "compilerOptions": {
    "preserveConstEnums": true,
    "strictNullChecks": true,
    "sourceMap": true,
    "allowJs": true,
    "target": "es2017",
    "outDir": ".build",
    "moduleResolution": "node",
    "lib": ["es2017"],
    "rootDir": "./",
    "strict": true,
    "module": "commonjs",
    "esModuleInterop": true
  },
  "include": ["**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

handler.ts

import { APIGatewayEvent } from 'aws-lambda';
import middy from '@middy/core';
import httpErrorHandler from '@middy/http-error-handler';
import errorLogger from '@middy/error-logger';
import createError from 'http-errors';

export const hello = middy(async (event: APIGatewayEvent) => {
  if (event.requestContext.routeKey?.includes('POST')) {
    if (!event.body) {
      throw createError(400, 'Field "body" is required!');
    }

    return {
      statusCode: 201,
      body: JSON.stringify({
        message: 'Resource created!',
      }),
    };
  }

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Hello AWS Lambda TypeScript on Serverless Framework!',
    }),
  };
})
  .use(httpErrorHandler())
  .use(errorLogger());

serverless.yml

service: my-first-serverless-app

frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs16.x
  httpApi:
    cors: true
  environment:
    DUMMY_API_KEY: 'Dummy value'

  stage: dev
  region: eu-central-1

functions:
  hello:
    handler: handler.hello
    environment:
      DUMMY_API_KEY: 'New Dummy value'
    events:
      - httpApi:
          path: /
          method: get
      - httpApi:
          path: /create
          method: post

plugins:
  - serverless-offline
  - serverless-plugin-typescript

لنشغّل npm run dev:

TypeScript Terminal
TypeScript

رفع التغييرات إلى Git وإنشاء خط أنابيب النشر

بدلًا من النشر يدويًا في كل مرة نجري فيها تغييرًا، نريد نشر تغييراتنا إلى AWS في كل مرة نرفع فيها كودنا إلى Git.

أولًا، سجّل الدخول إلى حساب GitHub لديك وأنشئ مستودعًا جديدًا. في الشاشة التالية، أعطِ المستودع اسمًا وانقر على زر Create repository.

Create New Repository

هيّئ المستودع في مجلد مشروعك بتشغيل git init، والذي سيُبرز بعدها جميع الملفات غير المدرجة في .gitignore.

Git Init
git init
git add .
git commit -m "initial commit"
git branch -M main
git remote add origin https://github.com/USERNAME/Your-Repository.git
git push -u origin main

الآن يجب أن يُنشَر كودنا إلى Git.

إنشاء خط أنابيب النشر

هل تتذكر مفتاح الوصول والمفتاح السري اللذين وَلَّدناهما في صفحة IAM؟ سنستخدم هذين المفتاحين ونخزّنهما في مستودع GitHub لدينا للسماح بعمليات النشر إلى AWS.

GitHub Secrets

نبدأ بإضافة أسرار (secrets) إلى مستودع GitHub لدينا. في صفحة المستودع، انقر على Settings في التنقل العلوي، ثم على Secrets، Actions، ثم على زر New repository secret.

Adding Secrets

ثم أضف كل واحد على حدة.

Added Secrets

عد إلى محرر الكود، في مجلدك الجذر، أنشئ مجلد .github، ثم مجلد workflows بداخله، ثم ملف main.yml (.github/workflows/main.yml).

GitHub Actions File

يمكننا إيجاد مثال على ملف main.yml في صفحة Serverless GitHub Actions. يجب أن يبدو هكذا:

name: Deploy Lambda to AWS

on:
  push:
    branches:
      - main

jobs:
  deploy:
    name: deploy
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16.x]
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci  # install dependencies from package.json file
      - name: serverless deploy
        uses: serverless/[email protected]
        with:
          args: deploy
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

الآن نُدرج هذه التغييرات ونرفعها إلى Git:

git add .
git commit -m "Added Github deployment actions"
git push

عند الرفع، يجب أن نرى مهمة جديدة تعمل تحت تبويب Actions في مستودع GitHub.

GitHub Actions Running

يمكننا النقر عليها لتوسيعها.

GitHub Actions Expanded

بمجرد اكتمال النشر، وسّع قسم serverless deploy وخذ نقطتي نهاية API:

API Endpoints from Deployment

لنختبر إحداهما في Postman للتحقق من أنها تعمل.

Testing in Production

إزالة دالة Lambda

Remove Section

لإزالة هذه الدالة من AWS، ببساطة شغّل:

$ sls remove
Removing Function from AWS

تأكد من تشغيل هذا الأمر لكل مرحلة (dev، production، إلخ.). يمكنك بناء دالة جديدة فقط بإعادة نشر الدالة الموجودة.

ملاحظات حول Lambda وإطار عمل Serverless

  • إذا أدخلنا أمرًا مهجورًا (deprecated)، سيعرض إطار عمل Serverless تحذيرات في الطرفية عند النشر.
  • إذا أدخلنا أمرًا غير صالح، سيرمي الإطار خطأً ولن ينشر.
  • فصل المسارات لكل دالة يتيح لنا نشر الدالة فقط (جزء من الكود) التي تغيّرت.
  • التصحيح صعب لأنك لا تستطيع الاتصال عبر SSH بالجهاز لرؤية ما حدث خطأً. السجلات هي أفضل خيار لك لإيجاد المشكلات.
  • لا يُنصَح باستخدام دوال Lambda مع الأنظمة التي تكون فيها الديمومة (persistence) مهمة. بسبب التوسع التلقائي، لا يمكننا أبدًا التأكد من النسخة التي نستخدمها، ومع طلبات القراءة والكتابة المتكررة، هناك احتمال أن تصبح البيانات غير متسقة (حالات تسابق - Race Conditions).

المزيد من ميزات Serverless

الأوامر

# Authenticate with AWS
sls config credentials --provider aws --key access_key --secret secret

# Create Project
sls
sls create -t aws-nodejs -p my-app

# Deploy Project
sls deploy             # deploy whole project
sls deploy -f hello    # deploy just a specified function

# Local Testing (with Serverless-Offline installed and plugin set)
sls offline start      # starts the app on localhost:3000

# Remove Project
sls remove             # Remove the project from AWS

مجموعة الأدوات: يمتلك Visual Studio Code إضافة Serverless-IDE تساعد في كتابة ملفات serverless.yml.

المراقبة: رأينا بالفعل كيفية مراقبة المشكلات باستخدام CloudWatch وServerless Console. خدمة رائعة أخرى هي Dashbird، التي تمنحك نظرة عامة مفصلة جدًا على دوال Lambda لديك وترسل تنبيهات بريد إلكتروني متكررة عند حدوث خطأ ما.

Dashbird Dashboard
Dashbird Errors

من السهل جدًا أيضًا إعدادها مع حساب AWS لديك. التوثيق الرسمي.

Dashbird Setup

لمعرفة المزيد

كانت هذه مجرد نظرة صغيرة على عالم Serverless. أشجعك بالتأكيد على استخدام AWS Lambda بنفسك وتجربة الأشياء المتاحة.

  • توثيق AWS Lambda
  • توثيق إطار عمل Serverless
  • Serverless Console
  • منتدى إطار عمل Serverless
  • دورة مكثفة عن إطار عمل Serverless مع AWS Lambda
  • قائمة تشغيل Youtube لإطار عمل Serverless
  • إطار عمل Serverless على AWS Youtube
  • تطبيق Express.js مع Serverless
  • Node.js Serverless TypeScript

يمكنك إيجاد الكود بأكمله في مستودع AWS-Lambda-Starter الخاص بي. شكر خاص لشريكي في الجريمة Dzenan Dzafic.

قراءات إضافية عرض الكل
الخطوة التالية

طبّق هذه الأفكار على منتجك

يساعد استوديو فالنس الفرق ذات المخاطر العالية على تحويل الوضوح الهندسي إلى تسليم منتج معياري.