繁体   English   中英

aws api 网关和 lambda:多个端点/功能与单个端点

[英]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 Gateway 的界面让您可以非常直观地查看 RESTful 端点的布局——名词和动词都一目了然。 实现自己的路由可能会牺牲这种可见性。
  • Lambda 大小和限制:如果您代理所有——那么您最终需要选择一个实例大小、超时等,以适应您的所有 RESTful 端点。 如果您创建离散函数,那么您可以更仔细地选择最能满足特定调用需求的内存占用、超时、死信行为等。

我一直在用 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 建议加上:

  • 我发现当功能分开时,更容易查看日志和监视器。
  • 作为初学者,我一开始没有注意到的一个细微差别是,您可以拥有一个代码库并部署与多个 Lambda 函数完全相同的代码。 这允许您在代码库中获得功能分离的好处和整合方法的好处。
  • 您可以使用 AWS CLI 跨多个功能自动执行任务,以减少/消除管理单独功能的缺点。 例如,我有一个脚本,用相同的代码更新 10 个函数。

据我所知,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 是出于以下考虑因素:

  1. 安全性:我认为这是拥有单一 API 结构的最大挑战。 对于需求的不同部分,可能有不同的安全配置文件

  2. 从业务角度考虑微服务模型:任何 API 的全部目的都应该是服务于一些请求,因此它必须易于理解且易于使用。 所以应该结合相关的API。 例如,如果您有一个移动客户端,它需要从数据库中拉入和拉出 10 个东西,那么将 10 个端点放入单个 API 是有意义的。 但这应该在合理范围内,并且应该在整体解决方案设计的背景下看到。 例如,如果您设计一个工资单产品,您可能会认为有单独的模块用于休假管理和用户详细信息管理。 即使它们经常被单个客户端使用,它们仍然应该是不同的 API,因为它们的业务含义不同。

  3. 可重用性:适用于代码和功能的可重用性。 代码重用性是一个更容易解决的问题,即为共享需求构建通用模块并将它们构建为库。 功能重用性更难解决。 在我看来,大多数情况都可以通过重新设计端点/功能的布局方式来解决,因为如果您需要功能重复,则意味着您的初始设计不够详细。

刚刚在另一篇 SO 帖子中找到了一个链接,该链接总结得更好

名为“组织大型无服务器应用程序的最佳实践”的官方 AWS 博客文章中介绍了类似的情况。

一般建议是将“整体 lambda”拆分为单独的 lambda,并将路由移动到 API 网关。

这是博客关于“整体式 lambda”方法的内容:

这种方法通常是不必要的,通常最好利用 API Gateway 中可用的本机路由功能。 ... API Gateway 还能够验证参数,减少使用自定义代码检查参数的需要。 它还可以防止未经授权的访问,以及更适合在服务级别处理的一系列其他功能。

从这个出发: 具有单片 lambda 函数的 AWS 应用程序

对此

具有单独 lambda 函数的 AWS 应用程序

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM