"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > AWS Lambda مع Go، نموذج أولي

AWS Lambda مع Go، نموذج أولي

تم النشر بتاريخ 2024-11-06
تصفح:802

تصوير لوكاش فاناتكو على Unsplash

مقدمة

كانت Go دائمًا إحدى اللغات المفضلة لدي نظرًا لبساطتها. قررت مؤخرًا معرفة ما يلزم لإنشاء مشروع بسيط بدون خادم مع وظائف lambda المكتوبة بلغة Go. كنت مهتمًا بالأدوات وتجربة المطورين.

هدف

أريد إنشاء واجهة برمجة تطبيقات REST، التي تستخدم قاعدة بيانات postgres كطبقة بيانات. متطلباتي الأولية هي ما يلي

  • المواصفات التي سيتم تحديدها باستخدام OpenApi والنماذج التي يتم إنشاؤها منها
  • استخدام AWS SAM
  • يتم التعامل مع كل نقطة نهاية بواسطة وظيفة لامدا منفصلة
  • التنمية المحلية سهلة قدر الإمكان
  • نفس الشيء بالنسبة للنشر

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

ستحتاج إلى تثبيت Go، بالإضافة إلى AWS SAM. إذا قمت بنشر مشروعك على AWS، فقد تتم محاسبتك على الموارد التي تقوم بإنشائها، لذا تذكر مسح الموارد الخاصة بك عندما لا تحتاج إليها.
بالنسبة لقاعدة البيانات أستخدم Supabase

بناء المشروع

الكود موجود في هذا الريبو

لنبدأ بتشغيل sam init. لقد اخترت قالب Hello World، استخدمه مع بيئة al.2023 المتوفرة. في السابق كان هناك وقت تشغيل مُدار لـ Go، ولكن في الوقت الحاضر تم إهماله.

مخطط OpenApi

إن تعريف مخطط واجهة برمجة التطبيقات (API) على أنه مواصفات OpenApi له بعض المزايا الواضحة. يمكننا استخدامه لإنشاء الوثائق وإنشاء العملاء والخوادم وما إلى ذلك. كما أستخدمه أيضًا لتحديد شكل بوابة AWS HttpApi.

مخططي واضح ومباشر. الجزء الوحيد المثير للاهتمام هو خاصية x-amazon-apigateway-integration، والتي تسمح بالاتصال بتكامل lambda. الإعداد حيادي للغة.

يمكنك العثور على ملف المخطط في الريبو

قالب سام

HttpApi والسرية

# template.yaml
# ....
ItemsAPI:
    Type: AWS::Serverless::HttpApi
    Properties:
      StageName: Prod
      DefinitionBody:
        'Fn::Transform':
          Name: 'AWS::Include'
          Parameters:
            Location: './api.yaml'
      FailOnWarnings: false
DBSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: my-db-secret
      Description: Postgres config string
      SecretString: 'host=172.17.0.1 port=5431 user=admin password=root dbname=lambdas sslmode=disable'
# ....

كما ذكرنا أعلاه، لا يوجد شيء محدد للذهاب هنا. تم إنشاء بوابة HttpApi بناءً على OpenApi.

هناك أيضًا سر لتخزين سلسلة الاتصال. سأقوم بتحديث قيمته بعد النشر

وظيفة لامدا

يعد دعم AWS SAM لـ Go رائعًا جدًا. يمكنني توجيه CodeUri إلى المجلد باستخدام معالج lambda وتحديد طريقة الإنشاء على أنها go1.x

تستخدم وظائف Lambda المضمنة في Go وقت التشغيل المتوفر.al2023، لأنها تنتج ملفًا ثنائيًا واحدًا قائمًا بذاته.

يبدو تعريف الدالة كما يلي:

# template.yaml
# ....
  GetItemFunction:
    Type: AWS::Serverless::Function
    Metadata:
      BuildMethod: go1.x
    Properties:
      Tracing: Active
      CodeUri: lambda_handlers/cmd/get_item/
      Handler: bootstrap
      Runtime: provided.al2023
      Architectures:
        - x86_64
      Environment:
        Variables:
          DB_SECRET_NAME: !Ref DBSecret
          API_STAGE: Prod
      Events:
        HttpApiEvents:
          Type: HttpApi
          Properties:
            Path: /item/{id}
            Method: GET
            ApiId: !Ref ItemsAPI
      Policies: 
        - AWSLambdaBasicExecutionRole
        - AWSSecretsManagerGetSecretValuePolicy:
            SecretArn: !Ref DBSecret
# ....

بفضل سحر SAM، سيتم إنشاء الاتصال بين بوابة HttpApi ووظيفة lambda مع جميع الأذونات المطلوبة.

رمز الوظيفة

هيكل المشروع

لأكون صادقًا، ربما لا تكون بنية المجلد اصطلاحية. لكنني حاولت اتباع أنماط Go العامة

lambda_handlers
|--/api
|--/cmd
|--/internal
|--/tools
|--go.mod
|--go.sum

cmd هو المجلد الرئيسي الذي يحتوي على معالجات لامدا الفعلية
داخلي يحمل الكود المشترك بين المعالجات
تحدد الأدوات الأدوات الإضافية التي سيتم استخدامها في المشاريع
واجهة برمجة التطبيقات لتكوين مولد openapi والنماذج التي تم إنشاؤها

معالج الوظيفة

يبدو النموذج الأولي لمعالج لامدا كما يلي:

// ...
func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    // handler logic
}

func main() {
    lambda.Start(handleRequest)
}

عادةً، السؤال الأول الذي يجب طرحه هو مكان وضع تهيئة عملاء AWS SDK، والاتصال بقاعدة البيانات، وأشياء أخرى، نرغب في التعامل معها أثناء البداية الباردة.

لدينا خيارات هنا. الأول هو اتباع النمط من مثال وثائق AWS وتهيئة الخدمات داخل وظيفة init(). لا أحب هذا الأسلوب، لأنه يجعل من الصعب استخدام المعالج في اختبارات الوحدة.

بفضل حقيقة أن طريقة lambda.Start() تأخذ وظيفة كمدخل، يمكنني تغليفها في البنية المخصصة وتهيئتها بالخدمات التي أحتاجها. في حالتي، يبدو الرمز بهذه الطريقة:

package main

// imports ...

type GetItemsService interface {
    GetItem(id int) (*api.Item, error)
}

type LambdaHandler struct {
    svc GetItemsService
}

func InitializeLambdaHandler(svc GetItemsService) *LambdaHandler {
    return &LambdaHandler{
        svc: svc,
    }
}

func (h *LambdaHandler) HandleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

    strId, err := helpers.GetPathParameter("id", request.PathParameters)

    if err != nil {
        return helpers.SendResponse("id is required", 400), nil
    }

    id, err := strconv.Atoi(*strId)

    if err != nil {
        return helpers.SendResponse("id must be an integer", 400), nil
    }

    log.Printf("id: %d", id)

    result, err := h.svc.GetItem(id)

    if err != nil {
        if err.Error() == "Item not found" {
            return helpers.SendResponse("Item not found", 404), nil
        }
        return helpers.SendResponse("error", 500), nil
    }

    jsonRes, err := json.Marshal(result)

    if err != nil {
        log.Printf("error marshalling response: %s", err.Error())
        return helpers.SendResponse("internal server error", 500), nil
    }

    return helpers.SendResponse(string(jsonRes), 200), nil
}

func main() {

    dbSecretName := os.Getenv("DB_SECRET_NAME")

    log.Printf("dbSecretName: %s", dbSecretName)

    cfg, err := awssdkconfig.InitializeSdkConfig()

    if err != nil {
        log.Fatal(err)
    }

    secretsClient := awssdkconfig.InitializeSecretsManager(cfg)

    connString, err := secretsClient.GetSecret(dbSecretName)

    if err != nil {
        log.Fatal(err)
    }

    conn, err := db.InitializeDB(connString)

    if err != nil {
        log.Fatal(err)
    }

    defer conn.Close()

    log.Println("successfully connected to db")

    svc := items.InitializeItemsService(conn)

    handler := InitializeLambdaHandler(svc)

    lambda.Start(handler.HandleRequest)
}

في الوظيفة الرئيسية () (التي تعمل أثناء البداية الباردة) أحصل على السر من Secretsmanager ثم أقوم بتهيئة الاتصال بقاعدة البيانات. يتم تعريف كلتا الوظيفتين داخل المجلدات الداخلية كمساعدين مشتركين بحيث يمكن إعادة استخدامها في معالجات أخرى. أخيرًا، تمت تهيئة ItemsService الخاصة بي باستخدام اتصال db الذي تم إنشاؤه، واستخدامه لإنشاء معالج lambda.

يقوم HandleRequest بتوزيع المعرف من معلمة المسار، ويستدعي ItemsService للحصول على عنصر من قاعدة البيانات.

الوحدات الداخلية

نظرًا لأن الوظيفة بسيطة، فلا يوجد الكثير من منطق العمل حولها. تقوم ItemsServise ببساطة باستدعاء قاعدة البيانات للعنصر المحدد

package items

import (
    "database/sql"
    "errors"
    api "lambda_handlers/api"
    "log"
)

type ItemsService struct {
    conn *sql.DB
}

func InitializeItemsService(conn *sql.DB) *ItemsService {
    return &ItemsService{
        conn: conn,
    }
}

func (svc ItemsService) GetItem(id int) (*api.Item, error) {

    log.Printf("Getting item id %v", id)

    query := `SELECT * FROM items WHERE id = $1`

    var item api.Item

    err := svc.conn.QueryRow(query, id).Scan(&item.Id, &item.Name, &item.Price)

    log.Printf("Got item id %v", id)

    if err != nil {
        log.Printf("Error getting item %v: %v", id, err)
        if err == sql.ErrNoRows {
            return nil, errors.New("Item not found")
        }
        return nil, err
    }

    return &item, nil

}

في هذه المرحلة، لا نحتاج إلى أي شيء آخر هنا.

أدوات

هدفي هو استخدام أدوات إضافية، والتي يمكن ربطها بتبعيات المشروع، لذلك ليست هناك حاجة للاعتماد على الأدوات المثبتة على جهاز المطور.

إحدى طرق القيام بذلك في Go هي الاحتفاظ بـ oapi-codegen في حزمة الأدوات

//go:build tools
//  build tools

package main

import (
    _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen"
)

واتصل به من داخل api_gen.go

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=cfg.yaml ../../api.yaml

package api

بهذه الطريقة يمكنني تشغيل go generator دون تثبيت ثنائيات oapi-codegen بشكل منفصل.

التنمية المحلية

يبني

تتطلب عملية الإنشاء خطوتين: إنشاء نماذج من OpenAPI، وبناء المشاريع نفسها. لقد سمحت لـ AWS SAM بالتعامل مع الأمر الأخير.

هذا هو ملف Makefile الخاص بي

.PHONY: build local deploy

generate:
    cd lambda_handlers && go generate ./...

build: generate
    rm -rf .aws-sam
    sam build

local: build
    sam local start-api --env-vars parameters.json

deploy: build
    sam deploy

النشر الأولي

بالنسبة لي، أسهل طريقة لاختبار API Gateway محليًا هي تشغيل sam local start-api
نظرًا لأن وظيفتنا تعتمد على متغيرات البيئة، فقد قمت بإنشاء ملف paramters.json لتمرير env vars إلى sam local

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

أجري عملية نشر ولكن في الوقت الحالي لا أتحقق من النشر بالكامل، فقط احصل على اسم سري من وحدة التحكم. أحتاج أيضًا إلى تحديث السر في وحدة التحكم، بحيث يحمل سلسلة الاتصال الصحيحة.

للاختبار، قمت بإنشاء قاعدة بيانات على Supabase وقمت بزرعها مع بعض السجلات الوهمية

الاختبار المحلي

بعد تشغيل make local يمكنني اختبار واجهة برمجة التطبيقات محليًا

AWS Lambda with Go, initial boilerplate

نظرًا لأن Go هي لغة مجمعة، بعد كل تغيير أحتاج إلى إعادة بناء المشروع وتشغيل start-api مرة أخرى. وبالنظر إلى السرعة المذهلة لمترجم Go، فإن ذلك ليس بالأمر الكبير.

اختبار على AWS

تمت طباعة عنوان URL لبوابة API في وحدة التحكم بعد النشر، ويمكن أيضًا الحصول عليه من وحدة تحكم AWS مباشرةً.

أتصل بنقطة النهاية، وهي تعمل كما هو متوقع:

AWS Lambda with Go, initial boilerplate

البداية الباردة طويلة بعض الشيء، حيث تستغرق التهيئة حوالي 300 مللي ثانية، ويرجع ذلك في الغالب إلى أنها تتضمن أخذ السر وإنشاء اتصال بقاعدة البيانات. ولكن لنكون صادقين، فهي أكثر من مجرد نتيجة لائقة.

ملخص

يعد المشروع المحدد نقطة انطلاق لإنشاء واجهة برمجة تطبيقات REST بدون خادم في Go. ويستخدم OpenAPI للمخطط وAWS SAM لإدارة النشر والاختبار المحلي.

لقد استخدمت قاعدة بيانات postgres خارجية وAWS SDK للحصول على سلسلة الاتصال من مدير الأسرار.

هناك أيضًا اختبارات وحدة لمعالج لامدا وخدمة العناصر

قضيت معظم الوقت في تكوين جزء AWS، والذي سيكون هو نفسه بالنسبة لجميع اللغات. يعد رمز Go واضحًا جدًا (لحالة الاستخدام البسيطة هذه).

بيان الافراج تم إعادة نشر هذه المقالة على: https://dev.to/aws-builders/aws-lambda-with-go-initial-boilerplate-2787?1 إذا كان هناك أي انتهاك، يرجى الاتصال بـ [email protected] لحذفه
أحدث البرنامج التعليمي أكثر>

تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.

Copyright© 2022 湘ICP备2022001581号-3