AWS Lambda + QuarkusでのREST API作成

# はじめに
QuarkusはコンテナファーストのJavaフレームワークですが、AWS Lambdaにも対応しています。当記事では、AWS Lambda上で、QuarkusベースのREST APIを作成・動作させる手順を説明します。

# 前提環境
当記事では以下を前提とします（ツールのバージョンは一致しなくても実施は可能）。

- AWSアカウントを保有していること
- Quarkus CLI 3.2.3 Final
- Maven 3.8.7
- AWS CLI 2.13.10

# 実施手順
基本的には、[Quarkusのドキュメント](https://ja.quarkus.io/guides/amazon-lambda)をベースに進めていきます。

## Mavenプロジェクトの作成
まず、Mavenでプロジェクトを作成します。

```bash
mvn archetype:generate \
       -DarchetypeGroupId=io.quarkus \
       -DarchetypeArtifactId=quarkus-amazon-lambda-archetype \
       -DarchetypeVersion=3.2.4.Final
```

上記のコマンドを実行後、groupIdなど、それぞれ以下の値を入力します。

- groupId：org.example
- artifactId：quarkus-lambda-example
- version：<ブランク>
- package：<ブランク>

以下が実行例です。

```shell
Define value for property 'groupId': org.example
Define value for property 'artifactId': quarkus-lambda-example
Define value for property 'version' 1.0-SNAPSHOT: : 
Define value for property 'package' org.example: : 
Confirm properties configuration:
groupId: org.example
artifactId: quarkus-lambda-example
version: 1.0-SNAPSHOT
package: org.example
Y: :
```

上記により、`quarkus-lambda-example`というディレクトリが作成されるので、そのディレクトリに移動します（以降は`quarkus-lambda-example`ディレクトリで作業します）。

作成されたプロジェクトの構造は以下のようになっています。
![2023-08-22 at 16.23.20@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/17e012b7-61b2-5f4c-459b-7d4fb2d0be41.png)

作成された`pom.xml`ファイルの中を見てみます。Quarkusでは`quarkus-amazon-lambda`エクステンションが提供されており、これがLambda独特の要素を吸収してくれます。

```xml:pom.xml
    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-amazon-lambda</artifactId>
        </dependency>
        <!-- 中略 -->
    </dependencies>
```

JavaでLambda関数を作成する際、ハンドラクラスを作成しますが、Quarkusの`quarkus-amazon-lambda`エクステンションでは、1つのプロジェクト内に複数のハンドラクラスを用意し、`application.properties`による設定、または環境変数によって、Lambda関数で実行されるハンドラクラスを切り替えることが可能になっています。

Mavenでのプロジェクト作成によるデフォルトで、`application.properties`では`test`が指定されているので、`@Named("test")`が設定されているハンドラクラス（`TestLambda.java`）が実行されます。

```properties:application.properties
quarkus.lambda.handler=test
```

`TestLambda.java`を見てみます。ここでは、`ProcessingService`クラスをインジェクトしており、実質的な処理は`ProcessingService`クラスが担っています。

```java:TestLambda.java
package org.example;

import jakarta.inject.Inject;
import jakarta.inject.Named;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

@Named("test")
public class TestLambda implements RequestHandler<InputObject, OutputObject> {

    @Inject
    ProcessingService service;

    @Override
    public OutputObject handleRequest(InputObject input, Context context) {
        return service.process(input).setRequestId(context.getAwsRequestId());
    }
}
```

`ProcessingService`クラスは以下のようになっています。Lambdaの入力で与えられた`greeting`、`name`の値を文字列連結して返すという単純な内容です。

```java:ProcessingService.java
package org.example;

import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ProcessingService {

    public static final String CAN_ONLY_GREET_NICKNAMES = "Can only greet nicknames";

    public OutputObject process(InputObject input) {
        if (input.getName().equals("Stuart")) {
            throw new IllegalArgumentException(CAN_ONLY_GREET_NICKNAMES);
        }
        String result = input.getGreeting() + " " + input.getName();
        OutputObject out = new OutputObject();
        out.setResult(result);
        return out;
    }
}
```

## ビルド
では次に、ソースコードをビルドし、デプロイパッケージを作成します。以下のコマンドを実行します。

```bash
quarkus build
```

これにより、`target`ディレクトリが作成され、以下のパッケージ群がビルドされます。特に赤枠で囲った`function.zip`と`manage.sh`が重要で、それぞれ以下の内容になっています。

- function.zip：Lambdaにデプロイされるファイル
- manage.sh：AWS CLIを使用し、Lambdaへのデプロイ処理を実行するスクリプト
![2023-08-22 at 18.04.47@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/d8c6fd39-8f8b-4b2b-e543-3858caffff7c.png)

`manage.sh`の中身を見ると、ファイルの中程に以下のような行があります。変数で、Lambda関数の名前、ハンドラー、ランタイムを指定しています。

```sh
FUNCTION_NAME=QuarkusLambdaExample
HANDLER=io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest
RUNTIME=java11
```

## AWS CLIの設定
`manage.sh`ではAWS CLIを使用しますが、AWS CLIを使用するにあたり、事前の設定を実施します。まず、以下のコマンドを実行し、AWS CLIを使用できるようにします。

```bash
aws configure
```

上記のコマンドでは、AWSのIAMユーザのAccess Key、Secret Access Keyが聞かれます。これらは、AWSコンソールの「IAM」→「ユーザー」→自身のIAMユーザー→「セキュリティ認証情報」→「アクセスキーを作成」で作成しておきます（詳細な手順は割愛します）。

## Lambda関数の実行ロール作成
次に、Lambda関数がAWSのリソースにアクセスできるようアクセス許可を付与するための実行ロールを作成します。詳細は[AWSのドキュメント](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/gettingstarted-awscli.html)を参照ですが、以下に手順のみを記載します。

```bash
# `lambda-ex`という名前のロールを作成する。
aws iam create-role --role-name lambda-ex --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'

# 作成したロールに AWSLambdaBasicExecutionRole ポリシーを付与する
# これにより、Lambda関数がログをCloudWatch Logsに出力することが可能になる
aws iam attach-role-policy --role-name lambda-ex --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
```

作成した`lambda-ex`ロールのARNを取得します。下記のコマンドで出力されたJSONの`Role.Arn`の値をメモしておきます。

```bash
aws iam get-role --role-name lambda-ex
```

## Lambda関数の作成
それではいよいよLambda関数を作成します。以下のコマンドを実行します。

```bash
LAMBDA_ROLE_ARN="<上記で取得したlambda-exロールのARN>" sh target/manage.sh create
```

コマンドが正常終了することを確認し、AWSコンソールのLambdaのページにアクセスすると、`manage.sh`で関数名として指定されていた「QuarkusLambdaExample」が作成されています。
![2023-08-22 at 18.27.34@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/0e5f3b12-c708-9cd2-b6e4-caaac35a892a.png)


では次に、作成した関数をテストしてみます。上記の関数一覧の「QuarkusLambdaExample」をクリックし、「テスト」タブを選択します。「イベント名」に適当な名前を入力し、「イベントJSON」に以下の内容を入力し、「テスト」ボタンをクリックします。

```json
{  
  "name": "Bill",  
  "greeting": "hello"  
}
```

![2023-08-22 at 18.28.44@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/3b2e65db-fa25-1cf0-e9f3-31b6380fab63.png)


テスト結果が以下の通り成功となり、レスポンスのJSONの`result`に`hello Bill`が出力されていればOKです。
![2023-08-22 at 18.37.54@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/e3409767-94b9-9182-8b51-89c2da496f12.png)



## API Gatewayの作成 & 外部からのAPIアクセス
次に、Lambda関数をAPI化し、外部（インターネット）からアクセスできるようにします。具体的にはAPI Gatewayを作成します。AWSコンソールのAPI Gatewayのページにアクセスし、「APIを作成」ボタンをクリックします。
![2023-08-22 at 18.43.29@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/603cb415-6b4c-162b-b832-abefd605d5ae.png)

「REST API」の「構築」ボタンをクリックします。
![2023-08-22 at 18.43.42@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/a3dcce42-21fd-8d97-f1af-456a7396af8f.png)

「API名」に適当な名前を入力し、「APIの作成」ボタンをクリックします。
![2023-08-22 at 18.44.07@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/3f410a89-beb4-7a2d-1469-51028949df39.png)

「アクション」→「メソッドの作成」を選択します。
![2023-08-22 at 18.44.37@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/09befa74-4731-3ef0-87fb-cb77a6f84f3a.png)

「POST」メソッドを選択し、チェックマークをクリックします。
![2023-08-22 at 18.45.19@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/f2b6c93f-97b3-6ab6-1ad0-d2515a51bde8.png)

「統合タイプ」で「Lambda関数」を選択し、「Lambda関数」で先ほど作成したLambda関数「QuarkusLambdaExample」を選択し、「保存」ボタンをクリックします。
![2023-08-22 at 18.45.31@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/c2891456-8e4b-1437-dc1a-8a695115c66e.png)

「テスト」をクリックします。
![2023-08-22 at 18.46.45@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/9c7a3a79-6099-79d2-f625-13ed4b28252b.png)

「リクエスト本文」に前述のJSONを入力し、「テスト」ボタンをクリックします。
![2023-08-22 at 18.47.06@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/eb4bab37-9dfa-fa14-da51-8f989d1a6d5c.png)

「ステータス」で`200`が返り、「レスポンス本文」でLambda関数本体のテスト実施時と同様のレスポンスが出力されていればOKです。
![2023-08-22 at 18.47.40@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/a7863d73-9e1a-a1de-d624-9fabae968788.png)

「アクション」→「APIのデプロイ」を選択し、APIのデプロイを行います。
![2023-08-22 at 18.48.33@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/2148f58e-163d-be6f-4e29-d3ad9f62f501.png)

「デプロイされるステージ」で「新しいステージ」を選択し、「ステージ名」を「example」とし、「デプロイ」ボタンをクリックします。
![2023-08-22 at 18.49.05@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/92656ad7-ee32-5989-985a-41199fe6ed4c.png)

すると、APIがデプロイされ、APIのエンドポイントが表示される（赤枠部分）のでメモします。
![2023-08-22 at 18.49.48@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/a06eabec-3c22-de1c-1c59-665bd8a54206.png)

ここまででAPIがデプロイされ、外部からアクセスできるようになりました。最後にAPIの稼働確認を実施します。以下の`curl`コマンドを実行します（URL部分は先ほどメモしたAPIのエンドポイントに置き換えてください）。以下のように、Lambda関数本体のテスト実施時と同様のレスポンスが出力されていればOKです。

```bash
curl -X POST -H "Content-Type: application/json" -d '{"name" : "Bill", "greeting" : "hello"}' https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/example
{"result":"hello Bill","requestId":"5aadf1cb-9460-47d5-90b2-8f6845817a0a"}
```