Amazon LambdaでScala

AWSのLambdaって、元々は、Node.jsベースの環境で、JavaScriptしか実行できませんでした。

つい先日ですが、Java8も実行できるようになりました。
http://aws.typepad.com/aws_japan/2015/06/aws-lambda-update-run-java-code-in-response-to-events.html

つまりそれって、JVMで実行できるってことですよね？じゃあScalaも動きますよね？
ってことで、やってみた。

コードは [こちら](https://github.com/yoshiyoshifujii/aws-lambda-scala)。

## AWSでJava8を動かす方法

Scala云々の前に、まずはJavaでどーやるのか知っておかないとねってことで、主に、
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-lambda.html
を読みました。
ので、読んでね。

## activator new

まずは、プロジェクトを作らないとねってことで、いつものこれを実行しました。

```bash
$ activator new                                                                                                                                                                                                       [~/workspace]

Fetching the latest list of templates...

Browse the list of templates: http://typesafe.com/activator/templates
Choose from these featured templates or enter a template name:
  1) minimal-akka-java-seed
  2) minimal-akka-scala-seed
  3) minimal-java
  4) minimal-scala
  5) play-java
  6) play-scala
(hit tab to see a list of all templates)
> 4
Enter a name for your application (just press enter for 'minimal-scala')
> aws-lambda-scala
OK, application "aws-lambda-scala" is being created using the "minimal-scala" template.

To run "aws-lambda-scala" from the command line, "cd aws-lambda-scala" then:
/home/vagrant/workspace/aws-lambda-scala/activator run

To run the test for "aws-lambda-scala" from the command line, "cd aws-lambda-scala" then:
/home/vagrant/workspace/aws-lambda-scala/activator test

To run the Activator UI for "aws-lambda-scala" from the command line, "cd aws-lambda-scala" then:
/home/vagrant/workspace/aws-lambda-scala/activator ui
```

これで、 `aws-lambda-scala` ってプロジェクトディレクトリができる。

## activator eclipse with-source=true

次に、いま作ったプロジェクトをEclipseで扱えるようにしたい。

ちょっと前のactivatorならnewした時点で、eclipseプラグインが最初っから読み込まれていたんだけど、いまは自分で仕込む必要があるので、仕込む。

```scala:project/plugins.sbt
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")
```

で、 

```bash
$ ./activator                                                                                                                                                             
...
> eclipse with-source=true
```

みたいなことをすると必要なパッケージをとってきてくれるので、あとはEclipseで、既存プロジェクトをインポートする操作をしてやったらいい。

## AWS SDK

Lambdaで動くよーにするために必要なLibraryを取得してくる。

けど、厳密にいうと、Lambdaで動かすだけなら、[POJO](https://ja.wikipedia.org/wiki/Plain_Old_Java_Object) でもいい。

が、S3のStreamで動かしたいとか、KinesisのStreamで動かしたいとか、そういったときにそれらからデータを取得してくるのに、SDKがあると便利だよねってことで使うことがあるから、一応、取ってくる。

```scala:build.sbt
val Version = "0.1-SNAPSHOT"

lazy val root = (project in file(".")).settings(
  name := "aws-lambda-scala",
  version := Version,
  organization := "com.example",
  scalaVersion := "2.11.7",
  libraryDependencies ++= Seq(
    "com.amazonaws" % "aws-lambda-java-core" % "1.0.0",
    "com.amazonaws" % "aws-lambda-java-events" % "1.1.0",
    "org.specs2" %% "specs2-core" % "3.6" % "test",
    "org.specs2" %% "specs2-mock" % "3.6" % "test",
    "org.specs2" %% "specs2-junit" % "3.6" % "test"
  ),
  assemblyJarName in assembly := "aws-lambda-scala-%s.jar" format(Version)
)

```

なんか色々書いたけど、必要なのは、 `aws-lambda-java-core` と `aws-lambda-java-events` のとこっすね。

## assembly

LambdaにJavaのコードを実行させるには、オールインのJarを作る必要がある。

sbtのプラグインで、それするのに便利なプラグインがあるから、それも取ってくる。

```scala:project/plugins.sbt
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0")
```

さっきのEclipseのしたに `sbt-assembly` ってやつを追記した。

~~ちなみに、 `plugins.sbt` は改行を1つ多めにはさんであげないとコードの区切りを認識しないから一行あけてね。~~
0.13.7以降でなくなったとのこと。コメありがとうございます!!

## Scala

で、やっとこコード。

```scala:src/main/scala/com/example/Hello.scala
package com.example

import scala.collection.JavaConverters._
import com.amazonaws.services.lambda.runtime.Context

trait HelloBase {

  def hello(myCount: Int, context: Context): java.util.List[String] = {
    println("Hello World!!")
    List("%d".format(myCount)).asJava
  }
}

class Hello extends HelloBase
```

こんな感じ。

このコードで大事なのは、

1. helloメソッドの第一引数に、Lambda実行時のインプットを受け取るパラメータを用意する
2. helloメソッドの第二引数に、 `com.amazonaws.services.lambda.runtime.Context` を受け取る
3. 返り値の型を、 `java.util.List` か `java.uti.Map` を指定する

ってとこで、それ以外は、好きにしていいみたい。

ちなみに、POJOでやるなら、

```scala
import scala.collection.JavaConverters._

class Hello {
  def hello(input: Any, context: Any): java.util.List[String] = {
    List("1","2","3").asJava
  }
}
```

って感じでもいけるはず。

## activator assembly

コードも出来たし、Jarファイルを出力します。

```bash
$ ./activator assembly
```

って実行すると、 `target/scala-2.11/aws-lambda-scala-0.1-SNAPSHOT.jar` ってjarが出来ているはず！

## Lambdaにfunctionを作成

コンソールでやってもいいけど、どうせならAWS CLI使いたいよねってことで、以下のコマンドを実行して、Lambda上に新しいFunctionを作る。

```bash
$ aws lambda create-function \
 --region us-east-1 \
 --function-name aws-lambda-scala \
 --zip-file fileb://aws-lambda-scala-0.1-SNAPSHOT.jar \
 --role arn:aws:iam::(AWSアカウントID):role/lambda_basic_execution \
 --handler com.example.Hello::hello \
 --runtime java8 \
 --timeout 15 \
 --memory-size 512
```

これ実行すると、

```bash
{
    "FunctionName": "aws-lambda-scala",
    "CodeSize": 12342769,
    "MemorySize": 512,
    "FunctionArn": "arn:aws:lambda:(リージョン):(AWSアカウントID):function:aws-lambda-scala",
    "Handler": "com.example.Hello::hello",
    "Role": "arn:aws:iam::(AWSアカウントID):role/lambda_basic_execution",
    "Timeout": 15,
    "LastModified": "2015-09-02T10:12:17.691+0000",
    "Runtime": "java8",
    "Description": ""
}
```

って結果が返ってくる。
これで出来た。

## Lambda Invoke!!

じゃ実行してみよう！

```bash
$ aws lambda invoke \
 --region us-east-1 \
 --function-name aws-lambda-scala \
 --payload 123 \
 --invocation-type RequestResponse /tmp/response
```

で、

```bash
{
    "StatusCode": 200
}
```

って返ってきた！
成功したっぽいぞ。

invokeの結果を `/tmp/response` に吐き出したので、そっちを開いて確認してみる。

```/tmp/response
["123"]
```

ってなってる！ `payload` で渡した 123 が `List[String]` になってJson化されて返ってきた！

## まとめ

ScalaのSpecsテストが使えるから、単体テストもやりたい放題だぞ。

以上。