在本文中,我们将学习使用AWS Lambda在Go中设计,构建和部署RESTful API。 在开始之前,让我给您简要介绍一下AWS Lambda。
什么是AWS Lambda?
AWS Lambda是一种无服务器计算服务,可运行我们的代码以响应事件并自动为我们管理基础计算资源。 我们可以使用AWS Lambda通过自定义逻辑扩展其他AWS服务,或者创建我们自己的后端服务,这些后端服务以AWS规模,性能和安全性运行。 AWS Lambda可以自动运行代码以响应多个事件,例如通过Amazon API Gateway发出的HTTP请求,对Amazon S3存储桶中的对象的修改,Amazon DynamoDB中的表更新以及AWS Step Functions中的状态转换。
Lambda在高可用性计算基础架构上运行我们的代码,并执行所有计算资源管理,包括服务器和操作系统维护,容量配置和自动缩放,代码和安全补丁部署以及代码监视和日志记录。 我们需要做的就是提供代码。
现在,让我们开始构建一个API,该API将帮助本地电影租赁店管理其可用电影。
API架构
下图显示了API Gateway和Lambda如何适应API体系结构:
AWS Lambda支持微服务开发。 话虽如此,每个端点都会触发不同的Lambda函数。 这些功能彼此独立并且可以用不同的语言编写,从而导致在功能级别上进行缩放,更易于进行单元测试以及松散耦合。
来自客户端的所有请求都首先通过API网关。 然后,它将传入的请求相应地路由到正确的Lambda函数。
请注意,单个Lambda函数可以处理多种HTTP方法( GET , POST , PUT , DELETE等)。 建议为每个功能创建多个Lambda函数,以利用微服务的功能。 但是,构建单个Lambda函数来处理多个端点可能是一个不错的练习。
端点设计
现在已经定义了体系结构,是时候完成上图中描述的功能的实现了。 您可以使用net / http Go包并使用内置的状态代码变量,例如http.StatusOK , http.StatusCreated , http.StatusBadRequest , http.StatusInternalServerError等 ,而不是对HTTP状态代码进行硬编码。
GET方法
要实现的第一个功能是列出电影。 这就是GET方法起作用的地方。 让我们从以下步骤开始:
步骤1:创建注册了findAll处理程序的Lambda函数。 该处理程序将电影列表转换为字符串,然后返回由APIGatewayProxyResponse变量包装的字符串以及200 HTTP状态代码。 如果转换失败,它也会处理错误。 处理程序的实现如下:
package main
import (
"encoding/json"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
var movies = []struct {
ID int `json:"id"`
Name string `json:"name"`
}{
{
ID: 1,
Name: "Avengers",
},
{
ID: 2,
Name: "Ant-Man",
},
{
ID: 3,
Name: "Thor",
},
{
ID: 4,
Name: "Hulk",
}, {
ID: 5,
Name: "Doctor Strange",
},
}
func findAll() (events.APIGatewayProxyResponse, error) {
response, err := json.Marshal(movies)
if err != nil {
return events.APIGatewayProxyResponse{}, err
}
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: string(response),
}, nil
}
func main() {
lambda.Start(findAll)
}
您可以使用net / http Go包并使用内置的状态代码变量,例如http.StatusOK , http.StatusCreated , http.StatusBadRequest , http.StatusInternalServerError等 ,而不是对HTTP状态代码进行硬编码。
步骤2:创建一个包含以下内容的脚本文件,以构建Lambda函数部署包,一个包含您的代码和任何依赖项的.zip文件,如下所示:
#!/bin/bash
echo "Build the binary"
GOOS=linux GOARCH=amd64 go build -o main main.go
echo "Create a ZIP file"
zip deployment.zip main
echo "Cleaning up"
rm main
步骤3:执行以下命令以将部署包构建为.zip文件:
$ chmod +x build.sh
$ ./build.sh
步骤4:使用此处提到的步骤配置AWS CLI。 配置完成后,按照此处提到的步骤创建一个名称为FindAllMoviesRole的AWS角色,并验证是否成功创建该角色:
$ aws iam get-role --role-name FindAllMoviesRole
上面的命令应给出响应,如下面的屏幕快照所示:
步骤5:接下来,使用AWS CLI创建一个新的Lambda函数,如下所示:
aws lambda create-function --function-name FindAllMovies \
--zip-file fileb://deployment.zip \
--runtime go1.x --handler main \
--role arn:aws:iam::ACCOUNT_ID:role/FindAllMoviesRole \
--region us-east-1
创建函数后,将为我们提供与以下屏幕快照所示的输出相同的输出:
步骤6 :回到AWS Lambda控制台,您应该看到该函数已成功创建:
步骤7 :创建一个带有空JSON的示例事件,因为该函数不需要任何参数,然后单击Test按钮:
您将在上一个屏幕截图中注意到,该函数以JSON格式返回预期的输出。
步骤8:现在已经定义了函数,您需要创建一个新的API网关来触发它:
步骤9:接下来,从“ 操作”下拉列表中,选择“ 创建资源并将其命名为电影” :
步骤10:通过点击Create Method在此/ movies资源上公开GET 方法 。 在“ 集成类型”部分下选择“ Lambda函数 ”,然后选择“ FindAllMovies”函数:
步骤11:要部署API,请从“ 操作”下拉列表中选择“ 部署API ”。 系统将提示您创建一个新的部署阶段:
步骤12:一旦创建了部署阶段,就会显示一个调用URL:
步骤13:将浏览器指向给定的URL或使用现代的REST客户端(例如Postman或Insomnia)。 您可以使用cURL工具,因为默认情况下它已安装在几乎所有操作系统上:
curl -sX GET https://51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.'
上面的命令将以JSON格式返回电影列表:
调用GET端点时,请求将通过API网关,这将触发findAll处理程序。 这会将API网关代理的响应以JSON格式返回给客户端。
现在已经部署了findAll函数,您可以实现findOne函数以通过其ID搜索电影。
带参数的GET方法
findOne处理程序需要包含事件输入的APIGatewayProxyRequest参数。 然后,它使用PathParameters方法获取影片ID并对其进行验证。
如果提供的ID无效,则Atoi方法将返回错误,并将500错误代码返回给客户端。 否则,将根据索引获取电影,并以包装在APIGatewayProxyResponse中的200 OK状态返回给客户端:
...
func findOne(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
id, err := strconv.Atoi(req.PathParameters["id"])
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 500,
Body: "ID must be a number",
}, nil
}
response, err := json.Marshal(movies[id-1])
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 500,
Body: err.Error(),
}, nil
}
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: string(response),
}, nil
}
func main() {
lambda.Start(findOne)
}
与FindAllMovies函数类似,创建一个新的Lambda函数以搜索电影:
aws lambda create-function --function-name FindOneMovie \
--zip-file fileb://deployment.zip \
--runtime go1.x --handler main \
--role arn:aws:iam::ACCOUNT_ID:role/FindOneMovieRole \
--region us-east-1
返回API Gateway控制台,创建一个新资源,公开GET方法,然后将资源链接到FindOneMovie函数。 注意在路径中使用{id}占位符。 id的值将通过APIGatewayProxyResponse对象提供。 以下屏幕截图描述了这一点:
重新部署API,并使用以下cURL命令测试端点:
curl -sX https://51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies/1 | jq '.'
将返回以下JSON:
使用ID调用API URL时,如果存在ID对应的影片,则返回该影片。
POST方法
现在,您知道带有和不带有路径参数的GET方法如何工作。 下一步是通过API网关将JSON有效负载传递给Lambda函数。 该代码是不言自明的。 它将输入的请求转换为电影结构,将其添加到电影列表,然后以JSON格式返回新的电影列表:
package main
import (
"encoding/json"
"strconv"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type Movie struct {
ID int `json:"id"`
Name string `json:"name"`
}
var movies = []Movie{
Movie{
ID: 1,
Name: "Avengers",
},
...
}
func insert(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
var movie Movie
err := json.Unmarshal([]byte(req.Body), &movie)
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 400,
Body: "Invalid payload",
}, nil
}
movies = append(movies, movie)
response, err := json.Marshal(movies)
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 500,
Body: err.Error(),
}, nil
}
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: string(response),
}, nil
}
func main() {
lambda.Start(insert)
}
接下来,使用以下命令为InsertMovie创建一个新的Lambda函数:
aws lambda create-function --function-name InsertMovie \
--zip-file fileb://deployment.zip \
--runtime go1.x --handler main \
--role arn:aws:iam::ACCOUNT_ID:role/InsertMovieRole \
--region us-east-1
接下来,在/ movies资源上创建一个POST方法,并将其链接到InsertMovie函数:
要对其进行测试,请使用下面的cURL命令以及POST动词和-d标志,后跟JSON字符串(具有id和name属性):
curl -sX POST -d '{"id":6, "name": "Spiderman:Homecoming"}' https://51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.'
上面的命令将返回以下JSON响应:
如您所见,新电影已成功插入。 如果再次测试,它应该可以按预期工作:
curl -sX POST -d '{"id":7, "name": "Iron man"}' https://51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.'
前面的命令将返回以下JSON响应:
如您所见,它成功完成并且再次按预期方式插入了影片,但是如果您等待几分钟并尝试插入第三部影片怎么办? 以下命令将用于再次执行它:
curl -sX POST -d '{"id":8, "name": "Captain America"}' https://51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.'
再次,将返回一个新的JSON响应:
您会发现ID为6和7的电影已被删除; 为什么会这样? 这很简单。 Lambda函数是无状态的。
首次(首次插入)调用InsertMovie函数时,AWS Lambda将创建一个容器并将函数有效负载部署到该容器。 然后,它保持活动状态几分钟,然后终止( 热启动 ),这说明了第二个刀片通过的原因。 在第三个插件中,该容器已经终止,因此Lambda创建一个新容器( 冷启动 )以处理该插件。
这就是为什么以前的状态会丢失的原因。 下图说明了冷/热启动问题:
这就解释了为什么Lambda函数应该是无状态的,为什么您不应该做出任何假设将状态从一次调用保留到另一次调用的假设。
完整的源代码托管在github上 。
翻译自: https://www.javacodegeeks.com/2018/11/build-restful-api-go-using-aws-lambda.html