aws api gateway& lambda:多个端点/函数与单个端点

时间:2017-01-02 10:57:10

标签: api amazon-web-services aws-lambda aws-api-gateway

我有一个AWS api代理lamba函数。我目前使用不同的端点和单独的lambda函数:

api.com/getData --> getData
api.com/addData --> addData
api.com/signUp --> signUp

管理所有端点和功能的过程变得很麻烦。当我将一个端点用于一个基于查询字符串决定做什么的lambda函数时,是否有任何不利之处?

api.com/exec&func=getData --> exec --> if(params.func === 'getData') { ... }

5 个答案:

答案 0 :(得分:34)

将多个方法映射到单个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网关的界面使您可以非常直观地查看RESTful端点的布局 - 名词和动词一目了然。实现自己的路由可能会牺牲这种可见性。
  • Lambda大小调整和限制:如果您全部代理 - 那么您最终需要选择适合所有RESTful端点的实例大小,超时等。如果您创建离散函数,那么您可以更仔细地选择最符合特定调用需求的内存占用,超时,死信行为等。

答案 1 :(得分:11)

我一直在使用Lambda-API网关构建5~6个微服务,经过多次尝试和试验。失败和成功。

简而言之,根据我的经验,最好只使用一个APIGateway通配符映射将所有API调用委托给lambda,例如

/api/{+proxy} -> Lambda

如果您使用过grape之类的任何框架,那么您在制作API时会知道诸如此类的功能 的 “中间件”
“全球异常处理”
“级联路由”
“参数验证”

真的很重要 随着API的增长,几乎不可能使用API​​网关映射管理所有路由,也不支持API网关支持这些功能。

此外,为了开发或部署,为每个端点打破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功能的框架,checkout this我们刚刚为lambda构建了web框架,因为我需要在我的公司使用它。

答案 2 :(得分:7)

我会评论只是为Dave Maple's很好的答案添加几点,但我还没有足够的声誉点,所以我会在这里添加评论。

我开始沿着指向一个Lambda函数的多个端点的路径前进,该函数可以通过访问“资源”来处理每个端点的不同。活动的财产。在尝试之后,我现在将它们分成单独的函数,原因是Dave建议加上:

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

答案 3 :(得分:2)

据我所知,AWS每个Lambda函数只允许一个处理程序。这就是为什么我用Java Generics创建了一个小的“路由”机制(用于在编译时进行更强大的类型检查)。在以下示例中,您可以调用多个方法并将不同的对象类型传递给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。:如果您有反序列化问题 LinkedTreeMap 无法转换为...)在您的Lambda函数中(因为uf Generics / 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);
}

答案 4 :(得分:0)

我认为,选择单个API和多个API是以下考虑因素的函数:

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

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

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

  4. 刚刚在另一个SO帖子中找到link,总结了更好的