はじめに
仕事で Serverless なアプリケーションを実装することがあり、設計や実装にあたって悩んだことをまとめてみました。
同じようなところで悩んでいる人の助けになるかな、と思い。
LambdaFunction の大きさについて
1つの大きなLambdaFunctionにする(以後 "Monolith" と表記します)のと、小さなLambdaFunctionをStepFunctionsで組わせて実装する(以後 "MicroFunctions" と表記します)やり方の両極端が考えられます。
"MicroFunctions" で実装するメリット
- 関数の影響範囲を分離しやすい
- 失敗した箇所の特定がしやすい
- 処理に失敗したときに、失敗したところだけが再試行されるので再試行の処理時間が平均的に短縮されることが期待できる
- 1つ1つのコードの処理時間が短くなるので、timeout するリスクが減る
"MicroFunctions" で実装するデメリット
- StepFunctions は処理時間でなく状態遷移の回数で課金されるため、分割を小さくすればするほど、コストパフォーマンスが悪くなる。
- 分割数が増えるほど、コールスタックの解析がしにくくなるので、分散トレースのしくみを検討する必要がある
コードがある程度規模が大きくて複雑な場合は、長期的な保守のしやすさを考えて最初から "MicroFunctions" で設計しておくのがおくのが良いと思います。コードの規模が小さい場合や、とりあえず早く作りたい場合は**"Monolith"で実装して、あとから"MicroFunctions"**に移行してもいいと思います。
Test 戦略について
まずは、和田卓人さんのスライドに目を通してください。Lambda Function のテストについて、大切な概念がとてもわかり易くまとまっていて、さらに実践的なコードも載っています。
基本的な戦略は以下です:
- 依存性の注入(dependency injection) によって、ローカルでテスト可能な部分を、そうでない部分と分離する。
-
ローカルでテスト可能な部分を限りなく増やすことで、開発サイクルを高速化する
- 手作業によるテスト: 30分 => ローカルで実行する自動テスト: 5秒
ローカルでテストができるようになることで、 コードを書いて実行結果を得る、というフィードバックサイクルが劇的に早くなります。テストコードが実装されていることで、リファクタリングががんがんできるメリットもあります。
なお、このスライドの内容をもとにサンプルコードを作ってみましたので、こちらも参考にしてみてください。
共通コードの管理について
実をいうと、上で紹介したテスト戦略を採用すると、Lambda関数自体の中身はほぼすっからかんになるので、自然と独自のライブラリが成長していきます。
いまのところ、独自のライブラリを安全にデプロイする場所としては Lambda Layer一択かな、と思います。
開発ツールは何を使えばいいか?
AWS SAM, Serverless, APEX を検討しました。それぞれの特徴は以下です。
APEX
- 3つの中だと最もシンプルなツール。必要最低限なことだけやってくれる
- Role/Lambda Function の作成
- zip化 & アップロード
- apex 自身は周辺リソースの作成はやってくれない
-
apex infra
という terraform の wrapper コマンドが実装されているが、YAMLでリソース定義ができるほかの2つと比べるとインフラの定義のコストがそこそこ大きい
-
- メインのコミッターが最近ほかのプロジェクトをメインに開発しているせいか、コミュニティの開発スピードが緩やかになっているように感じる。
Serverless
- Lambda関数のデプロイに加えて、周辺リソースの作成もやってくれる
- 企業でコミットしているので割と開発スピードが早い
AWS SAM CLI
- Lambda関数のデプロイに加えて、周辺リソースの作成もやってくれる
- デプロイのフローが3つの中では最も堅牢性が高い
- Lambda Function のビルドを docker container 上で行うオプションがある
- デプロイ用の資材をS3に配置する(package) ことができる
- Amazon 純正のツールなので、AWS側の変更に対するキャッチアップが早いことが期待できる
- Policy Template など、リソース定義を簡単にするための便利な機能がある
ぶっちゃけ好みの問題だと思いますが、これから開発ツールを選択する場合は Serverless か AWS SAM CLI が良いと個人的には思います。