GitBucketに見る長期的なメンテナンスの必要なScalaアプリケーションにおいてScalaのバージョンアップのために留意しておくべきこと

Scalaのバージョンアップはなぜ大変なのか?

GitBucketはもうかれこれ5年近く開発を継続しており、Scalaのメジャーバージョンアップも何度か経験してきました。ScalaのバージョンアップはJavaと比べるとかなり苦労することが多いのですが、それはScalaのバージョンアップに伴うアプリケーションコードの修正よりも、むしろ以下の2点による部分が大きいのではないかと思います。

  1. フレームワークやライブラリの仕様がアグレッシブに変わる
  2. Scalaのメジャーバージョン間でバイナリ互換性が保証されていない

まず前者についてですが、これまでPlay FrameworkやSlickなど標準的なフレームワークやライブラリもかなり活発に仕様変更が行われてきました。フレームワークの開発が活発に行われているという状況はメリットも多い反面、後方互換性のない変更が多いとバージョンアップがつらいというデメリットもあります(とはいえ個人的にはフレームワークの開発が停滞しているよりは活発な方が好ましいと思いますし、最近は主要なフレームワークやライブラリについてはある程度落ち着いてきた感があります)。

後者については、Scalaはメジャーバージョンアップでのバイナリ互換性が保証されていないため、フレームワークやライブラリはScalaのメジャーバージョン毎にビルドする必要があります。このため、まずはすべての依存ライブラリが新しいバージョンのScalaに対応してからでないとScalaのバージョンをあげることができないという問題が発生します。中にはメンテの止まってしまっているライブラリが存在する可能性もありますので、それらへの対処も必要になります。

GitBucketの場合

GitBucketではWebフレームワークとしてScalatra、DBアクセスライブラリにSlickを使っています。他にも様々なライブラリを使用していますが、Javaライブラリが多く、Scalaのバージョンアップには影響していません。つまり、ScalatraとSlickというフレームワークについて上記の2点が主な課題となってきます。

Scalatra

Scalatraについてはすでに枯れているフレームワークであり、GitBucketで使い始めてから大きな仕様変更はありません。開発はさほどアクティブとは言えませんが、自分がコミッタをやっているのでScalaのバージョンアップ対応など必要な作業については自分で行なうことができるという安心感があります。

とはいえ、Scalatra自体様々なサブモジュールがあり、依存関係もそれなりに複雑です。Scalaのバージョンアップの際にはこれらの依存ライブラリにプルリクエストを出して回り、それらがリリースされてからScalatraをバージョンアップするということを行なっており、毎回数ヶ月がかりの作業になります。そのため、最近はメンテナンスコストを減らすため重要度の低いサブモジュールや依存ライブラリをなるべく減らす(小さなものでライセンス上問題なければScalatraのソースツリーに取り込んでしまうということも)という取り組みを行なっています。

Slick

Slickについては元々Typesafe社謹製のライブラリでScala業界ではデファクトスタンダードとして利用されていることもあり、メンテナンスを自分で行わないといけないというような状況には至っていません。しかし、Slick2から3へのバージョンアップでAPIがIOモナドを使った非同期方式に変更され、アプリケーションコードを大幅に書き換える必要がありました。また、当時Slick2はScala 2.12向けにはリリースされていなかったため、Scala 2.12にバージョンアップするにはSlick3に移行する必要がありました(その後有志の手によってSlick2もScala 2.12対応が行われ、Slick 2.1.0のScala 2.12版がリリースされています)。

GitBucketでは本体のコードの修正量もさることながら、IOモナドや非同期処理を導入することでプラグイン開発の難易度が上がってしまうのは避けたい事態でした。さらにGitBucketはWebフレームワークとしてScalatraを使っており、従来のサーブレットによる同期処理が基本となっているためDBアクセスを非同期に行うメリットが薄いという事情もありました。ScalikeJDBCやquillなど別のDBアクセスライブラリへの移行も検討したのですが、様々な事情から最終的にはblocking-slickという、Slick3上でSlick2互換の同期APIを提供するライブラリを自作し、これを用いてSlick3に移行するという選択をしました。

Scalaのバージョンアップのために留意しておくべきこと

冒頭でも書いたように、Scalaのバージョンアップの難しさは、アプリケーション本体のコードよりも、使用しているフレームワークや依存ライブラリによる影響の方が大きいというのがGitBucketの開発を通じての感想です。

Javaの場合、古いライブラリでも基本的に新しいバージョンのJavaでそのまま動作しますが、Scalaライブラリの場合、メンテナンスが止まってしまうとScalaのバージョンアップ時にblockerになってしまうため、長期的にメンテナンスする必要のあるアプリケーションでマイナーなフレームワークやライブラリを選択するのは大きなリスクになる可能性があります。マイナーなものを選択する場合は最悪自分でメンテナンスを行う覚悟をするか、別のものへの移行を常に視野に入れておく必要があります。

また、依存ライブラリについては必要最低限に留め、小さなものであれば敢えて自前で書いてしまうのも1つの手です。同じ機能を提供するJavaライブラリが存在するのであればそちらを利用したり、自前でライブラリを書く場合でもScala固有の機能が不要なものであればJavaで書いたものをScalaから使うのも有効な手段です。実際にGitBucketではMarkdownパーサやマイグレーションツールなどはJavaで書いた独自ライブラリをScalaから使用しています。

とはいえ、Scala界隈も黎明期の混沌とした状況からは脱して標準的なフレームワークやライブラリはだいぶ安定・収束しつつあり、メンテナンス性を考慮した選択も数年前と比べるとかなり容易になっています。Scalaのバージョンアップは新しい機能が利用できるようになったり、コンパイルが速くなったりなど様々なメリットがあるので、特に長期的にメンテナンスを行う必要のあるアプリケーションでは継続的に追従できる体制を整えておきたいところです。