いまからはじめるJavaでAWS Lambda(ラムダ)  前編

# いまからはじめるJavaでAWS Lambda(ラムダ)

AWS Lambda関数をJava8で記述し、AWS上 Lambda Functionとしてアップロードし、それをJavaクライアントから呼び出す例をステップ・バイ・ステップで紹介します。

### 想定読者
- Javaが書けて、はじめてAWS Lambdaをつかう人
- いままではnode.jsでLambda関数を作っていたが、わけあってJavaでつくってみようとおもう人（＝私のような）

### 記事構成
TL;DR 前編・後編で書きます

- **【前編】 JavaでLambda関数(クラウド側）と、クライアント(ローカル側）をお手軽に作る**←本稿
- 【後編】 Lambda関数（クラウド側）の同期型、非同期型の呼び出しタイプ(Invocation Type)と、Javaクライアント側の同期、非同期呼び出し、API Gatewayでの公開


# AWS Lambda(ラムダ)とは
- 自前でサーバーを作ったり、管理したりする必要が無く、コードをAWS Lambdaにあげるだけで各種リクエスト（イベント）の処理が可能な素敵なサービスです。  

　http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html
　https://aws.amazon.com/jp/lambda/faqs/

- Alexaの[スキル](https://developer.amazon.com/docs/ask-overviews/build-skills-with-the-alexa-skills-kit.html#what-do-i-build)もAWS Lambdaで作成することができます。


# JavaとAWS Lambda
- JavaでAWS Lambdaの処理(Lambda関数）を記述することが可能
- Javaクライアントから、直接Lambda関数を呼び出すことも可能
- Javaに限らないが、API Gatewayというサービスと連携させると、Lambda関数にendpointをつくって公開することができ、Web APIのようにGETやPOSTといったHTTP(S)経由でも呼び出すことが可能

# 目次

以下のステップを１つずつ説明しつつ実施していきます

1. AWS Lambda関数をJavaでコーディングする
2. アップロード用のjarファイルを作る
3. jarファイルをAWS Lambdaに登録して実行可能（呼び出し可能）にする
4. AWSコンソール上でテストする
5. ローカルJavaから、AWS Lambda関数を呼び出しする

# 1.AWS Lambda関数をコーディングする
### Java用ライブラリ読み込み

[aws-lambda-java-core](https://mvnrepository.com/artifact/com.amazonaws/aws-lambda-java-core/1.1.0)ライブラリを追加します。mavenを使う場合は、以下の記述を追加します

```xml:maven
<dependency>
	<groupId>com.amazonaws</groupId>
	<artifactId>aws-lambda-java-core</artifactId>
	<version>1.1.0</version>
</dependency>

```

### Lambda関数の中身をコーディングする

ここでは、関数を呼び出すと結果が返るリクエストレスポンス型(RequestResponse)のLambda関数をつくります。

今回は[aws-lambda-java-core](https://mvnrepository.com/artifact/com.amazonaws/aws-lambda-java-core/1.1.0)ライブラリが用意している**RequestHandler**インタフェースを実装して、POJOを入出力できるLambda関数を実装します。

以下は、姓(lastName)と名(firstName)を入力すると、フルネームを出力してくれるLambda関数のサンプルです。

```java:MyLambda.java
package lambda.cloud;

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

import lambda.cloud.MyLambda.Input;
import lambda.cloud.MyLambda.Output;

public class MyLambda implements RequestHandler<Input, Output> {

  @Override
  public Output handleRequest(Input in, Context context) {

    final Output out = new Output();
    out.in = in;
    out.fullName = in.firstName + "_" + in.lastName;

    return out;
  }

  public static class Input {
    public String firstName;
    public String lastName;
  }

  public static class Output {
    public Input in;
    public String fullName;

  }

}
```

---- 

以下のように、**handleRequest**メソッドを実装するだけです。引数 **Input** はリクエスト、戻り値 **Output** がレスポンスを示します。

```java
 public Output handleRequest(Input in, Context context) {
```



# 2. アップロード用のjarファイルを作る

次にLambda関数としてAWS Lambdaで使えるようにするためにはコードをjarファイル(またはzip)にワンパッケージ化してAWS Lambdaにアップロードする必要があります。

このjarファイルは、いまつくったコードの他、依存しているライブラリなどをひとまとめに統合しておく必要があります。

ひとまとめに統合したjarファイル(つまりfat jar)をつくるためにmaven pom.xmlのplugins以下に**maven-shade-plugin**を追加しておきます。


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

### pom.xml

pom.xml全体は、以下のようになります

```xml:pom.xml
<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/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>lambda.cloud</groupId>
	<artifactId>mylambda</artifactId>
	<version>1.0.0</version>
	<packaging>jar</packaging>
	<name>AWS lambda example</name>
	<description>example of AWS lambda
	</description>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

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

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>

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

		</plugins>
	</build>

</project>
```

### ソースコードダウンロード

上記サンプルのフルソースコードは以下にあります
https://github.com/riversun/aws_lambda_example_basic_client.git

**Eclipseでcloneする場合**
- **File>Import>Git>Projects from Git>Clone URI**
- **https://github.com/riversun/aws_lambda_example_basic_client.git** を指定
- **import as general project**を選択してインポート
- インポート後に、プロジェクト上を右クリックしたメニューで**Configure>Convert to Maven project**を実行

これでEclipse上で**Mavenプロジェクト**となるので、以後取り扱いが楽になります。

### mavenをつかってアップロード用のjarを作成する

- (1)**コマンドラインでjarを作る場合**

pom.xmlのあるディレクトリで以下のコマンドを実行します

```
mvn packgage
```

- (2)**Eclipse上でjarを作る場合**

　Step 1. 右クリックメニューで**Run As>Maven build**を選択する
　<img src="https://riversun.github.io/img/aws_lambda/build_01.png">


　Step 2. **Edit configurations**ダイアログで、**Goals**に **package shade:shade** と入力し**Run**
　<img src="https://riversun.github.io/img/aws_lambda/build_02.png">

(1)(2)いずれの場合でも、**/target**以下にすべての依存コード・ライブラリを含んだ **mylambda-1.0.0.jar**が生成されます。

これがAWS Lambdaアップロード用のjarファイルとなります。

# 3. jarファイルをアップロードしてAWS Lambdaに登録する

AWSコンソールからLambda関数を登録する手順をみていきます

**(1)AWS Lambdaを開く**
Lambdaサービスを開きます。
![img01.png](https://qiita-image-store.s3.amazonaws.com/0/170905/ee0913dd-aa4c-60f3-759c-9022918ecb04.png)

**(2)新しいLambda関数を作る**
まだLambda関数をつくっていない場合は以下のような画面になります。

**関数の作成(Create function)**をクリックします。

![img02.png](https://qiita-image-store.s3.amazonaws.com/0/170905/bc2f0b5e-ad9e-cfa1-0c9a-8fbdf7c8da8e.png)

**(3)Lambda関数を[一から作成]する**

以下のように設計図(blue print)一覧から選択する画面が出ますが、**一から作成(Author from scratch)**を選択します。

![img03.png](https://qiita-image-store.s3.amazonaws.com/0/170905/4d8c8b88-05a9-e735-c236-cf86a48a9e39.png)




**(4)基本的情報画面で名前、ロールを設定する**

ここでは、**myFunction**という名前のLambda関数をつくっていきます

- 名前(Name)は「**myFunction**」
- ロール(Role)は「**テンプレートから新しいロールを作成(Create new role from template)**」
- ロール名(Role name)は「**myRole**」

入力できたら、**関数の作成(Create function)**をクリックします

![img04.png](https://qiita-image-store.s3.amazonaws.com/0/170905/2de4e142-54de-8644-c131-70ae8d14d65c.png)


**(5) jarファイルをアップロードする**

![img06.png](https://qiita-image-store.s3.amazonaws.com/0/170905/0fc45a0b-5f72-e07b-9eed-c6dafdcb07b3.png)

**STEP 1**
まず、画面上部にある、ARNを確認しておきます
画面に表示されている、**arn:aws:lambda:ap-northeast-1:000000000000:function:myFunction**部分をメモしておきます

ご存知*ARN(Amazon Resource Name)はAWS リソースを一意に識別するためのもので、Lambda関数実行時にその特定のために使います。*

**STEP 2**
ランタイム(runtime)から**Java8**を選択する

**STEP 3**
ハンドラ(Handler)に**lambda.cloud.MyLambda**と入力する
ハンドラ名は、**パッケージ名.クラス名::メソッド名** のフォーマットで記述します。
さきほど作ったコードにあわせて、パッケージ名が、**lambda.cloud**、クラス名が**MyLambda**で**lambda.cloud.MyLambda**を入力しています。この例では、メソッド名は省略しても動作します。

**STEP 4**
**アップロード(upload)**をクリックしてさきほど作った**mylambda-1.0.0.jar**をアップロードします。

はい、ここまでで、さきほど自作したLambda関数がAWS Lambdaに登録されました。

# 4. AWSコンソール上でテストする

アップロードが終わったら、コンソール上からテストしてみます。

**(1)テスト用イベントの準備**

Lambda関数は　**何らかのイベントをトリガーにして起動する** という[考え方](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/invoking-lambda-function.html)があり、たとえば、**S3バケットにオブジェクトが作成された**というイベントをトリガーとして、Lambda関数を発火させる、という使い方があります。

そこで、Lambda関数を実行するための入力のことを**イベント**と呼びます。

ここでは、**イベント**をコンソール上から発火させ、Lambda関数の動作を確認します。

画面上部にある、**テストイベントの設定(Configure test events)**を選択します。

![img07.png](https://qiita-image-store.s3.amazonaws.com/0/170905/6c970ccf-46d6-a432-42db-b0d363eb12ed.png)

すると以下のように、**テストイベントの設定(Configure test events)**画面が開きますので、ここでテスト用のイベントを作成します。


![image08b.jpg](https://qiita-image-store.s3.amazonaws.com/0/170905/73aa12a2-5265-3e5a-65c9-3e9b4aadcc19.jpeg)


**イベント名(Event name)**を**MyEvent**として、その下にあるエディットボックスはLambda関数をリクエストするときの本文です。

さきほどつくったPOJOで入出力するLambda関数は、実際の呼び出しではPOJOが自動的にJSONにマップされ、JSONで入出力されます。

そこで以下のようにJSON形式でイベントの本文を入力します

```JSON
{
  "firstName": "john",
  "lastName": "doe"
}
```

入力したら、画面下にある**作成**を押します。


**(2)テスト用イベントの実行**

いま作成した**MyEvent**を早速実行します。画面右上の**テスト**を押します。

![img09.png](https://qiita-image-store.s3.amazonaws.com/0/170905/d0107e38-320e-b874-6d48-ebb202dd3dca.png)

ちなみに、**「ハンドラーで指定されたファイル名がデプロイパッケージのファイル名と一致しないため、Lambda 関数 「myFunction」はインラインで編集できません。」**というメッセージが表示されても気にしなくてOkです。
Lambdaコンソールでは、Java、C# などのコンパイル済み言語のインラインエディタは提供されていません。

**(3)実行結果を確認する**

**テスト**を押してしばらくすると、結果画面が表示されます。

成功のときは、**実行結果: 成功(Execution result:succeeded)**と表示されます。その下の**▼詳細(details)**を押して展開すると結果の詳細を確認できます。

![img10.png](https://qiita-image-store.s3.amazonaws.com/0/170905/68c402c6-301f-f7e0-5b5f-35b39005311b.png)

Lambda関数の出力も、さきほどのPOJO **Output**クラスが以下のようなJSONに変換されます。

```java:入出力用のPOJO
public static class Input {
    public String firstName;
    public String lastName;
  }

  public static class Output {
    public Input in;
    public String fullName;

  }
```

```JSON:実行結果
{
  "in": {
    "firstName": "john",
    "lastName": "doe"
  },
  "fullName": "john_doe"
}
```

# 5. ローカルJavaから、AWS Lambda関数を呼び出す

さきほど作ったLambda関数 **myFunction** をJavaプログラムから呼び出します。

### ライブラリを読み込む

JavaからAWS Lambda関数をたたく場合には、[aws-java-sdk-lambda](https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-lambda)ライブラリを追加します。mavenを使う場合は、以下を追加します

```xml:maven
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-lambda</artifactId>
    <version>1.11.210</version>
</dependency>
```

#### Javaクライアント

コードは以下のようになります。（各種設定値はダミーです）

```java:Javaクライアント
public class ExampleLambdaClient {

  public static void main(String[] args) {
    ExampleLambdaClient client = new ExampleLambdaClient();
    client.invokeLambdaFunction();

  }

  private void invokeLambdaFunction() {

    final String AWS_ACCESS_KEY_ID = "ANMNRR35KPTR7PLB3C7D";
    final String AWS_SECRET_ACCESS_KEY = "UKA6EsKY25LJQBEpUvXyYkj8aWKEDnynEZigVPhz";

    AWSCredentials credentials = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY);

    // ARN
    String functionName = "arn:aws:lambda:ap-northeast-1:000000000000:function:myFunction";

    String inputJSON = "{\"firstName\":\"john\",\"lastName\": \"doe\"}";

    InvokeRequest lmbRequest = new InvokeRequest()
        .withFunctionName(functionName)
        .withPayload(inputJSON);

    lmbRequest.setInvocationType(InvocationType.RequestResponse);

    AWSLambda lambda = AWSLambdaClientBuilder.standard()
        .withRegion(Regions.AP_NORTHEAST_1)
        .withCredentials(new AWSStaticCredentialsProvider(credentials)).build();

    InvokeResult lmbResult = lambda.invoke(lmbRequest);

    String resultJSON = new String(lmbResult.getPayload().array(), Charset.forName("UTF-8"));

    System.out.println(resultJSON);

  }
}
```

----

```java
AWSCredentials credentials = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY);
```

- アクセスキーID(AWS_ACCESS_KEY_ID)とシークレットアクセスキー(AWS_SECRET_ACCESS_KEY)をつかってcredentialsをつくります。

----

```java
InvokeRequest lmbRequest = new InvokeRequest()
        .withFunctionName(functionName)
        .withPayload(inputJSON);
```

- リクエストを生成します。**#withFunctionName**では関数名を指定します。ARN(**arn:aws:lambda:ap-northeast-1:000000000000:function:myFunction**)を指定するか、関数名(**myFunction**)を指定します。ここでは**ARN**を指定しました

- **#withPayload**でリクエストの本文(JSON)を指定します。
ここではJSON文字列 **"{\"firstName\":\"john\",\"lastName\": \"doe\"}";**を指定してます。

----

```java
lmbRequest.setInvocationType(InvocationType.RequestResponse);
```

- **呼び出しタイプ(invocation type)**を指定します
ここでは **InvocationType.RequestResponse** を指定しています。これで**RequestResponse**型の呼び出しタイプになります。RequestResponseにしておくと、処理結果を受け取ることができます。

----

```java
InvokeResult lmbResult = lambda.invoke(lmbRequest);
String resultJSON = new String(lmbResult.getPayload().array(), Charset.forName("UTF-8"));
```

- Lambda関数を呼び出して、結果(JSON)を受け取ります。

----


#### Javaクライアントのソースコード

クライアント側のフルソースコードは以下においてあります
https://github.com/riversun/aws_lambda_example_basic_client.git

# まとめ
- Javaでも比較的簡単にAWS lambdaの関数を記述できました
- Javaのクライアントから直接 Lambda関数を呼び出せました  

 サンプルはEclipse(Oxygen)で作成しましたが、特段プラグインなど必要ありません
 (AWS SDK Plugin for Eclipseも不要。あれば、もっと手軽になりますが）

- 後編では、Lambda関数およびJavaクライアント側の同期/非同期についてや、API Gatewayについて書きます。

# おまけ 
- 2009年頃ですが**コードだけ書いたら後はおまかせ、課金は使った分だけ**というサービスの元祖「**Google App Engine (GAE)**」が登場したときは衝撃をうけました。独特の制約が多かったものの、とてもお世話になりました。

- 今度はその**元祖!?**が、**Google Cloud Functions**を投入しています。今現在はβで実績はこれからだとおもいますが、今後はそちらも選択肢に入ってきそうです。