AWS Lambda + Java で Hello World

## 概要

- AWS (Amazon Web Services) Lambda で Java によるシンプルな Hello World を動かす
- ハンドラの実装には AWS Lambda Java コアライブラリ (aws-lambda-java-core) に用意されている定義済みインターフェイス RequestHandler を使用する
- JAR ファイルの生成には Maven を使用する

## ソースコード

### ファイル一覧

HelloWorld.java と pom.xml のみのシンプルな構成。

```
├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── example
                    └── HelloWorld.java
```

### HelloWorld.java

汎用的な JSON 入力データを処理できるように入力データを Map<String, Object> オブジェクトで受け取る。
出力も同じく Map<String, Object> オブジェクトで返す。

```java
package com.example;

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

import java.util.HashMap;
import java.util.Map;

public class HelloWorld implements RequestHandler<Map<String, Object>, Map<String, Object>> {

  /**
   * AWS Lambda のハンドラーメソッドです。
   *
   * @param input   入力データ
   * @param context AWS Lambda Context オブジェクト
   * @return 出力データ
   */
  @Override
  public Map<String, Object> handleRequest(Map<String, Object> input, Context context) {
    Map<String, Object> output = new HashMap<>();
    output.put("foo", "Hello, world");
    output.put("bar", "Goodbye, world");
    output.put("input", input); // 入力情報を見たいので出力に含める
    output.put("context", context); // コンテキスト情報を見たいので出力に含める
    return output;
  }
}
```

### Maven 用ビルド設定ファイル pom.xml

- AWS Lambda でサポートされている Java のバージョン 1.8 に合わせる
- AWS Lambda 用ライブラリ aws-lambda-java-core を導入する
- ライブラリも含めた JAR ファイルを生成するため maven-shade-plugin を導入する
 
[IDE なしで Maven を使用した \.jar デプロイパッケージの作成 \(Java\) \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-create-jar-pkg-maven-no-ide.html)

>「dependencies」セクションで、groupId (com.amazonaws) は Maven 中央レポジトリの Maven アーティファクト用の Amazon AWS グループ ID です。artifactId (aws-lambda-java-core) は、RequestHandler、RequestStreamHandler の定義、および Java アプリケーションで使用する Context AWS Lambda インターフェイスを提供する AWS Lambda のコアライブラリです。ビルド時に、Maven はこれらの依存関係を解決します。
>プラグインセクションでは、Apache maven-shade-plugin は、Maven がビルドプロセス中にダウンロードして使用するプラグインです。このプラグインは、デプロイパッケージであるスタンドアロン .jar (.zip ファイル) を作成するために、jar のパッケージ化に使用されます。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>lambda-hello-world</artifactId>
  <packaging>jar</packaging>
  <version>1.0</version>
  <name>lambda-hello-world</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-lambda-java-core</artifactId>
      <version>1.2.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.2.1</version>
        <configuration>
          <createDependencyReducedPom>false</createDependencyReducedPom>
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>
```

## JAR ファイルを作る

mvn package コマンドで JAR ファイルを生成。

```
$ mvn package
```

```
$ ls -l target/*.jar
-rw-r--r--@ 1 foo  staff  10047  8 25 10:08 target/lambda-hello-world-1.0.jar
-rw-r--r--@ 1 foo  staff   2692  8 25 10:08 target/original-lambda-hello-world-1.0.jar
```

AWS Lambda にデプロイするのは lambda-hello-world-1.0.jar ファイルとなる。
jar コマンドで含まれているファイル一覧を確認できる。

```
$ jar --list --file target/lambda-hello-world-1.0.jar 
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/HelloWorld.class
META-INF/maven/
META-INF/maven/com.example/
META-INF/maven/com.example/lambda-hello-world/
META-INF/maven/com.example/lambda-hello-world/pom.xml
META-INF/maven/com.example/lambda-hello-world/pom.properties
com/amazonaws/
com/amazonaws/services/
com/amazonaws/services/lambda/
com/amazonaws/services/lambda/runtime/
com/amazonaws/services/lambda/runtime/LambdaRuntime$1.class
com/amazonaws/services/lambda/runtime/CognitoIdentity.class
com/amazonaws/services/lambda/runtime/LambdaRuntime.class
com/amazonaws/services/lambda/runtime/ClientContext.class
com/amazonaws/services/lambda/runtime/Client.class
com/amazonaws/services/lambda/runtime/Context.class
com/amazonaws/services/lambda/runtime/LambdaRuntimeInternal.class
com/amazonaws/services/lambda/runtime/LambdaLogger.class
com/amazonaws/services/lambda/runtime/RequestHandler.class
com/amazonaws/services/lambda/runtime/RequestStreamHandler.class
META-INF/maven/com.amazonaws/
META-INF/maven/com.amazonaws/aws-lambda-java-core/
META-INF/maven/com.amazonaws/aws-lambda-java-core/pom.xml
META-INF/maven/com.amazonaws/aws-lambda-java-core/pom.properties
```

## Lambda 関数の作成

[Lambda Management Console](https://console.aws.amazon.com/lambda/) から Lambda 関数を作成する。

「一から作成」を選択。

<img width="527" alt="aws-lambda-java-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/535eea4a-4409-1351-cc5a-95ff83b0702c.png">

「基本的な情報」を入力する。
関数名: 任意の名前
ランタイム: Java 8
実行ロール: 基本的な Lambda アクセス権限で新しいロールを作成

<img width="953" alt="aws-lambda-java-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/81222d52-b81a-8627-dab7-bd6ebc334ee5.png">

「Designer」では特に何もしない。

<img width="955" alt="aws-lambda-java-3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/2c62ebe1-fb9d-9158-875b-365d2ebf7d5b.png">

「関数コード」を入力する。
コード エントリ タイプ: .zip または jar ファイルをアップロード
ランタイム: Java 8
ハンドラ: com.example.HelloWorld::handleRequest

「アップロード」をクリックして生成した JAR ファイル lambda-hello-world-1.0.jar をアップロードして、右上の「保存」をクリックする。

<img width="959" alt="aws-lambda-java-4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/d6ff99ea-e539-c9c0-0db6-6631d69ee126.png">

ページ右上の「テスト」をクリックして、新しいテストイベントを作成する。
イベントテンプレート: Hello World
イベント名: 任意の名前
入力データとなる JSON は任意のものに変更する (あるいはデフォルトのまま)。

<img width="830" alt="aws-lambda-java-5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/e6f3443d-8f4a-5f66-ad2f-45cd7fccbe2c.png">

ページ右上の「テスト」をクリックすると、テストを実行できる。結果が表示される。

<img width="958" alt="aws-lambda-java-6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/a8452f73-b2fd-815a-8ceb-0bb4399bcad8.png">

出力結果の例。

```json
{
  "input": {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
  },
  "bar": "Goodbye, world",
  "foo": "Hello, world",
  "context": {
    "awsRequestId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "logGroupName": "/aws/lambda/myJavaHelloWorld",
    "logStreamName": "2019/08/25/[$LATEST]XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "functionName": "myJavaHelloWorld",
    "functionVersion": "$LATEST",
    "invokedFunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:myJavaHelloWorld",
    "logger": {},
    "identity": {
      "identityId": "",
      "identityPoolId": ""
    },
    "remainingTimeInMillis": 14814,
    "memoryLimitInMB": 512
  }
}
```

## ハンドラについて

- ハンドラの実装方法は大きく分けて2パターンある
    - 任意の名前とパラメータを使用して独自のハンドラーメソッドを記述する
    - 定義済みインターフェイス (RequestStreamHandler または RequestHandler) を使用する
- 両者とも入出力のインターフェースは同じ
    - ハンドラには引数で入力データと Context オブジェクトが渡される

### RequestHandler インターフェース

[ハンドラ作成用に事前定義されているインターフェイスの利用 \(Java\) \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-handler-using-predefined-interfaces.html)

> 任意の名前とパラメータを使用して独自のハンドラーメソッドを記述する代わりに、AWS Lambda Java コアライブラリ (aws-lambda-java-core) に用意されている定義済みインターフェイスのいずれかを使用して、Lambda 関数ハンドラーを作成することもできます。

> いずれかの定義済みインターフェイス (RequestStreamHandler または RequestHandler) を実装し、インターフェイスに用意された handleRequest メソッドを実装できます。標準の Java の型を使用するか、ハンドラーの入出力（AWS Lambda が自動的に入出力をシリアル化して、データ型に一致させる）用のカスタム POJO 型を使用するかに応じて、これらのインターフェイスの 1 つを実装します。または Stream 型を使用してシリアル化をカスタマイズします。

javadoc による公式ドキュメントが見つからなかったので、GitHub にあるソースコードを参考にする。

[aws\-lambda\-java\-libs/RequestHandler\.java at master · aws/aws\-lambda\-java\-libs · GitHub](https://github.com/aws/aws-lambda-java-libs/blob/master/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/RequestHandler.java)

```java
/**
 * 
 * Lambda request handlers implement AWS Lambda Function application logic using plain old java objects 
 * as input and output.
 *
 * @param <I> The input parameter type
 * @param <O> The output parameter type
 */
public interface RequestHandler<I, O> {
    /**
     * Handles a Lambda Function request
     * @param input The Lambda Function input
     * @param context The Lambda execution environment context object.
     * @return The Lambda Function output
     */
    public O handleRequest(I input, Context context);
}
```

### 入力データ

[Java の AWS Lambda 関数ハンドラー \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-programming-model-handler-types.html)

> inputType – ハンドラーの最初のパラメータはハンドラーへの入力です。このパラメータには、イベントデータ (イベントソースによって発行される) またはユーザーが提供するカスタム入力 (文字列やカスタムデータオブジェクトなど) を指定できます。AWS Lambda がこのハンドラーを正常に呼び出せるようにするには、関数は、input パラメータのデータ型にシリアル化できる入力データとともに呼び出す必要があります。

[ハンドラーの入出力タイプ \(Java\) \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-programming-model-req-resp.html)

> AWS Lambda は、ハンドラーの次の入出力タイプをサポートします。
> ・Java のシンプルな型（AWS Lambda は、文字列型、整数型、ブール型、マップ型、およびリスト型をサポートします）
> ・POJO (Plain Old Java Object) 型
> ・ストリーム型（POJO を使用しない場合、または Lambda のシリアル化のアプローチがニーズに合わない場合は、バイトストリーム実装を使用できます）。詳細については、「例: ハンドラーの入出力にストリームを使用する (Java)」を参照してください。

### Context オブジェクト

[Java の AWS Lambda Context オブジェクト \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-context-object.html)

> Lambda で関数が実行されると、コンテキストオブジェクトはハンドラに渡されます。このオブジェクトは、呼び出し、関数、および実行関数に関する情報を示すメソッドおよびプロパティを提供します。

[aws\-lambda\-java\-libs/Context\.java at master · aws/aws\-lambda\-java\-libs · GitHub](https://github.com/aws/aws-lambda-java-libs/blob/master/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java)

> The context object allows you to access useful information available within the Lambda execution environment

### 戻り値について

[Java の AWS Lambda 関数ハンドラー \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-programming-model-handler-types.html)

> outputType – Lambda 関数を同期的に呼び出す (RequestResponse 呼び出しタイプを使用) 場合は、サポートされているいずれかのデータ型を使用して関数の出力を返すことができます。たとえば、Lambda 関数をモバイルアプリケーションのバックエンドとして使用する場合、これを同期的に呼び出しています。出力データ型は JSON にシリアル化されます。
> Lambda 関数を非同期的に呼び出す（Event 呼び出しタイプを使用）計画の場合、outputType は void である必要があります。たとえば、Amazon S3 や Amazon SNS などのイベントソースとともに AWS Lambda を使用する場合、これらのイベントソースは Event 呼び出しタイプを使用して Lambda 関数を呼び出します。

## 参考資料
- [コンソールで Lambda 関数を作成する \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/getting-started-create-function.html)
- [Java による Lambda 関数のビルド \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-programming-model.html)