[英]aws api gateway & lambda: multiple endpoint/functions vs single endpoint
我有一个代理 Lamba 函数的 AWS api。 我目前使用不同的端点和单独的 lambda 函数:
api.com/getData --> getData
api.com/addData --> addData
api.com/signUp --> signUp
管理所有端点和功能的过程变得繁琐。 当我使用单个端点到一个基于查询字符串决定做什么的 lambda 函数时,有什么缺点吗?
api.com/exec&func=getData --> exec --> if(params.func === 'getData') { ... }
将多个方法映射到单个 lambda 函数是完全有效的,今天许多人正在使用这种方法,而不是为每个离散方法创建 api 网关资源和 lambda 函数。
您可能会考虑将所有请求代理到单个函数。 查看以下有关创建 API 网关 => Lambda 代理集成的文档: http : //docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html
他们的例子在这里很棒。 类似如下的请求:
POST /testStage/hello/world?name=me HTTP/1.1
Host: gy415nuibc.execute-api.us-east-1.amazonaws.com
Content-Type: application/json
headerName: headerValue
{
"a": 1
}
最后将以下事件数据发送到您的 AWS Lambda 函数:
{
"message": "Hello me!",
"input": {
"resource": "/{proxy+}",
"path": "/hello/world",
"httpMethod": "POST",
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"cache-control": "no-cache",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Content-Type": "application/json",
"headerName": "headerValue",
"Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com",
"Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f",
"User-Agent": "PostmanRuntime/2.4.5",
"Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==",
"X-Forwarded-For": "54.240.196.186, 54.182.214.83",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"queryStringParameters": {
"name": "me"
},
"pathParameters": {
"proxy": "hello/world"
},
"stageVariables": {
"stageVariableName": "stageVariableValue"
},
"requestContext": {
"accountId": "12345678912",
"resourceId": "roq9wj",
"stage": "testStage",
"requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33",
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"apiKey": null,
"sourceIp": "192.168.196.186",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "PostmanRuntime/2.4.5",
"user": null
},
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"apiId": "gy415nuibc"
},
"body": "{\r\n\t\"a\": 1\r\n}",
"isBase64Encoded": false
}
}
现在您可以访问所有标头、url 参数、正文等,并且您可以使用它在单个 Lambda 函数中以不同方式处理请求(基本上是实现您自己的路由)。
作为一种意见,我看到了这种方法的一些优点和缺点。 其中许多取决于您的特定用例:
我一直在用 Lambda-API 网关构建 5~6 个微服务,并经历了几次尝试、失败和成功。
简而言之,根据我的经验,最好只使用一个 APIGateway 通配符映射将所有 API 调用委托给 lambda,例如
/api/{proxy+} -> Lambda
如果你曾经使用过像葡萄这样的框架,你就会知道在制作 API 时,像这样的功能
“中间件”
“全局异常处理”
“级联路由”
“参数验证”
真的很关键。 随着 API 的增长,几乎不可能使用 API Gateway 映射来管理所有路由,而且 API Gateway 也不支持这些功能。
此外,为开发或部署的每个端点中断 lambda 实际上并不是很实际。
从你的例子,
api.com/getData --> getData
api.com/addData --> addData
api.com/signUp --> signUp
假设您有数据 ORM、用户身份验证逻辑、通用视图文件(例如 data.erb)……那么您将如何共享它?
你可能会崩溃,
api/auth/{+proxy} -> AuthServiceLambda
api/data/{+proxy} -> DataServiceLambda
但不像“每个端点”。 您可以查找微服务的概念以及有关如何拆分服务的最佳实践
像功能的web框架,结帐这个因为我需要这在我的公司,我们刚刚建立的Web框架拉姆达。
我会评论说只是为Dave Maple 的好答案添加几点,但我还没有足够的声望点,所以我会在这里添加评论。
我开始沿着指向一个 Lambda 函数的多个端点的路径前进,该函数可以通过访问事件的“资源”属性来对每个端点进行不同的处理。 在尝试之后,我现在将它们分成不同的功能,原因是 Dave 建议加上:
据我所知,AWS 只允许每个 Lambda 函数有一个处理程序。 这就是为什么我用 Java 泛型创建了一个小的“路由”机制(用于在编译时进行更强的类型检查)。 在以下示例中,您可以调用多个方法并将不同的对象类型传递给 Lambda 并通过一个 Lambda 处理程序返回:
带有处理程序的 Lambda 类:
public class GenericLambda implements RequestHandler<LambdaRequest<?>, LambdaResponse<?>> {
@Override
public LambdaResponse<?> handleRequest(LambdaRequest<?> lambdaRequest, Context context) {
switch (lambdaRequest.getMethod()) {
case WARMUP:
context.getLogger().log("Warmup");
LambdaResponse<String> lambdaResponseWarmup = new LambdaResponse<String>();
lambdaResponseWarmup.setResponseStatus(LambdaResponse.ResponseStatus.IN_PROGRESS);
return lambdaResponseWarmup;
case CREATE:
User user = (User)lambdaRequest.getData();
context.getLogger().log("insert user with name: " + user.getName()); //insert user in db
LambdaResponse<String> lambdaResponseCreate = new LambdaResponse<String>();
lambdaResponseCreate.setResponseStatus(LambdaResponse.ResponseStatus.COMPLETE);
return lambdaResponseCreate;
case READ:
context.getLogger().log("read user with id: " + (Integer)lambdaRequest.getData());
user = new User(); //create user object for test, instead of read from db
user.setName("name");
LambdaResponse<User> lambdaResponseRead = new LambdaResponse<User>();
lambdaResponseRead.setData(user);
lambdaResponseRead.setResponseStatus(LambdaResponse.ResponseStatus.COMPLETE);
return lambdaResponseRead;
default:
LambdaResponse<String> lambdaResponseIgnore = new LambdaResponse<String>();
lambdaResponseIgnore.setResponseStatus(LambdaResponse.ResponseStatus.IGNORED);
return lambdaResponseIgnore;
}
}
}
LambdaRequest 类:
public class LambdaRequest<T> {
private Method method;
private T data;
private int languageID;
public static enum Method {
WARMUP, CREATE, READ, UPDATE, DELETE
}
public LambdaRequest(){
}
public Method getMethod() {
return method;
}
public void setMethod(Method create) {
this.method = create;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public int getLanguageID() {
return languageID;
}
public void setLanguageID(int languageID) {
this.languageID = languageID;
}
}
LambdaResponse 类:
public class LambdaResponse<T> {
private ResponseStatus responseStatus;
private T data;
private String errorMessage;
public LambdaResponse(){
}
public static enum ResponseStatus {
IGNORED, IN_PROGRESS, COMPLETE, ERROR, COMPLETE_DUPLICATE
}
public ResponseStatus getResponseStatus() {
return responseStatus;
}
public void setResponseStatus(ResponseStatus responseStatus) {
this.responseStatus = responseStatus;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
示例 POJO 用户类:
public class User {
private String name;
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
JUnit测试方法:
@Test
public void GenericLambda() {
GenericLambda handler = new GenericLambda();
Context ctx = createContext();
//test WARMUP
LambdaRequest<String> lambdaRequestWarmup = new LambdaRequest<String>();
lambdaRequestWarmup.setMethod(LambdaRequest.Method.WARMUP);
LambdaResponse<String> lambdaResponseWarmup = (LambdaResponse<String>) handler.handleRequest(lambdaRequestWarmup, ctx);
//test READ user
LambdaRequest<Integer> lambdaRequestRead = new LambdaRequest<Integer>();
lambdaRequestRead.setData(1); //db id
lambdaRequestRead.setMethod(LambdaRequest.Method.READ);
LambdaResponse<User> lambdaResponseRead = (LambdaResponse<User>) handler.handleRequest(lambdaRequestRead, ctx);
}
ps.:如果您在 Lambda 函数中遇到反序列化问题( LinkedTreeMap不能强制转换为 ...)(因为 uf 泛型/Gson),请使用以下语句:
YourObject yourObject = (YourObject)convertLambdaRequestData2Object(lambdaRequest, YourObject.class);
方法:
private <T> Object convertLambdaRequestData2Object(LambdaRequest<?> lambdaRequest, Class<T> clazz) {
Gson gson = new Gson();
String json = gson.toJson(lambdaRequest.getData());
return gson.fromJson(json, clazz);
}
在我看来,选择单个还是多个 API 是出于以下考虑因素:
安全性:我认为这是拥有单一 API 结构的最大挑战。 对于需求的不同部分,可能有不同的安全配置文件
从业务角度考虑微服务模型:任何 API 的全部目的都应该是服务于一些请求,因此它必须易于理解且易于使用。 所以应该结合相关的API。 例如,如果您有一个移动客户端,它需要从数据库中拉入和拉出 10 个东西,那么将 10 个端点放入单个 API 是有意义的。 但这应该在合理范围内,并且应该在整体解决方案设计的背景下看到。 例如,如果您设计一个工资单产品,您可能会认为有单独的模块用于休假管理和用户详细信息管理。 即使它们经常被单个客户端使用,它们仍然应该是不同的 API,因为它们的业务含义不同。
可重用性:适用于代码和功能的可重用性。 代码重用性是一个更容易解决的问题,即为共享需求构建通用模块并将它们构建为库。 功能重用性更难解决。 在我看来,大多数情况都可以通过重新设计端点/功能的布局方式来解决,因为如果您需要功能重复,则意味着您的初始设计不够详细。
名为“组织大型无服务器应用程序的最佳实践”的官方 AWS 博客文章中介绍了类似的情况。
一般建议是将“整体 lambda”拆分为单独的 lambda,并将路由移动到 API 网关。
这是博客关于“整体式 lambda”方法的内容:
这种方法通常是不必要的,通常最好利用 API Gateway 中可用的本机路由功能。 ... API Gateway 还能够验证参数,减少使用自定义代码检查参数的需要。 它还可以防止未经授权的访问,以及更适合在服务级别处理的一系列其他功能。
对此
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.