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, которое лучше всего соответствует