Google App Engine Java 8 から Java11 移行対策

この記事では、Google App Engine for JavaのJava8からJava11への移行方法について考えてみる
## Google App Engineとは
Google App Engine(GAE)は、Google謹製のPaaSサービスであり、Google Cloud Platformの中でも最古参級の古株である。
Go, Python, .NETなど様々な言語に対応しており、もちろんJavaにも対応している(GAE/J)。
## GAE Java11 からサーブレットコンテナではなくなった
2019年10月にこれまでのGAE Java 8版に加えて、GAE Java11版が正式リリースされた[^1] 
だが、Java 11版のGAE/Jはこれまでのバージョンとは大きな違いがある。
[Google App Engine Java 11 ドキュメント - Java 11 ランタイム環境 - フレームワークの互換性](https://cloud.google.com/appengine/docs/standard/java11/runtime?hl=ja#framework_compatibility "Google App Engine Java 11 ドキュメント")にこんなことが書いてある。
> App Engine Java 11 ランタイムでは、実行可能な JAR ファイルをデプロイできます。ランタイムには、ウェブサービス フレームワークは含まれません。つまり、サーブレット ベースのフレームワークやライブラリの使用に制限されません。

*「サーブレット ベースのフレームワークやライブラリの使用に制限されません」* なんて書いてあるが、要するにGAE/Jはサーブレットコンテナではなくなったということだ。
つまり
<dl>
<dt>GAE Java8</dt><dd>WARファイルをデプロイするサーブレットコンテナ</dd>
<dt>GAE Java11</dt><dd>JARファイルをデプロイする単なるJavaコンテナ</dd>
</dl>

ということで、同じJavaのPaaSといっても、全く別物になったのである。
Ubar JAR(Fat JAR)であれば、なんでもデプロイできるようになったので、柔軟性が増したのだが、これまで作ってきたWARファイル形式のアプリケーションは一工夫しないとJava11版のGAEでは使えないことになった。
そこで、**GAE/JでJava11へバージョンアップするための方法論はどのようなものがあるのか検討してみたい。**
## GAE Java8 vs Java11
GAEのJava8とJava11には、その他にもいくつか違いがあるので、比較してみる
ポイントは、Java11になって同じインスタンスクラスでもメモリが倍増している点と、すでにJava8の段階でレガシーに位置付けられていた App Engineにバンドルされていた各種API(com.google.appengine.api 以下のパッケージ)が全廃されたことである。

||Java8|Java11|
|:-:|:-:|:-:|
|コンテナ|Servletコンテナ|JVMコンテナ|
|デプロイするファイル|WARファイル|JAR(Uber Jar)ファイル|
|構成ファイル|XML or YAML|YAMLのみ|
|App Engine バンドル サービス|Cloud Datastore, Memcache などのGAE専用API|廃止<br/>(各サービス提供のAPIを使用)|
|インスタンスサイズ<br/>(Auto Scaling/F1)|メモリ:128MB CPU:600MHz|メモリ:256MB CPU:600MHz

[^1]: [App Engine Java 11 is GA—deploy a JAR, scale it, all fully managed] (https://cloud.google.com/blog/products/application-development/app-engine-java-11-is-ga-deploy-a-jar-scale-it-all-fully-managed "Google Cloud Blog")


#移行戦略の検討
前述のようにGAE/Jは、Java8からJava11の間で互換性を失ったわけだが、それでも移行したい場合はどのようにすればいいのか方法を考えてみる。
なお、具体的な方法は、後日別の記事で紹介したい。
## A. WARファイルを生かして移行
GAE/Jがサーブレットコンテナではなくなったとしても、それでもサーブレットを使い続けたいといった場合はどうすればよいのか考えてみる。
### 自力でJettyを立ち上げる
手っ取り早い方法が、GAE/Jの入門ガイドにも書かれているPure JavaのWeb/APサーバーであるJettyを使って[WAR ファイルを JAR ファイルに再パッケージ化する](https://cloud.google.com/appengine/docs/standard/java11/java-differences#re-packaging_a_war_file_into_a_jar_file)方法だ。
WARファイルを内包したUber Jar(Fat Jar)を作成し、Jettyを起動させWARファイルを読み込ませるという方法だ。
この方法なら、app.yamlを修正するだけで、既存のWARファイルが使用できるようになるはずだ。
（もちろん、廃止になったGAE APIは使用できないので、その部分は修正が必要）

## B. Servletを捨てて移行
サーブレットを捨てる覚悟ができれば、よりモダンなWebフレームワークへ移行する道が開ける。
GAE/Jのリファレンスやチュートリアル、サンプルソースを見る限り、デファクトスタンダードとなりつつある[Spring Boot](https://spring.io/projects/spring-boot)をWebフレームワークとして推しているようなので。Spring Boot への全面移行を考えてみる。
方法論としては、大きく分けて二つある。
### B-1.Web.xmlのみををSpring Bootに書き換える
基本的にサーブレットクラス自体は、Spring Bootでも使用可能だ。
変更ポイントは今までWeb.xml（もしくはWebServletアノテーション）で記述していたサーブレットマッピングで、これをどこかに記述すればよいことになる。
方法はいくつかあるのだが、[Spring Boot Servletマッピング](https://qiita.com/blue_islands/items/75c54e747ed1bbbf71fa)あたりを参照してほしい。
### B-2.全面的にSpring Bootに移行
最後の手段は、いっそ全面的にSpring的なAutowiredしまくったソースに書き換えてしまおうという作戦。
当たり前だが、これが一番手間がかかる。
実際に移行するとどのような感じになるのかは、[Google App Engine Java 8 から Java11への移行 実践編](https://qiita.com/hiroa2/items/9bba2a663deffd5c7ae6)を参照。
## メリット/デメリット
これらの方法論のメリット/デメリットは簡単で、デメリットはA → B-1 → B-2と作業が増え面倒になり、その分、サーブレットというレガシーフレームワークを一掃することができる。
できれば面倒な手は加えたくないが、いつまでもサーブレットを使いつつづけるのも大概なので、どのあたりで折り合いをつけるかという判断である。
しかしながら、どのみち使えなくなったGAEのAPIに対してなんらかの手を入れなくてはならない以上、これを機会に一気にSpring Boot化するというのが王道だと考える。
なお、GAEのAPIの移行は、[App Engine 固有の API からの移行](https://cloud.google.com/appengine/docs/standard/java11/java-differences#migrating_from_the_app_engine-specific_apis) 参照。
#注意事項
筆者は自分のサイト<a href="ter.fun">欧亜大陸鉄道</a>（戦前の世界中の時刻表を利用した、タイムスリップ仮想乗り換え案内サイト）で実際にJava8からJava11への移行を行った。移行した方法は、上記のB-2にあたる、Spring Bootへの全面的な書き換えである。
この作業で一つ大きな問題にぶち当たったので、参考情報として付記しておく。
GAE/Jでは、Java 8からJava 11に移行することで同じインスタンスクラス、すなわち同じ料金で使えるメモリが倍増するのだが、Spring Bootに移行したことで消費メモリは倍以上増加し、結局インスタンスクラスを1段階上げるはめになった。
このあたりの事情は各アプリで異なるため、一概には言えないが、Spring Bootはなかなかメモリを食うフレームワークであることは間違い。