Aws api gateway & lambda: несколько конечных точек/функций по сравнению с одной конечной точкой

У меня есть AWS api, который проксирует функции lamba. В настоящее время я использую разные конечные точки с отдельными лямбда-функциями:

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

Процесс управления всеми конечными точками и функциями становится громоздким. Есть ли недостаток, когда я использую одну конечную точку для одной лямбда-функции, которая решает, что делать на основе строки запроса?

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

Ответ 1

Совершенно верно для сопоставления нескольких методов с одной лямбда-функцией, и многие люди используют эту методологию сегодня, а не для создания ресурса шлюза api и лямбда-функции для каждого дискретного метода.

Вы можете рассмотреть возможность проксирования всех запросов к одной функции. Взгляните на следующую документацию по созданию API Gateway = > Lambda proxy integration: 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
}

Завершает отправку следующих данных события в вашу функцию AWMS 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 (в основном, используя собственную маршрутизацию).

Как мнение, я вижу некоторые преимущества и недостатки такого подхода. Многие из них зависят от вашего конкретного варианта использования:

  • Развертывание. Если каждая функция лямбда дискретна, вы можете развернуть их самостоятельно, что может снизить риск изменения кода (стратегия микросервисов). И наоборот, вы можете обнаружить, что необходимость развертывания функций по отдельности добавляет сложности и обременительна.
  • Описание самопомощи. Интерфейс API Gateway делает его чрезвычайно интуитивным, чтобы видеть макет ваших конечных точек RESTful - существительные и глаголы видны с первого взгляда. Реализация вашей собственной маршрутизации может произойти за счет этой видимости.
  • Размер и ограничения лямбда. Если вы прокси-сервер все - тогда вам нужно будет выбрать размер экземпляра, время ожидания и т.д., которые будут соответствовать всем вашим конечным точкам RESTful. Если вы создаете дискретные функции, тогда вы можете более тщательно выбирать размер памяти, тайм-аут, поведение в недозвольном состоянии и т.д., Что наилучшим образом отвечает потребностям конкретного вызова.

Ответ 2

Я строю 5 ~ 6 микросервисов со шлюзом Лямбда-API, и через несколько попыток и неудач и успехов.

Вкратце, по моему опыту, лучше делегировать все вызовы API лямбда только с одним сопоставлением подстановок APIGateway, например

/api/{+proxy} -> Lambda

Если вы когда-либо использовали какие-либо фреймворки, такие как grape, вы знаете, что при создании API-интерфейсов такие функции, как "промежуточного"
"обработка глобальных исключений"
"каскадная маршрутизация"
"проверка параметров"

действительно важны. по мере роста вашего API, практически невозможно управлять всеми маршрутами с помощью сопоставления API-шлюза, а также API Gateway не поддерживает эту функцию.

Кроме того, на самом деле это практически не разрушает лямбда для каждой конечной точки для разработки или развертывания.

из вашего примера,

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

Представьте, что у вас есть данные ORM, логика аутентификации пользователя, общий файл вида (например, data.erb).. то как вы собираетесь поделиться этим?

вы можете сломать, как,

api/auth/{+proxy} -> AuthServiceLambda  
api/data/{+proxy} -> DataServiceLambda  

но не как "на конечную точку". вы можете найти концепцию микросервиса и наилучшую практику о том, как вы можете разделить службу

для этих веб-фреймворков, таких как функции, checkout это, мы только что создали веб-фреймворк для лямбды, так как мне это нужно в моей компании.

Ответ 3

Я бы прокомментировал, чтобы просто добавить пару пунктов в отличный ответ Dave Maple, но у меня пока нет достаточных очков репутации, поэтому я добавлю комментарии здесь.

Я начал спускаться по пути нескольких конечных точек, указывая на одну функцию Lambda, которая могла бы обрабатывать каждую конечную точку по-другому, обращаясь к свойству ресурса события. Попытавшись, я теперь разделил их на отдельные функции по причинам, которые предложил Дэйв плюс:

  • Мне удобнее проходить журналы и мониторы, когда функции разделены.
  • Один нюанс, который, как новичок, я сначала не забирал, так это то, что вы можете иметь одну базу кода и развернуть тот же самый код, что и несколько функций Lambda. Это позволяет использовать преимущества разделения функций и преимущества консолидированного подхода в базе кода.
  • Вы можете использовать AWS CLI для автоматизации задач по нескольким функциям, чтобы уменьшить/устранить недостатки управления отдельными функциями. Например, у меня есть script, который обновляет 10 функций с тем же кодом.

Ответ 4

Насколько я знаю, AWS допускает только один обработчик на функцию лямбда. Вот почему я создал небольшой механизм "маршрутизации" с помощью Java Generics (для более сильных проверок типов во время компиляции). В следующем примере вы можете вызвать несколько методов и передать разные типы объектов Лямбде и обратно через один обработчик 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 не может быть применено к...) в вашей лямбда-функции (потому что 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);
}

Ответ 5

Как я вижу, выбор одного или нескольких API - это функция следующих соображений:

  • Безопасность: я считаю, что это самая большая проблема с наличием единой структуры API. Возможно, у вас может быть другой профиль безопасности для разных частей требования.

  • Подумайте о модели микросервиса с точки зрения бизнеса: Вся цель любого API должна обслуживать некоторые запросы, поэтому она должна быть хорошо понята и проста в использовании. Соответствующие API-интерфейсы должны быть объединены. Например, если у вас есть мобильный клиент, и ему требуется 10 вещей, которые нужно вытащить из БД и выйти из него, имеет смысл иметь 10 конечных точек в один API. Но это должно быть в разумных пределах и должно рассматриваться в контексте общей конструкции решения. Например, если вы разрабатываете продукт для расчета заработной платы, вы можете подумать, что у вас есть отдельные модули для управления отпусками и управления данными пользователей. Даже если они часто используются одним клиентом, они все равно должны быть разными API, потому что их бизнес-смысл отличается.

  • Повторяемость: применяется как для повторного использования кода, так и для функциональности. Повторное использование кода - это более легкая задача для решения, т.е. сбор общих модулей для общих требований и их создание в виде библиотек. Повторное использование функциональности сложнее решить. На мой взгляд, большинство случаев могут быть решены путем изменения способа вывода конечных точек/функций, поскольку, если вам требуется дублирование функциональности, это означает, что ваш первоначальный дизайн недостаточно подробен.

Просто нашел ссылку в другом сообщении SO, которое лучше всего соответствует