Edited at

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

More than 1 year has passed since last update.


いまからはじめる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のスキルもAWS Lambdaで作成することができます。


JavaとAWS Lambda


  • JavaでAWS Lambdaの処理(Lambda関数)を記述することが可能

  • Javaクライアントから、直接Lambda関数を呼び出すことも可能

  • Javaに限らないが、API Gatewayというサービスと連携させると、Lambda関数にendpointをつくって公開することができ、Web APIのようにGETやPOSTといったHTTP(S)経由でも呼び出すことが可能


目次

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


  1. AWS Lambda関数をJavaでコーディングする

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

  3. jarファイルをAWS Lambdaに登録して実行可能(呼び出し可能)にする

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

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


1.AWS Lambda関数をコーディングする


Java用ライブラリ読み込み

aws-lambda-java-coreライブラリを追加します。mavenを使う場合は、以下の記述を追加します


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ライブラリが用意しているRequestHandlerインタフェースを実装して、POJOを入出力できるLambda関数を実装します。

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


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 がレスポンスを示します。

 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を追加しておきます。

    <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全体は、以下のようになります


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を選択する

 

 Step 2. Edit configurationsダイアログで、Goalspackage shade:shade と入力しRun

 

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

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


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

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

(1)AWS Lambdaを開く

Lambdaサービスを開きます。

(2)新しいLambda関数を作る

まだLambda関数をつくっていない場合は以下のような画面になります。

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

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

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

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

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


  • 名前(Name)は「myFunction

  • ロール(Role)は「テンプレートから新しいロールを作成(Create new role from template)

  • ロール名(Role name)は「myRole

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

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

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、クラス名がMyLambdalambda.cloud.MyLambdaを入力しています。この例では、メソッド名は省略しても動作します。

STEP 4

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

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


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

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

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

Lambda関数は 何らかのイベントをトリガーにして起動する という考え方があり、たとえば、S3バケットにオブジェクトが作成されたというイベントをトリガーとして、Lambda関数を発火させる、という使い方があります。

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

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

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

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

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

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

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

{

"firstName": "john",
"lastName": "doe"
}

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

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

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

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

Lambdaコンソールでは、Java、C# などのコンパイル済み言語のインラインエディタは提供されていません。

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

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

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

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


入出力用のPOJO

public static class Input {

public String firstName;
public String lastName;
}

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

}



実行結果

{

"in": {
"firstName": "john",
"lastName": "doe"
},
"fullName": "john_doe"
}


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

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


ライブラリを読み込む

JavaからAWS Lambda関数をたたく場合には、aws-java-sdk-lambdaライブラリを追加します。mavenを使う場合は、以下を追加します


maven

<dependency>

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


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);

}
}



AWSCredentials credentials = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY);


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


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\"}";を指定してます。



lmbRequest.setInvocationType(InvocationType.RequestResponse);



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


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を投入しています。今現在はβで実績はこれからだとおもいますが、今後はそちらも選択肢に入ってきそうです。