contentなぜ僕たちはサーバレスでJavaを諦めTypescriptを採用したかJavaNode.jsAWSTypeScriptserverlessこの記事は個人ブログのうち技術に関する箇所のみを抜粋した転載です。sweep-またブログには書いたのですが、諸事情により先に英語版が存在します。こちらも書いたのは僕なので、剽窃などではないことはご了承ください。dev.toはじめにサーバレス(serverless)は昨今もっとも注目を集める設計手法の一つで、おそらく多くの開発者が大なり小なり自分のプロダクトに応用し始めているのではないでしょうか?僕自身、完全にサーバレスに魅せられてしまい、昔ながらの自分でサーバやミドルウェアを管理しながら運用するみたいな世界には戻れる気がしません。そもそもスケーリングや分散可能性をきちんと考えて設計されたアプリケーションであれば、旧来のサーバーアプリケーションの機能から受けられる恩恵も比較的少なくなりますし、サーバレスに切り替えるデメリットはそこまでありません。最近は設計に関して相談された時は、必ずサーバレスの話題を出してみることにしています。サーバレスは既存の開発手法とは大きく異なるため、今持っている知識を刷新し、既存の手法や技術スタックを見直しながら開発していく必要があります。見直しというからには、開発基盤として何の言語を使うかも、当然ながら見直さなくてはいけない技術スタックの対象になります。タイトルにある通り、最終的に僕たちはTypeScriptを採用し、およそ一年半開発・メンテナンスを行ってきました。一年半経った今、あくまで個人的な感想ではありますが、TypeScriptは僕たちが期待した以上に成果を出してくれました。そこでこの記事では、以前使用していた言語にどんな問題があったのか、そしてなぜTypeScriptに切り替えたことでどんな恩恵があったのかをこの記事では解説していきたいと思います。Javaを諦めなくてはならなかったのかさて、なぜTypeScriptを採用したかについて話す前に、まずなぜ以前使用していた非常に強力な言語であるJavaを諦めなくてはいけなかったかについてお話ししたいと思います。先に述べておきますが、僕は結構なJava好きです。なんなら初めて触った言語もJavaでした。JVMに関してもそれなりに勉強して、その神がかったランタイムの仕組みにかなり感銘を受けています。(てか多分作ったやつは神)なので、どこかの大学生のようにJavaがクソだとかレガシーだとか使い物にならんとか、この記事でそういうことを言うつもりは一切ありません。いったコメントもあまり嬉しくないです。サーバレスという仕組みにJavaがあまり合わなかっただけなので。その点だけはご了承いただければ幸いです。Javaを諦めなくてはならなかったのかさて、なぜTypeScriptを採用したかについて話す前に、まずなぜ以前使用していた非常に強力な言語であるJavaを諦めなくてはいけなかったかについてお話ししたいと思います。僕たちのサービスでは、サーバサイドはサービス設立当時から基本的にJavaだけで書かれていました。静的型付け関数型サポート(最近は特に)多様なライブラリ信頼できるコミュニティ(Oracleではなく、開発者の方)などなど挙げればきりがありません。Lambda上でコードを試していて気づいたのですが、Javaはあまりサーバレスに向かないことがわかりました。理由としては以下のことが挙げられます。Linuxのコンテナで動くため、プラットフォームフリーは関係ない。(欠点ではないけど)上述の点は全てなかなかに厄介ですが、今回は特に厄介だった問題についてもう少し書いてみたいと思います。Startのオーバーヘッドです。多くの開発者の方々もこいつに悩まされているのではないかと思います。Lambdaはユーザからのリクエストが来るたびに新しいコンテナを立ち上げます。一度立ち上がってしまえば、しばらくは同じコンテナインスタンスを再利用してくれるのですが、初回起動時にはJavaのランタイムに加え、フレームワークで利用されるDIコンテナやWebコンテナなども全て初期化する必要があります。言えば、一つのコンテナで処理できるのはあくまで一つのリクエストのみで、複数のリクエストを処理することはできません。(内部で数百のリクエストスレッドをプーリングしてたとしても同じです。)つまりどういうことかというと、もし複数のユーザがリクエストを同時に送ってきた場合、Lambdaは起動中のコンテナの他に、別のコンテナを起動しなくてはいけなくなるということです。通常、僕たちはどの時間に具体的に何件のリクエストが同時に来るかを事前に予測することはできません。standbyさせることはできないのです。これは必然的にユーザに数秒から10秒以上の待機時間を強制し、ユーザビリティを著しく下げることにつながります。Startがえげつない事を理解した僕らは、これまでの数年かけて書かれた技術スタックを捨てて、他の言語を選択することを決めました。TypeScriptを選んだのかめちゃくちゃ恥ずかしい話なのですが、正直Lambdaでサポートされている全ての言語をきちんと精査・比較して決めたわけではないのです。正直な話、状況的にTypeScript以外の選択肢はなかったのです。第一に、動的型付け言語は外しました。長期に渡ってスキルのバラバラな開発者によって保守・メンテ。拡張されるコードなので、動的型付けはあまり使いたくありません。したがって、PythonとRubyに関してはかなり序盤で選択肢から外れました。C#とGoに関しても、現在ほとんどのチームがJavaをメインに開発しているサービスなので、既存言語とあまりかけ離れた言語を使うと新規開発者のジョインが難しくなると判断し、今回は見送られました。昨今この二大言語は非常に注目度が高く、特にGolangに関しては徐々にシェアを伸ばしつつあるのは知っています。急いでサーバレスに開発を移す必要があったため、僕たち自身のキャッチアップの時間も考慮し、見送らざるを得なかった感じでした。TypeScriptの利点という事で、僕たちはTypeScriptを使い始めたわけです。TypeScriptの利点を挙げるとしたらこんな感じでしょうか?静的型付け小さいパッケージアーカイブほぼ0秒の起動オーバーヘッドJavaとJavaScriptの知識が再利用できるNodeJSのライブラリやコミュニティが使えるJavaScriptと比べても関数型プログラミングがしやすいClassとInterfaceにより構造化されたコードが描きやすい長期に渡って運用・開発が行われるプロジェクトにおいて静的型付け言語がどれだけ大きな恩恵を与えるかは今更語るまでもありませんので、ここには書きません。ここでは主に、TypeScriptのどういった点がサーバレス開発によく馴染んだかについて書いていきたいと思います。小さいパッケージと小さい起動オーバーヘッドおそらくサーバレスでTypeScriptを使う利点という観点からいうとこれが一番大事だった気がします。(なにせ他のメリットはほぼTypeScript自体のメリットなので・・・)先ほど触れた通り、JavaはJVM本体やフレームワークが利用するDI/Webコンテナなどの起動にかかるオーバヘッドが非常に大きいです。Lambdaで流すには以下の弱点があります。マルチスレッドとそれを取り巻くエコシステムマルチスレッドは非常に強力な機能であり、事実として僕たちはこの機能のおかげで多くのパフォーマンス問題を解決してきました。JVM自体もガーベージコレクションやJITコンパイルにおいて、デフォルトでにマルチスレッドを活用してあの素晴らしいランタイムを実現してます。Compileを参照)しかし、起動時間単体で見ると、アプリケーションに使用する全てのスレッドを立て終わるまでに、100ミリ秒から数秒かかっていることがわかります。この機能自体は旧来のいわゆるクラサバモデルでEC2上で動くアプリケーションならほぼ無視できるオーバーヘッドですが、LambdaなどのFaaS上で動くサーバレスアプリケーションでは決して無視できません。TypeScriptはNodeJSベースであり、基本的にシングルスレッドです。非同期は別スレッドや別プロセスではなくあくまでジョブキュー、イベントループなどで管理されます。したがって、ほとんどのライブラリやフレームワークは起動時にスレッド展開をする必要はありませんし、ランタイムを起動するためのオーバーヘッドもほとんどかかりません。巨大なパッケージアーカイブサーバレスにおいてソースコードのパッケージアーカイブは、基本的に小さいに越したことはありません。Lambdaのコンテナは起動時、AWSにより管理されたソースコード用のS3バケットからソースをダウンロードし、コンテナに展開します。S3からのダウンロード時間は通常非常に短時間ですが、100MBや200MBとなると無視はできません。NodeJSのパッケージは基本的にJavaに比べて小さくなります。正直なんでそうなるかに関しては不勉強でわかっていないのですが、以下の理由が関係してるんじゃないかなと思ったりします。(もしこれやでっていうのをご存知の方はコメントで教えてください)Javaのフレームワークやライブラリは包括的なものも多く、本来使いたい機能に必要ない依存性を引き込んで来るが、JavaScriptは目的特化のライブラリが多く、必要最低限に依存を抑えられることが多い。JavaScript(NodeJS)は1ファイルに複数のmoduleを書くことができ、それでいてメンテもしやすいが、Javaにおけるメンテナンス性の重要なポイントはファイル分割とパッケージ管理のためソースが肥大化しやすい。実際Javaで書いていた時は最大で200MB以上のパッケージができることもあったのですが、NodeJSに変えてからは35MB程度で済んでいます。この巨大なパッケージアーカイブは、僕たちがSpringで書かれた旧来のコードを再利用しようとしたのが大きな原因なのですが、実際これらのいらないフレームワークを除いて最適化したコードでも、どうしても50MBは必要になってしまいました。JavaScriptの知識やエコシステムを利用できる僕たちもWeb開発者のため、基本的にフロントエンドも書きます。したがって、ある程度のJavaScriptやNodeJSに関する知識は蓄えていました。jQuery全盛時代からReact/Vueのようなモダンフレームワークでの開発までを通じて、言語的な特徴はある程度抑えていましたし、どうやって書けばいいコードになるかもある程度理解してるつもりです。TypeScriptはJavaScriptの拡張言語であり、最終的にはJavaScriptにトランスパイルされます。多くの文法やイディオムはJavaScriptから受け継がれているので、実際それほど準備期間を要さずにサービス開発を始められました。加えて、ほとんどのメジャなNodeJSのライブラリはTypeScriptに必要な型定義を提供しているので、NodeJSのエコシステムのメリットをそのまま享受できたのも非常に嬉しいポイントでした。関数型の実装が非常にしやすい昨今の技術トレンドを語る上で、関数型の台頭はなくして語ることはできません。関数型の実装はその性質上、シンプルでテスト可能で危険性の低い安定したコードを書くのに大きく寄与します。Lambdaの場合、常に状態を外部化するコードが求められるため、状態や副作用を隔離する関数型の実装は非常に相性が良く、メンテもしやすくなります。ResigがJavaScriptニンジャの極意で語ったように、JavaScriptはそもそも関数型のプログラミングをある程度サポートしています。JavaScriptにおいて関数は第1級オブジェクトであり、jQueryも実は関数型で書かれることを期待して作られています。一方で、動的型付け言語で関数型のコードを書こうとすると、時折非常にめんどくさい事になることがあります。プリミティブ型だけで表現できる関数は非常に限られますし、返り値や引数にObjectを取るのは普通に結構危険です。TypeScriptでは引数や返り値に型を指定することができます。加えて、以下のTypeScriptの機能は、僕達の書く関数の表現の幅を広げ、より安全でシンプルなコードを書くのに寄与してくれます。共通に使用される型をコンテクストに合わせて型付けできる。Objectで表現されるの引数や返り値をコンテクストにあった型で表現できる。関数のインターフェイスの表現の幅が広がるTypeScriptは他にも関数型で書こうとした時に非常に便利な機能をいろいろ備えていますが、全てをここであげることはしません。(っていうか、結構JavaScript由来のものが多い)関数型とTypeScriptに関する記事は今後どこかで書いていきたいなと思っています。Practiceを再利用できるTypeScriptの文法を学ぶと、かなりJavaやScalaに似通った記述ができることに驚きます。僕たちはそもそも、それなりの期間をJavaで開発してくる中で、Javaにおけるいいコードのお作法をある程度蓄積してきました。APIはどう書くと保守性が上がるかなど、蓄積してきたノウハウはそれなりに捨てがたいものがありました。TypeScriptはインターフェイスやクラスに加えて、アクセスモディファイアやreadonly(Javaでいうfinalのプロパティ)をサポートしており、僕たちは割とさらっとJavaで育んだノウハウをそのまま導入することができました。これにはオブジェクト指向的なベストプラクティスやデザインパターンなども含まれます。(関数指向とオブジェクト指向は二律背反ではないので、プロジェクト内で同時に使用されても問題ないと考えています。)もし僕たちがやや文法が独特なPythonやRubyを採用していたとしたら、より品質の高いコードを書くためのプラクティスをどうこの言語に応用すべきかに多くの時間を費やすこになったことかと思います。(それも楽しいんですよ、知ってます、ただ時間がね。)当然ながら全ての設計やロジックをコピペしたわけではないですし、むしろ大半のコードを書き直ししました。部分をスクラッチで書き直した割に、それなりの品質でそれなりの短期間で書き直しが終わったんだよということは特筆しておくべきかと思います。僕たちもまだまだTypeScriptに関しては初心者といっていいレベルでまだまだ勉強が必要ですが、すでにそのメリットは全力で享受しています。今聞かれれば、Golangもいいなあとか、MicronautとGraalVMとかも面白そうだなあとか、もっと他の選択肢もあったかもなあとか考えたりもするのですが、現状TypeScriptには非常に満足しており、最善の選択肢の一つではないかと信じています。Gatewayのタイムアウトどうハンドルするねんとか、データの一貫性どう担保すんねんとか、サーバレスやTypeScriptに起因する問題にはたくさんぶち当たりました。practiceじゃね?っていう方法もいくつか見つけました。(これはのちのち記事にしていきたい。)もし今Javaでサーバレスに取り組んでいて、サーバレスくそやん、きついやん、やっぱ普通にサーバ欲しいわってなっている方がいたら、ぜひTypeScriptも試してみてください。想像する以上に生産性出るんじゃないかなぁって期待してます。長文おつきあいいただきありがとうございました。何かコメントや訂正があればぜひお願いします。requestStockLike313@MunchkinJava、Scala、NodeJs、Pythonあたりを仕事で使っています。最近はFaaSベースのそこそこ大規模なレビュサービスの設計および実装をしています。認可系の話はそこそこ長いのでちょっと喋れます。設計やテストの話も好きです。upLoginユーザーは見つかりませんでした。