Help us understand the problem. What is going on with this article?

dart2nativeでもAWS LambdaのCustom RuntimeでサーバーレスDartしてみた

どうも前にこんな記事を書いた者です。
ちょっとNimやKotlin/Nativeを相手にしている合間にdart2nativeなるものができてDartからシングルバイナリが作れるようなっていました。
シングルバイナリが作れるようになったのですからcliを作りやすくなりますしサーバーサイドのデプロイも今までよりも楽になりますしこれはエラいことです。
Lambdaのような意地でも必要なものを全部詰め込まなければならない環境ならなおさらです。

ということでこの正月にdart2native対応したので変更点を中心に簡単にまとめ直します。
毎度のことですが最終的な成果物はこちらにおいてあります。

デプロイスクリプト

まずデプロイ用のシェルスクリプトから見てみましょう。
前回のスクリプトがシェルとしてはそれなりに複雑だったのに対してかなり単純な作りだと思います。
やっていることは単純です。
Custom RuntimeではLambdaの起動時に ./bootstrap というファイルが実行されるようになっているのでdart2nativeで同名のシングルバイナリを生成しているだけです。
dockerを使っているのは今の所dart2nativeがクロスコンパイル未対応なのでlinux(すなわちLamdba)で動かせるバイナリを作成するために使っています。
ただライブラリ周りをうまいこと処理できなかったのでビルド前に pub get をコンテナの中で強引にすることで解決しています。
本当はもう少しスマートな解決方法もあるんじゃないかとは思いますがシングルバイナリが来たならどうせクロスコンパイルはいつか解決するんでしょうしここで頑張っても後で意味が無くなりそうだなって思ってしまったのでこれでお許しいただきたい…

deploy.sh
rm -rf ./bootstrap
pub get || exit 1

# TODO: change use cross compile
sudo docker run --rm -v $(pwd):/work -w /work google/dart bash -c "pub get && dart2native ./src/main.dart -o ./bootstrap" &&
sudo chmod 755 ./bootstrap || exit 1

sls deploy -s $stg &&
pub get

ハンドラー

さてでは次はDartのコードの方を見ていきます。
前回は ./src/handler/hoge.dart みたいなhandlerというディレクトリ内にあるファイルをそれぞれコンパイルするという方式をとっていましたが、デプロイスクリプトを見て頂いた通りシングルバイナリを1つだけ出力するように変更しましたのでそれに伴いハンドラーの構造も変更しました。

ではまずdart2nativeで指定している ./src/main.dart とその中で記述されている各ハンドラーの振る舞いを定義する serverless.yml です。

main.dart
import 'dart:convert';
import './runtime.dart';

void main() {
  lambdaHandler("hello", (event) async {
    return {
      'statusCode': 200,
      'body': json.encode({'msg': '新たな光に会いに行こう。'}),
    };
  });

  lambdaHandler("world", (event) async {
    final body = event['body'];
    return {
      'statusCode': 200,
      'body': body,
    };
  });
}
serverless.yml
functions:
  hello:
    handler: hello
    events:
      - http:
          path: hello
          method: get
  world:
    handler: world
    events:
      - http:
          path: world
          method: post

serverless.ymlは前回とほぼ同じです。(ついでにプロキシ統合を使うように変更してたりしますが…)
<url>/hello へgetでリクエストをするとhelloが、<url>/world へpostでリクエストをするとworldが動作します。
一方でハンドラーにはcallbackの他に引数が1つ増えています。
前回は各ハンドラーでそれぞれ別のファイルをコンパイルしていたので不要でしたが今回は同じmain関数内にハンドラーが記述されることになるのでどのハンドラーが動くのか指定できるように増やしました。
これの仕組みは環境変数 _HANDLER にserverless.ymlで指定した文字列が入っているのでそれを利用して判定、違ったら早期returnをするという極めて単純なものです。

runtime.dart
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io' show Platform;

void lambdaHandler(String name, Function callback) async {
  if (name != Platform.environment['_HANDLER']) {
    return;
  }

  final api = Platform.environment['AWS_LAMBDA_RUNTIME_API'];

  while (true) {
    final response =
        await http.get('http://${api}/2018-06-01/runtime/invocation/next');

    final event_data = json.decode(utf8.decode(response.bodyBytes));
    final request_id = response.headers['lambda-runtime-aws-request-id'];

    try {
      final result = await callback(event_data);
      http.post(
          'http://${api}/2018-06-01/runtime/invocation/${request_id}/response',
          body: json.encode(result));
    } catch (e) {
      http.post(
          'http://${api}/2018-06-01/runtime/invocation/${request_id}/error',
          body: json.encode({
            'statusCode': 500,
            'body': json.encode({'msg': 'Internal Lambda Error'}),
          }));
    }
  }
}

デプロイしてみれば前回と同じ結果が帰ってくることが確認できるはずです。

$ ./deploy.sh
(略)
Service Information
service: serverless-dart-sls
stage: dev
region: ap-northeast-1
stack: serverless-dart-sls-dev
resources: 17
api keys:
  None
endpoints:
  GET - https://hoge.execute-api.ap-northeast-1.amazonaws.com/dev/hello
  POST - https://hoge.execute-api.ap-northeast-1.amazonaws.com/dev/world
functions:
  hello: serverless-dart-sls-dev-hello
  world: serverless-dart-sls-dev-world
layers:
  None
Serverless: Removing old service artifacts from S3...
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.
Resolving dependencies...
Got dependencies!

$ curl https://hoge.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"msg":"新たな光に会いに行こう。"}

$ curl -X POST -H "Content-Type: application/json" -d '{"msg": "大石泉すき"}' \
  https://hoge.execute-api.ap-northeast-1.amazonaws.com/dev/world
{"msg": "大石泉すき"}

まとめ

ということで駆け足でしたがdart2nativeでもサーバーレスできました。
dart2native、触ってみた感じやはりクロスコンパイルできないのはちょっとつらいですが悪くはないですね。
GoもそうですがGoogleの作るプログラミング言語って文法は保守的ですけど足回りはめちゃくちゃ良いみたいなところがありますね。
現在1番手のGoはともかくNimやcrystal、Kotlin/NativeみたいなGCありのシングルバイナリ出力言語的には手強いライバルだと思います。
(ところで直近の記事でCustom Runtimeの記事を書く人は脱却したいとか言いつつ結局Custom Runtimeの記事書いてるな…)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした