Playに依存しないコードを書く

フレームワークの便利さとコードの柔軟性とのバランスの話。

PlayにはいわゆるMVC的な機能の他にいろいろと便利っぽいモジュールが付いていますが、 実際そいつらを使っていると依存関係や柔軟性の面でちょくちょく困ることがあります。 最近何度か困ったのはプロジェクトを分割する場面です。

Playでプロジェクトをスタートするときはwebアプリケーションのプロジェクトとしてスタートするわけで、 シンプルに次のような構成をとります。

.
├── app
├── build.sbt
├── conf
├── public
├── test
└── test

しかし、だんだんプロジェクトが進むにつれて、あ、webアプリだけじゃなくてバックエンドでバッチ動かす必要あるわ、となったりします。バッチからも今までに書いたコード使いたいな。じゃあ共通コードをcoreっていうサブプロジェクトに移そうか。coreはplayには依存させたくないなあ。

こんな構成にしたい。

.
├── batch
│   └── src
├── build.sbt
├── core
│   └── src
└── web
    ├── app
    ├── conf
    ├── public
    └── test

でもこれが意外にめんどうだったりします。

今まで書いたコードがPlayに依存していると、そのコードはbatchでは使うことができません。 たとえばPlayにはwsというHTTPクライアントライブラリが付属していますが、wsはplay.api.Applicationに依存しています。つまりPlayアプリケーションが起動している状態でなければ使えません。 従って、batchでもHTTPクライアントを使いたい場合はwsではなく他のHTTPクライアントへの置き換え作業が発生します。最初っからws使わなければよかったね...

wsのほかにも

あたりは動作するためにplay.api.Applicationが必要なのでめんどうです。

(追記・捕捉)
Play2.4からはDI前提のAPIにApplicationに依存しないものもあります。
ただしモジュールレベルでは依存関係があるので、結果的にはcoreをPlayに依存させることになります。
互換性のために以前のAPIを残しているからと思われるので今後改善される可能性はありそうです。

あと、configなども

// play.api.Play.currentはplay.api.Applicationのインスタンス
play.api.Play.current.configuration.get.....

というようにplay.api.Applicationをいじるコードを書いてしまいがちなので注意が必要です。 (それ以前にテストしにくいからやめたほうが良いですね)

設定ファイルを読み込むところは局所化して、必要な設定はコンストラクタ経由で渡すとかそういう手間のかけ方をするのが良いでしょう。

まとめ

  • コードの再利用を考えるとPlayに依存したコードをかけない場面は多い
  • ws, cache, dbはPlayにべったり依存している上、それを使ったコードは再利用したい場面が多いのでほいほい使わない
  • play.api.Application, play.api.play.current をあちこちで使わない