」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > AWS Lambda 與 Go,初始樣板

AWS Lambda 與 Go,初始樣板

發佈於2024-11-06
瀏覽:514

照片由 Lukáš Vaňátko 在 Unsplash 上拍摄

介绍

Go 由于其简单性一直是我最喜欢的语言之一。最近,我决定弄清楚如何使用用 Go 编写的 lambda 函数创建一个简单的样板无服务器项目。我对工具和开发人员体验很好奇。

目标

我想创建一个 REST API,它使用 postgres db 作为数据层。我的初步要求如下

  • 使用 OpenApi 定义的规范,以及从中生成的模型
  • 使用 AWS SAM
  • 每个端点都由单独的 lambda 函数处理
  • 本地开发尽可能简单
  • 部署相同

先决条件

您需要安装 Go 以及 AWS SAM。如果您将项目部署到 AWS,您可能需要为正在创建的资源付费,因此请记住在不需要资源时清除资源。
对于数据库我使用supabase

构建项目

代码可在此存储库中找到

让我们从运行 sam init 开始。我选择了 Hello World 模板,Go 提供了 al.2023 env。以前有一个 Go 的托管运行时,但现在它已被弃用。

OpenApi 架构

将 API 架构定义为 OpenApi 规范有一些明显的优势。我们可以用它来生成文档、创建客户端、服务器等。我还用它来定义 AWS HttpApi 网关的形状。

我的架构很简单。唯一有趣的部分是 x-amazon-apigateway-integration 属性,它允许与 lambda 集成连接。该设置与语言无关。

您可以在存储库中找到架构文件

SAM模板

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'
# ....

如上所述,这里没有任何特定于 Go 的内容。 HttpApi Gateway是基于OpenApi创建的。

还有一个存储连接字符串的秘密。我会在部署后更新它的值

拉姆达函数

AWS SAM 对 Go 的支持非常棒。我可以将 CodeUri 指向带有 lambda 处理程序的文件夹,并将构建方法定义为 go1.x

Go 中内置的 Lambda 函数使用provided.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 是包含实际 lambda 处理程序的主文件夹
内部保存处理程序之间共享的代码
工具定义了项目中使用的附加工具
用于 openapi 生成器配置和生成模型的 api

函数处理程序

lambda 处理程序的初始样板如下所示:

// ...
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)
}

在 main() 函数(在冷启动期间运行)中,我从 Secretsmanager 获取秘密,然后初始化与数据库的连接。这两个功能都在内部文件夹中定义为通用助手,因此可以在其他处理程序中重用。最后,我的 ItemsService 使用创建的数据库连接进行初始化,并用于创建 lambda 处理程序。

HandleRequest 从路径参数中解析 ID,并调用 ItemsService 从 DB 中获取项目。

内部模块

由于功能简单,没有太多业务逻辑。 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

这样我就可以运行 gogenerate 而无需单独安装 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 文件以将环境变量传递给 sam local

在进行无服务器开发时,有时您可能希望开始使用云资源,甚至进行本地开发。就我而言,我将立即使用机密管理器来存储数据库的连接字符串。这意味着,我需要先部署堆栈,以便我可以在本地开发中使用它。

我运行 make deploy 但现在我不检查整个部署,只是从控制台获取一个秘密名称。我还需要更新控制台中的秘密,以便它保存正确的连接字符串。

为了测试,我在supabase上创建了一个数据库,并在其中添加了一些虚拟记录

本地测试

运行 make local 后我可以在本地测试 API

AWS Lambda with Go, initial boilerplate

由于Go是一种编译语言,每次更改后我都需要重建项目并再次运行start-api。考虑到 Go 编译器惊人的速度,这没什么大不了的。

在 AWS 上测试

API Gateway URL是部署后在控制台打印出来的,也可以直接从AWS控制台抓取。

我调用端点,它按预期工作:

AWS Lambda with Go, initial boilerplate

冷启动有点长,因为初始化需要约 300 毫秒,主要是因为它包括获取机密并建立与数据库的连接。但说实话,这不仅仅是一个不错的结果。

概括

给定的项目是在 Go 中创建无服务器 REST API 的起点。它使用 OpenAPI 作为架构,使用 AWS SAM 来管理部署和本地测试。

我使用了外部 postgres 数据库和 AWS SDK 从机密管理器获取连接字符串。

还有 lambda 处理程序和项目服务的单元测试

我大部分时间都花在配置 AWS 部分上,这对于所有语言都是相同的。 Go 代码非常简单(对于这个简单的用例)。

版本聲明 本文轉載於:https://dev.to/aws-builders/aws-lambda-with-go-initial-boilerplate-2787?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 專案透過其介面引用對象
    專案透過其介面引用對象
    介面作為引用型別的偏好: 如果存在合適的接口,您應該透過它引用對象而不是具體的類別。 這適用於參數、傳回值、變數和欄位。 使用介面的彈性: 使用介面允許您更改實作而不影響程式碼。 範例:從 LinkedHashSet 變更為 HashSet 或 TreeSet,只需更改建構子即可。 何時不使...
    程式設計 發佈於2024-11-06
  • 如何防止遊戲網站頁面載入時重複插入?
    如何防止遊戲網站頁面載入時重複插入?
    調試頁面載入時的重複插入在遊戲網頁上,觀察到使用者活動查詢在頁面刷新時將重複記錄插入資料庫。 $insert_user_activity = mysql_query("INSERT INTO game_activity (user_id,user_full_name,game_id,gam...
    程式設計 發佈於2024-11-06
  • Python 最佳實務:編寫乾淨、有效率且可維護的程式碼
    Python 最佳實務:編寫乾淨、有效率且可維護的程式碼
    Python 因其简单性、可读性和多功能性而成为最流行的编程语言之一。 无论您是经验丰富的开发人员还是初学者,遵循 Python 最佳实践对于编写干净、高效和可维护的代码至关重要。 在这篇博文中,我们将探讨编写 Python 代码时要牢记的一些关键最佳实践。 1 - 遵守 PEP...
    程式設計 發佈於2024-11-06
  • std::lock_guard 與 std::scoped_lock:何時使用哪個鎖?
    std::lock_guard 與 std::scoped_lock:何時使用哪個鎖?
    std::lock_guard 與std::scoped_lock:為任務選擇正確的鎖隨著C 17 的引入,std :: scoped_lock 類別與現有的std::lock_guard 一起出現,引發了關於它們之間的差異以及何時使用它們的問題。 雖然 std::scoped_lock 與 std...
    程式設計 發佈於2024-11-06
  • WebRTC簡介
    WebRTC簡介
    安裝和代碼指南 WebRTC(網路即時通訊)是一種開源技術,可透過網頁瀏覽器和行動應用程式中的簡單 API 進行即時通訊。它允許在點之間直接共享音訊、視訊和數據,無需中間伺服器,非常適合視訊會議、直播和檔案共享等應用程式。 在本部落格中,我們將深入探討以下主題: 什麼是WebRT...
    程式設計 發佈於2024-11-06
  • 如何在不使用 JavaScript 的情況下使用 CSS 隱藏和顯示內容?
    如何在不使用 JavaScript 的情況下使用 CSS 隱藏和顯示內容?
    使用CSS 隱藏和顯示內容:無需JavaScript 的技巧在進行Web 開發時,控制內容的可見性通常至關重要。傳統上,這是使用 JavaScript 實現的,但 CSS 也可用於創建優雅的隱藏和顯示效果。下面描述了一種此類技術,解決了先前方法遇到的特定挑戰。 隱藏/顯示內容切換:可以使用 CSS ...
    程式設計 發佈於2024-11-06
  • 如何建立重複最少的 5 個字元的隨機字串?
    如何建立重複最少的 5 個字元的隨機字串?
    產生5 個具有最少重複的隨機字元要建立具有最少重複的隨機5 個字元字串,最有效的方法之一是使用PHP 函數和巧妙技術的結合。讓我們深入研究解決方案:使用md5 和rand$rand = substr(md5(microtime()),rand(0,26),5);此方法使用md5雜湊函數根據時間戳記產...
    程式設計 發佈於2024-11-06
  • 如何在 Go 中處理不同套件之間相同的方法簽名?
    如何在 Go 中處理不同套件之間相同的方法簽名?
    處理不同套件中具有相同方法簽名的介面在Go中,當處理具有相同方法簽署但定義在不同套件中的多個介面時,可能會出現以下情況實作兩個介面的類型會導致意外行為。 考慮在不同套件中定義的這兩個介面(Doer)和函數(FuncA 和 FuncB):// Package A type Doer interface...
    程式設計 發佈於2024-11-06
  • 如何使用 jQuery 填充級聯下拉清單以獲得更好的相容性和使用者體驗?
    如何使用 jQuery 填充級聯下拉清單以獲得更好的相容性和使用者體驗?
    使用jQuery 填充級聯下拉清單在表單開發領域,級聯下拉清單經常用於提供更用戶友好和動態體驗。為了增強相容性並解決跨瀏覽器問題,jQuery 提供了一個強大的解決方案來非同步填充這些下拉清單。 問題中所示的用於建立級聯下拉清單的原始 JavaScript 函數缺乏與 IE 的兼容性。為了解決這個問...
    程式設計 發佈於2024-11-06
  • 了解 JavaScript 中的擴充運算子:初學者簡單指南
    了解 JavaScript 中的擴充運算子:初學者簡單指南
    介紹 JavaScript 是一種有趣的程式語言,其最令人興奮的功能之一是擴充運算子。如果您剛開始編碼,或者即使您是一個對學習 JavaScript 感興趣的孩子,也不必擔心!我將以最簡單的方式分解這個概念,並舉例來幫助您理解。 什麼是價差運算子? 擴充運算子看起...
    程式設計 發佈於2024-11-06
  • 在 Python 中使用 OpenSearch 掌握 CRUD 操作:實用指南
    在 Python 中使用 OpenSearch 掌握 CRUD 操作:實用指南
    OpenSearch, an open-source alternative to Elasticsearch, is a powerful search and analytics engine built to handle large datasets with ease. In this b...
    程式設計 發佈於2024-11-06
  • 冰沙框架的重要概念||如何精通冰沙
    冰沙框架的重要概念||如何精通冰沙
    要精通 Frappe,有几个关键概念和领域需要关注。以下是最重要的细分: 1. 文档类型 定义:DocTypes是Frappe中的核心数据模型。每个实体或记录都存储在 DocType 中,并且它们可以具有字段、权限和工作流程。 为什么它很重要:了解如何创建和自定义 DocType 至...
    程式設計 發佈於2024-11-06
  • 如何解決 JLabel 拖放的滑鼠事件衝突?
    如何解決 JLabel 拖放的滑鼠事件衝突?
    用於拖放的JLabel 滑鼠事件:解決滑鼠事件衝突為了在JLabel 上啟用拖放功能,滑鼠事件必須被覆蓋。然而,當嘗試使用 mousePressed 事件實作拖放時,會出現一個常見問題,因為 mouseReleased 事件對該 JLabel 無效。 提供的程式碼在 mousePressed 事件中...
    程式設計 發佈於2024-11-06
  • MySQL 中的資料庫分片:綜合指南
    MySQL 中的資料庫分片:綜合指南
    随着数据库变得越来越大、越来越复杂,有效地控制性能和扩展就出现了。数据库分片是用于克服这些障碍的一种方法。称为“分片”的数据库分区将大型数据库划分为更小、更易于管理的段(称为“分片”)。通过将每个分片分布在多个服务器上(每个服务器保存总数据的一小部分),可以提高可扩展性和吞吐量。 在本文中,我们将探...
    程式設計 發佈於2024-11-06
  • 如何將 Python 日期時間物件轉換為秒?
    如何將 Python 日期時間物件轉換為秒?
    在Python 中將日期時間物件轉換為秒在Python 中使用日期時間物件時,通常需要將它們轉換為秒以適應各種情況分析目的。但是,toordinal() 方法可能無法提供所需的輸出,因為它僅區分具有不同日期的日期。 要準確地將日期時間物件轉換為秒,特別是對於 1970 年 1 月 1 日的特定日期,...
    程式設計 發佈於2024-11-06

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3