Lamba機能をプロキシするAWS APIを持っています。私は現在、別々のラムダ関数で異なるエンドポイントを使用しています:
api.com/getData --> getData
api.com/addData --> addData
api.com/signUp --> signUp
すべてのエンドポイントと機能を管理するプロセスは面倒になります。クエリ文字列に基づいて何を行うかを決定する1つのラムダ関数に単一のエンドポイントを使用する場合、デメリットはありますか?
api.com/exec&func=getData --> exec --> if(params.func === 'getData') { ... }
複数のメソッドを単一のラムダ関数にマッピングすることは完全に有効であり、多くの人々は現在、個々のメソッドごとにAPIゲートウェイリソースとラムダ関数を作成するのではなく、この方法論を使用しています。
すべてのリクエストを単一の関数にプロキシすることを検討することもできます。 API Gatewayの作成に関する次のドキュメントをご覧ください=> 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パラメーター、ボディなどにアクセスできるようになり、それを使用して、1つのLambda関数でリクエストを異なる方法で処理できます(基本的に独自のルーティングを実装します)。
意見として、このアプローチにはいくつかの利点と欠点があります。それらの多くは、特定のユースケースに依存しています。
私はLambda-API Gatewayで5〜6個のマイクロサービスを構築してきましたが、いくつかの試行錯誤と成功を経験しました。
要するに、私の経験から、すべてのAPI呼び出しをラムダに1つのAPIGatewayワイルドカードマッピングだけで委任する方が良いでしょう。
/api/{+proxy} -> Lambda
grape のようなフレームワークを使用したことがあるなら、APIを作成するとき、
「ミドルウェア」
「グローバル例外処理」
「カスケードルーティング」
"パラメータ検証"
は非常に重要です。 APIが大きくなると、API Gatewayマッピングですべてのルートを管理することはほぼ不可能になり、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
「エンドポイントごと」ではありません。マイクロサービスの概念と、サービスの分割方法に関するベストプラクティスを参照できます。
機能のようなWebフレームワークの場合、チェックアウト this 私は会社でこれが必要だったので、ラムダ用のWebフレームワークを作成しました。
Dave Maple's great answerにいくつかのポイントを追加するようにコメントしますが、まだ十分な評価ポイントがないため、ここにコメントを追加します。
イベントの「リソース」プロパティにアクセスすることで、各エンドポイントを別々に処理できる1つのLambda関数を指す複数のエンドポイントのパスを探し始めました。試した後、Daveが提案した理由により、それらを別々の関数に分離しました。
私の知る限り、AWSはLambda関数ごとに1つのハンドラーのみを許可します。だからこそ、私はJava Generics(コンパイル時のより強力な型チェックのために)で小さな「ルーティング」メカニズムを作成しました。次の例では複数のメソッドを呼び出し、異なるオブジェクトを渡すLambdaに入力し、1つの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関数内(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);
}
私が見るように、単一または複数のAPIを選択することは、次の考慮事項の機能です。
セキュリティ:これは、単一のAPI構造を持つことの最大の課題だと思います。要件の異なる部分に異なるセキュリティプロファイルを設定することも可能です。
ビジネスの観点からマイクロサービスモデルを考える:APIの全体的な目的はいくつかのリクエストに対応する必要があるため、十分に理解され、使いやすい必要があります。したがって、関連するAPIを組み合わせる必要があります。たとえば、モバイルクライアントを使用していて、DBから10個のデータを取得および取得する必要がある場合、10個のエンドポイントを1つのAPIに含めることは理にかなっています。しかし、これは理にかなっているはずであり、全体的なソリューション設計のコンテキストで見られるべきです。たとえば、給与計算製品を設計する場合、休暇管理とユーザー詳細管理用に別々のモジュールがあると考えるかもしれません。 1つのクライアントで頻繁に使用される場合でも、ビジネス上の意味が異なるため、異なるAPIである必要があります。
再利用性:コードと機能の両方の再利用性に適用されます。コードの再利用性は、簡単に解決できる問題です。つまり、共有要件の共通モジュールをビルドし、ライブラリとしてビルドします。機能の再利用性は解決が困難です。私の考えでは、機能の重複が必要な場合、初期設計が十分に詳細ではないことを意味するため、エンドポイント/機能のレイアウト方法を再設計することで、ほとんどのケースを解決できます。
リンク を別のSO