1. 概要

AWS Lambda は、アマゾンウェブサービスが提供するサーバーレスコンピューティングサービスです。

以前の2つの記事では、 Javaを使用してAWSLambda関数を作成する方法と、Lambda関数からDynamoDBにアクセスする方法について説明しました。

このチュートリアルでは、AWS Gateway を使用して、Lambda関数をRESTエンドポイントとして公開する方法について説明します。

次のトピックについて詳しく見ていきます。

  • APIゲートウェイの基本的な概念と用語
  • Lambdaプロキシ統合を使用したLambda関数とAPIGatewayの統合
  • APIの作成、その構造、およびAPIリソースをLambda関数にマッピングする方法
  • APIのデプロイとテスト

2. 基本と用語

API Gatewayは完全に管理されたサービスであり、開発者はあらゆる規模でAPIを作成、公開、維持、監視、保護できます

一貫性のあるスケーラブルなHTTPベースのプログラミングインターフェース(RESTfulサービスとも呼ばれます)を実装して、Lambda関数、その他のAWSサービス(EC2、S3、DynamoDBなど)、および任意のHTTPエンドポイントなどのバックエンドサービスにアクセスできます。 ]。

機能には次のものが含まれますが、これらに限定されません。

  • 交通管理
  • 承認とアクセス制御
  • モニタリング
  • APIバージョン管理
  • 攻撃を防ぐためのスロットルリクエスト

AWS Lambdaと同様に、API Gatewayは自動的にスケールアウトされ、API呼び出しごとに課金されます。

詳細については、公式ドキュメントをご覧ください。

2.1. 条項

API Gateway は、バックエンドHTTPエンドポイント、AWS Lambda関数、およびその他のAWSサービスを公開するためのRESTfulアプリケーションプログラミングインターフェースの作成、デプロイ、および管理をサポートするAWSサービスです。

API Gateway API は、Lambda関数、他のAWSサービス、またはバックエンドのHTTPエンドポイントと統合できるリソースとメソッドのコレクションです。 APIは、API構造を形成するリソースで構成されています。 各APIリソースは、一意のHTTP動詞が必要な1つ以上のAPIメソッドを公開できます。

APIを公開するには、 APIデプロイメントを作成し、それをいわゆるステージに関連付ける必要があります。 ステージは、APIの時点でのスナップショットのようなものです。 APIを再デプロイする場合、既存のステージを更新するか、新しいステージを作成することができます。 これにより、 dev ステージ、 test ステージ、さらには v1 のような複数の製品バージョンなど、同時に異なるバージョンのAPIが可能になります。 ]、v2など。

Lambdaプロキシ統合は、Lambda関数とAPIGatewayを統合するための簡略化された構成です。

API Gatewayは、リクエスト全体をバックエンドのLambda関数への入力として送信します。 応答に関しては、APIGatewayはLambda関数の出力をフロントエンドHTTP応答に変換します。

3. 依存関係

AWS Lambda Using DynamoDB WithJavaの記事と同じ依存関係が必要になります。

さらに、 JSONSimpleライブラリも必要です。

<dependency>
    <groupId>com.googlecode.json-simple</groupId>
    <artifactId>json-simple</artifactId>
    <version>1.1.1</version>
</dependency>

4. Lambda関数の開発とデプロイ

このセクションでは、JavaでLambda関数を開発およびビルドし、AWSコンソールを使用してデプロイし、簡単なテストを実行します。

API GatewayをLambdaと統合する基本的な機能を示したいので、2つの関数を作成します。

  • 関数1:は、PUTメソッドを使用してAPIからペイロードを受信します
  • 関数2:は、APIからのHTTPパスパラメーターまたはHTTPクエリパラメーターの使用方法を示しています

実装に関しては、1つの RequestHandler クラスを作成します。このクラスには、関数ごとに1つずつ、合計2つのメソッドがあります。

4.1. モデル

実際のリクエストハンドラーを実装する前に、データモデルを簡単に見てみましょう。

public class Person {

    private int id;
    private String name;

    public Person(String json) {
        Gson gson = new Gson();
        Person request = gson.fromJson(json, Person.class);
        this.id = request.getId();
        this.name = request.getName();
    }

    public String toString() {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        return gson.toJson(this);
    }

    // getters and setters
}

このモデルは、2つのプロパティを持つ1つの単純なPersonクラスで構成されています。 唯一の注目すべき部分は、JSON文字列を受け入れる Person(String)コンストラクターです。

4.2. RequestHandlerクラスの実装

AWS Lambda With Java の記事と同様に、RequestStreamHandlerインターフェースの実装を作成します。

public class APIDemoHandler implements RequestStreamHandler {

    private static final String DYNAMODB_TABLE_NAME = System.getenv("TABLE_NAME"); 
    
    @Override
    public void handleRequest(
      InputStream inputStream, OutputStream outputStream, Context context)
      throws IOException {

        // implementation
    }

    public void handleGetByParam(
      InputStream inputStream, OutputStream outputStream, Context context)
      throws IOException {

        // implementation
    }
}

ご覧のとおり、 RequestStreamHander インターフェースは、 handeRequest()という1つのメソッドのみを定義します。 とにかく、ここで行ったように、同じクラスでさらに関数を定義できます。 もう1つのオプションは、関数ごとにRequestStreamHanderの実装を1つ作成することです。

特定のケースでは、簡単にするために前者を選択しました。 ただし、パフォーマンスやコードの保守性などの要素を考慮して、ケースバイケースで選択する必要があります。

また、TABLE_NAME環境変数からDynamoDBテーブルの名前を読み取ります。 その変数は、後で展開中に定義します。

4.3. 機能1の実装

最初の関数では、 APIゲートウェイからペイロード(PUTまたはPOSTリクエストなど)を取得する方法を示します。

public void handleRequest(
  InputStream inputStream, 
  OutputStream outputStream, 
  Context context)
  throws IOException {

    JSONParser parser = new JSONParser();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    JSONObject responseJson = new JSONObject();

    AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient();
    DynamoDB dynamoDb = new DynamoDB(client);

    try {
        JSONObject event = (JSONObject) parser.parse(reader);

        if (event.get("body") != null) {
            Person person = new Person((String) event.get("body"));

            dynamoDb.getTable(DYNAMODB_TABLE_NAME)
              .putItem(new PutItemSpec().withItem(new Item().withNumber("id", person.getId())
                .withString("name", person.getName())));
        }

        JSONObject responseBody = new JSONObject();
        responseBody.put("message", "New item created");

        JSONObject headerJson = new JSONObject();
        headerJson.put("x-custom-header", "my custom header value");

        responseJson.put("statusCode", 200);
        responseJson.put("headers", headerJson);
        responseJson.put("body", responseBody.toString());

    } catch (ParseException pex) {
        responseJson.put("statusCode", 400);
        responseJson.put("exception", pex);
    }

    OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8");
    writer.write(responseJson.toString());
    writer.close();
}

前に説明したように、Lambdaプロキシ統合を使用するようにAPIを後で構成します。 API Gatewayは、InputStreamパラメーターのLambda関数に完全なリクエストを渡すことを期待しています。

含まれているJSON構造から関連する属性を選択するだけです。

ご覧のとおり、この方法は基本的に3つのステップで構成されています。

  1. 入力ストリームからbodyオブジェクトをフェッチし、そこからPersonオブジェクトを作成します
  2. そのPersonオブジェクトをDynamoDBテーブルに保存する
  3. 応答のbody、カスタムヘッダー、HTTPステータスコードなど、いくつかの属性を保持できるJSONオブジェクトを構築する

ここで言及する価値のある1つのポイント:API Gatewayは、bodyString(要求と応答の両方)であることを想定しています。

APIGatewayからStringbodyとして取得する予定なので、bodyStringにキャストし、を初期化します。 Person オブジェクト:

Person person = new Person((String) event.get("body"));

API Gatewayは、応答bodyStringであることも想定しています。

responseJson.put("body", responseBody.toString());

このトピックは、公式ドキュメントでは明示的に言及されていません。 ただし、よく見ると、リクエストのスニペットとレスポンスの両方で、body属性がStringであることがわかります。

利点は明らかです。JSONがAPIGatewayとLambda関数の間の形式であっても、実際の本文にはプレーンテキスト、JSON、XMLなどを含めることができます。 その場合、フォーマットを正しく処理するのはLambda関数の責任です。

後でAWSコンソールで関数をテストすると、リクエストとレスポンスの本文がどのように表示されるかがわかります。

以下の2つの機能についても同様です。

4.4. 機能2の実装

2番目のステップでは、IDを使用してデータベースから Person アイテムを取得するために、パスパラメーターまたはクエリ文字列パラメーターを使用する方法を示します。

public void handleGetByParam(
  InputStream inputStream, OutputStream outputStream, Context context)
  throws IOException {

    JSONParser parser = new JSONParser();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    JSONObject responseJson = new JSONObject();

    AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient();
    DynamoDB dynamoDb = new DynamoDB(client);

    Item result = null;
    try {
        JSONObject event = (JSONObject) parser.parse(reader);
        JSONObject responseBody = new JSONObject();

        if (event.get("pathParameters") != null) {
            JSONObject pps = (JSONObject) event.get("pathParameters");
            if (pps.get("id") != null) {
                int id = Integer.parseInt((String) pps.get("id"));
                result = dynamoDb.getTable(DYNAMODB_TABLE_NAME).getItem("id", id);
            }
        } else if (event.get("queryStringParameters") != null) {
            JSONObject qps = (JSONObject) event.get("queryStringParameters");
            if (qps.get("id") != null) {

                int id = Integer.parseInt((String) qps.get("id"));
                result = dynamoDb.getTable(DYNAMODB_TABLE_NAME)
                  .getItem("id", id);
            }
        }
        if (result != null) {
            Person person = new Person(result.toJSON());
            responseBody.put("Person", person);
            responseJson.put("statusCode", 200);
        } else {
            responseBody.put("message", "No item found");
            responseJson.put("statusCode", 404);
        }

        JSONObject headerJson = new JSONObject();
        headerJson.put("x-custom-header", "my custom header value");

        responseJson.put("headers", headerJson);
        responseJson.put("body", responseBody.toString());

    } catch (ParseException pex) {
        responseJson.put("statusCode", 400);
        responseJson.put("exception", pex);
    }

    OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8");
    writer.write(responseJson.toString());
    writer.close();
}

ここでも、3つのステップが関係しています。

  1. pathParametersまたはid属性を持つqueryStringParameters配列が存在するかどうかを確認します。
  2. true の場合、所属する値を使用して、データベースからそのIDを持つPersonアイテムを要求します。
  3. 受信したアイテムのJSON表現を応答に追加します。

公式ドキュメントには、プロキシ統合の入力形式および出力形式の詳細な説明が記載されています。

4.5. 建築基準法

ここでも、Mavenを使用してコードを簡単にビルドできます。

mvn clean package shade:shade

JARファイルはtargetフォルダーの下に作成されます。

4.6. DynamoDBテーブルの作成

JavaでDynamoDBを使用するAWSLambdaで説明されているようにテーブルを作成できます。

テーブル名としてPerson、主キー名として id 、主キーのタイプとしてNumberを選択しましょう。

4.7. AWSコンソールを介したコードのデプロイ

コードを作成してテーブルを作成したら、関数を作成してコードをアップロードできます。

これは、 AWS Lambda with Java の記事の手順1〜5を、2つの方法のそれぞれに対して1回繰り返すことで実行できます。

次の関数名を使用してみましょう。

  • handleRequestメソッドのStorePersonFunction(関数1)
  • GetPersonByHTTPParamFunction for handleGetByParam メソッド(関数2)

また、値“ Person”で環境変数TABLE_NAMEを定義する必要があります。

4.8. 機能のテスト

実際のAPIGatewayの部分に進む前に、AWSコンソールでクイックテストを実行して、Lambda関数が正しく実行され、プロキシ統合フォーマットを処理できることを確認できます。

AWS ConsoleからのLambda関数のテストは、 AWS Lambda withJavaの記事で説明されているように機能します。

ただし、テストイベントを作成するときは、関数が期待する特別なプロキシ統合形式を考慮する必要があります。 API Gateway AWS Proxy テンプレートを使用して必要に応じてカスタマイズするか、次のイベントをコピーして貼り付けることができます。

StorePersonFunction の場合、次を使用する必要があります。

{
    "body": "{\"id\": 1, \"name\": \"John Doe\"}"
}

前に説明したように、 body は、JSON構造が含まれている場合でも、タイプStringである必要があります。 その理由は、APIGatewayが同じ形式でリクエストを送信するためです。

次の応答が返されます。

{
    "isBase64Encoded": false,
    "headers": {
        "x-custom-header": "my custom header value"
    },
    "body": "{\"message\":\"New item created\"}",
    "statusCode": 200
}

ここでは、応答のbodyStringであることがわかりますが、JSON構造が含まれています。

GetPersonByHTTPParamFunction。の入力を見てみましょう。

パスパラメータ機能をテストする場合、入力は次のようになります。

{
    "pathParameters": {
        "id": "1"
    }
}

また、クエリ文字列パラメータを送信するための入力は次のようになります。

{
    "queryStringParameters": {
        "id": "1"
    }
}

応答として、両方のケースのメソッドについて次のように取得する必要があります。

{
  "headers": {
    "x-custom-header": "my custom header value"
  },
  "body": "{\"Person\":{\n  \"id\": 88,\n  \"name\": \"John Doe\"\n}}",
  "statusCode": 200
}

ここでも、bodyStringです。

5. APIの作成とテスト

前のセクションでLambda関数を作成してデプロイした後、 AWSConsoleを使用して実際のAPIを作成できるようになりました。

基本的なワークフローを見てみましょう。

  1. AWSアカウントでAPIを作成します。
  2. APIのリソース階層にリソースを追加します。
  3. リソースに対して1つ以上のメソッドを作成します。
  4. メソッドと所属するLambda関数の間の統合を設定します。

次のセクションでは、2つの機能のそれぞれについて手順2〜4を繰り返します。

5.1. APIの作成

APIを作成するには、次のことを行う必要があります。

  1. https://console.aws.amazon.com/apigatewayでAPIGatewayコンソールにサインインします
  2. 「はじめに」をクリックし、「新しいAPI」を選択します
  3. APIの名前( TestAPI )を入力し、[APIの作成]をクリックして確認します

APIを作成したら、API構造を作成し、それをLambda関数にリンクできます。

5.2. 関数1のAPI構造

StorePersonFunction には、次の手順が必要です。

  1. 「リソース」ツリーで親リソース項目を選択し、「アクション」ドロップダウンメニューから「リソースの作成」を選択します。 次に、[新しい子リソース]ペインで次の操作を行う必要があります。
    • 「リソース名」入力テキストフィールドに名前として「Persons」と入力します
    • 「リソースパス」入力テキストフィールドにデフォルト値を残します
    • 「リソースの作成」を選択します
  2. 作成したリソースを選択し、[アクション]ドロップダウンメニューから[メソッドの作成]を選択して、次の手順を実行します。
    • [HTTPメソッド]ドロップダウンリストから[PUT]を選択し、チェックマークアイコンを選択して選択内容を保存します
    • 統合タイプとして「LambdaFunction」を残し、「UseLambdaProxyintegration」オプションを選択します
    • 以前にLambda関数をデプロイした「LambdaRegion」からリージョンを選択します
    • 「LambdaFunction」に「StorePersonFunction」と入力します
  3. 「保存」を選択し、「Lambda関数へのアクセス許可の追加」でプロンプトが表示されたら「OK」で確認します

5.3. 関数2のAPI構造–パスパラメーター

パスパラメータを取得する手順は似ています。

  1. [リソース]ツリーで/ peoples リソース項目を選択し、[アクション]ドロップダウンメニューから[リソースの作成]を選択します。 次に、[新しい子リソース]ペインで次の操作を行う必要があります。
    • 「リソース名」入力テキストフィールドに名前として「Person」と入力します
    • 「リソースパス」入力テキストフィールドを「{id}」に変更します
    • 「リソースの作成」を選択します
  2. 作成したリソースを選択し、[アクション]ドロップダウンメニューから[メソッドの作成]を選択して、次の手順を実行します。
    • [HTTPメソッド]ドロップダウンリストから[GET]を選択し、チェックマークアイコンを選択して選択内容を保存します
    • 統合タイプとして「LambdaFunction」を残し、「UseLambdaProxyintegration」オプションを選択します
    • 以前にLambda関数をデプロイした「LambdaRegion」からリージョンを選択します
    • 「LambdaFunction」に「GetPersonByHTTPParamFunction」と入力します
  3. 「保存」を選択し、「Lambda関数へのアクセス許可の追加」でプロンプトが表示されたら「OK」で確認します

注:ここでは、「リソースパス」パラメーターを「{id}」に設定することが重要です。これは、 GetPersonByPathParamFunction が、このパラメーターの名前をこのように正確に指定することを想定しているためです。

5.4. 関数2のAPI構造–クエリ文字列パラメーター

リソースを作成する必要はなく、代わりにidパラメーターのクエリパラメーターを作成する必要があるため、クエリ文字列パラメーターを受け取る手順は少し異なります。

  1. [リソース]ツリーで/peoples リソース項目を選択し、[アクション]ドロップダウンメニューから[メソッドの作成]を選択して、次の手順を実行します。
    • [HTTPメソッド]ドロップダウンリストから[GET]を選択し、チェックマークアイコンを選択して選択内容を保存します
    • 統合タイプとして「LambdaFunction」を残し、「UseLambdaProxyintegration」オプションを選択します
    • 以前にLambda関数をデプロイした「LambdaRegion」からリージョンを選択します
    • 「LambdaFunction」に「GetPersonByHTTPParamFunction」と入力します。
  2. 「保存」を選択し、「Lambda関数へのアクセス許可の追加」でプロンプトが表示されたら「OK」で確認します
  3. 右側の「メソッドリクエスト」を選択し、次の手順を実行します。
    • URLクエリ文字列パラメータリストを展開します
    • 「クエリ文字列の追加」をクリックします
    • 名前フィールドに“ id” と入力し、チェックマークアイコンを選択して保存します
    • 「必須」チェックボックスを選択します
    • パネル上部の「Requestvalidator」の横にあるペン記号をクリックし、「Validate query string parameters and headers」を選択して、チェックマークアイコンを選択します

注: GetPersonByHTTPParamFunction はこのパラメーターがこのように正確に名前付けされることを期待しているため、「QueryString」パラメーターを「id」に設定することが重要です。

5.5. APIのテスト

これでAPIの準備が整いましたが、まだ公開されていません。 公開する前に、まずコンソールからクイックテストを実行します

そのためには、「リソース」ツリーでテストするそれぞれの方法を選択し、「テスト」ボタンをクリックします。 次の画面では、HTTP経由でクライアントに送信するので、入力を入力できます。

StorePersonFunction の場合、「RequestBody」フィールドに次の構造を入力する必要があります。

{
    "id": 2,
    "name": "Jane Doe"
}

パスパラメータを使用するGetPersonByHTTPParamFunctionの場合、「パス」の下の「{id}」フィールドに値として2を入力する必要があります。

クエリ文字列パラメータを使用するGetPersonByHTTPParamFunctionの場合、[クエリ文字列]の下の[{persons}]フィールドに値として id =2と入力する必要があります。

5.6. APIのデプロイ

これまで、APIは公開されていなかったため、AWSコンソールからのみ利用できました。

前に説明したように、 APIをデプロイするときは、APIのスナップショットのようなステージに関連付ける必要があります。 APIを再デプロイする場合、既存のステージを更新するか、新しいステージを作成することができます

APIのURLスキームがどのようになるか見てみましょう。

https://{restapi-id}.execute-api.{region}.amazonaws.com/{stageName}

展開には、次の手順が必要です。

  1. 「API」ナビゲーションペインで特定のAPIを選択します
  2. [リソース]ナビゲーションペインで[アクション]を選択し、[アクション]ドロップダウンメニューから[APIのデプロイ]を選択します
  3. [展開ステージ]ドロップダウンから[[新しいステージ]]を選択し、[ステージ名]に「テスト」と入力し、オプションでステージと展開の説明を入力します
  4. 「デプロイ」を選択してデプロイメントをトリガーします

最後の手順の後、コンソールはAPIのルートURLを提供します(例: https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test )。

5.7. エンドポイントの呼び出し

APIは現在公開されているため、任意のHTTPクライアントを使用して呼び出すことができます

cURL を使用すると、呼び出しは次のようになります。

StorePersonFunction

curl -X PUT 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons' \
  -H 'content-type: application/json' \
  -d '{"id": 3, "name": "Richard Roe"}'

GetPersonByHTTPParamFunction パスパラメーターの場合:

curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons/3' \
  -H 'content-type: application/json'

GetPersonByHTTPParamFunction クエリ文字列パラメーターの場合:

curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons?id=3' \
  -H 'content-type: application/json'

6. 結論

この記事では、AWS API Gatewayを使用して、AWSLambda関数をRESTエンドポイントとして利用できるようにする方法について説明しました。

API Gatewayの基本的な概念と用語を調べ、Lambdaプロキシ統合を使用してLambda関数を統合する方法を学びました。

最後に、APIを作成、デプロイ、テストする方法を確認しました。

いつものように、この記事のすべてのコードは、GitHubから入手できます。