”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > AWS Lambda 与 Go,初始样板

AWS Lambda 与 Go,初始样板

发布于2024-11-06
浏览:362

照片由 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]删除
最新教程 更多>
  • 如何在 Go 中处理不同包之间相同的方法签名?
    如何在 Go 中处理不同包之间相同的方法签名?
    处理不同包中具有相同方法签名的接口在Go中,当处理具有相同方法签名但定义在不同包中的多个接口时,可能会出现以下情况实现两个接口的类型会导致意外行为。考虑在不同包中定义的这两个接口(Doer)和函数(FuncA 和 FuncB):// Package A type Doer interface { D...
    编程 发布于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
  • 如何使用 Laravel Eloquent 的 firstOrNew() 方法有效优化 CRUD 操作?
    如何使用 Laravel Eloquent 的 firstOrNew() 方法有效优化 CRUD 操作?
    使用 Laravel Eloquent 优化 CRUD 操作在 Laravel 中使用数据库时,插入或更新记录是很常见的。为了实现这一点,开发人员经常求助于条件语句,在决定执行插入或更新之前检查记录是否存在。firstOrNew() 方法幸运的是, Eloquent 通过firstOrNew() 方...
    编程 发布于2024-11-06
  • 为什么在 PHP 中重写方法参数违反了严格的标准?
    为什么在 PHP 中重写方法参数违反了严格的标准?
    在 PHP 中重写方法参数:违反严格标准在面向对象编程中,里氏替换原则 (LSP) 规定:子类型的对象可以替换其父对象,而不改变程序的行为。然而,在 PHP 中,用不同的参数签名覆盖方法被认为是违反严格标准的。为什么这是违规?PHP 是弱类型语言,这意味着编译器无法在编译时确定变量的确切类型。这意味...
    编程 发布于2024-11-06
  • 哪个 PHP 库提供卓越的 SQL 注入防护:PDO 还是 mysql_real_escape_string?
    哪个 PHP 库提供卓越的 SQL 注入防护:PDO 还是 mysql_real_escape_string?
    PDO vs. mysql_real_escape_string:综合指南查询转义对于防止 SQL 注入至关重要。虽然 mysql_real_escape_string 提供了转义查询的基本方法,但 PDO 成为了一种具有众多优点的卓越解决方案。什么是 PDO?PHP 数据对象 (PDO) 是一个数...
    编程 发布于2024-11-06
  • React 入门:初学者的路线图
    React 入门:初学者的路线图
    大家好! ? 我刚刚开始学习 React.js 的旅程。这是一次令人兴奋(有时甚至具有挑战性!)的冒险,我想分享一下帮助我开始的步骤,以防您也开始研究 React。这是我的处理方法: 1.掌握 JavaScript 基础知识 在开始使用 React 之前,我确保温习一下我的 JavaScript 技...
    编程 发布于2024-11-06
  • 如何引用 JavaScript 对象中的内部值?
    如何引用 JavaScript 对象中的内部值?
    如何在 JavaScript 对象中引用内部值在 JavaScript 中,访问引用同一对象中其他值的对象中的值有时可能具有挑战性。考虑以下代码片段:var obj = { key1: "it ", key2: key1 " works!" }; ...
    编程 发布于2024-11-06
  • Python 列表方法快速指南及示例
    Python 列表方法快速指南及示例
    介绍 Python 列表用途广泛,并附带各种内置方法,有助于有效地操作和处理数据。下面是所有主要列表方法的快速参考以及简短的示例。 1. 追加(项目) 将项目添加到列表末尾。 lst = [1, 2, 3] lst.append(4) # [1, 2, 3, 4]...
    编程 发布于2024-11-06

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3