私たちはこれまでjavaでWebアプリケーションを作ってきました。成果物は概ねwarファイルでした。
AWS Lambdaの世界に触れて、マイクロサービスだなんだと急に頭を切り替えるのは中々大変です。
モノシリックなサービスは、デプロイ単位が分かりやすく全体としてのバージョン管理が容易です。
そしてロジックの共有や共通コードの統合も容易です。
マイクロサービスというバズワードに唆されて1functionに1つの機能しか持たせないのは勿体ないと思うのです。
なので、AWS Lambda上でもこれまでに近い形のモノシリックなサービスを構築できるよう考えてみましょう。 なお、今回のエントリの使用言語はKotlinです。
ルータ
AWS Lambdaのエントリポイントは、コンソールで指定したクラスのメソッドです。 このエントリポイントをルータとし、必要な機能を呼び出すようにしてみます。
class Application: RequestStreamHandler {
val charset = Charsets.UTF_8
val router = Router()
override fun handleRequest(input: InputStream, output: OutputStream, context: Context) {
var gson = Gson()
var json = gson.fromJson(input.reader(charset), Map::class.java)
var response = router.invoke(context, json)
var ret = gson.toJson(response)
output.writer(charset).apply {
write(ret)
flush()
close()
}
}
}
class Router() {
fun invoke(context: Context, json: Map<*,*>): Map<*,*> {
var action = json["action"] as String?
return when(action){
"echo" -> {
mapOf( "data" to json["data"] )
}
else -> makeResult(HttpStatus.SC_BAD_REQUEST, "bad request")
}
}
fun makeResult(code: Int): Map<*,*> {
return mapOf("error" to code)
}
fun makeResult(code: Int, message: String): Map<*,*>{
return mapOf("error" to code, "message" to message)
}
}
入力が
{
"action":"echo",
"data":"hoge"
}
だとすると、出力は
{
"data": "hoge"
}
となります。
ハンドラではただRouterに入力jsonを渡すだけ。 そしてRouterはjson内のactionの内容を見て適切に処置して結果を返すだけ。 Routerが処理用のクラスへふり分けるので これで、今まで通りにWEBアプリケーションが作れます。 JSON-RPC的なインターフェースにすればより汎用的(?)かもしれませんね。
プレウォーミング
AWS LambdaでJVM言語を使用する場合、初回のロード時間が気になるところです。
そして、モノシリックなLambda Functionを作るということはその分サービスに必要なクラスが増え、
よりロード時間がかかるということです。
気になる場合は、プレウォーミング をしましょう。
どうやら公開したLambda関数は、(アクセスに応じてだとは思いますが)呼び出し時に2つのインスタンスにデプロイされるようです。
適当な周期で該当の関数へアクセスすることでそれらのインスタンスを延命し、初回ロードが発生しないようにするなどの工夫をすると良いかもしれません。
最後に
なお、この記事は実用的な目的では書かれていません。 実用的なモノシリックWebアプリケーションを作りたいのであれば、Beanstalkとか使って普通にEC2にデプロイした方がいいんじゃないですかね。(なげやり)