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も動きますよね?
ってことで、やってみた。
コードは こちら。
AWSでJava8を動かす方法
Scala云々の前に、まずはJavaでどーやるのか知っておかないとねってことで、主に、
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-lambda.html
を読みました。
ので、読んでね。
activator new
まずは、プロジェクトを作らないとねってことで、いつものこれを実行しました。
$ 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プラグインが最初っから読み込まれていたんだけど、いまは自分で仕込む必要があるので、仕込む。
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")
で、
$ ./activator
...
> eclipse with-source=true
みたいなことをすると必要なパッケージをとってきてくれるので、あとはEclipseで、既存プロジェクトをインポートする操作をしてやったらいい。
AWS SDK
Lambdaで動くよーにするために必要なLibraryを取得してくる。
けど、厳密にいうと、Lambdaで動かすだけなら、POJO でもいい。
が、S3のStreamで動かしたいとか、KinesisのStreamで動かしたいとか、そういったときにそれらからデータを取得してくるのに、SDKがあると便利だよねってことで使うことがあるから、一応、取ってくる。
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のプラグインで、それするのに便利なプラグインがあるから、それも取ってくる。
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
で、やっとこコード。
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
こんな感じ。
このコードで大事なのは、
- helloメソッドの第一引数に、Lambda実行時のインプットを受け取るパラメータを用意する
- helloメソッドの第二引数に、
com.amazonaws.services.lambda.runtime.Context
を受け取る - 返り値の型を、
java.util.List
かjava.uti.Map
を指定する
ってとこで、それ以外は、好きにしていいみたい。
ちなみに、POJOでやるなら、
import scala.collection.JavaConverters._
class Hello {
def hello(input: Any, context: Any): java.util.List[String] = {
List("1","2","3").asJava
}
}
って感じでもいけるはず。
activator assembly
コードも出来たし、Jarファイルを出力します。
$ ./activator assembly
って実行すると、 target/scala-2.11/aws-lambda-scala-0.1-SNAPSHOT.jar
ってjarが出来ているはず!
Lambdaにfunctionを作成
コンソールでやってもいいけど、どうせならAWS CLI使いたいよねってことで、以下のコマンドを実行して、Lambda上に新しいFunctionを作る。
$ 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
これ実行すると、
{
"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!!
じゃ実行してみよう!
$ aws lambda invoke \
--region us-east-1 \
--function-name aws-lambda-scala \
--payload 123 \
--invocation-type RequestResponse /tmp/response
で、
{
"StatusCode": 200
}
って返ってきた!
成功したっぽいぞ。
invokeの結果を /tmp/response
に吐き出したので、そっちを開いて確認してみる。
["123"]
ってなってる! payload
で渡した 123 が List[String]
になってJson化されて返ってきた!
まとめ
ScalaのSpecsテストが使えるから、単体テストもやりたい放題だぞ。
以上。