ScalaではJavaと違ってDIコンテナの必要性を感じることがあまりないのですが、フレームワークを作っているとユーザコードにフレームワークが提供するコンポーネントを供給したり、フレームワーク自体を拡張するための拡張ポイントを提供するためにDIコンテナ的なものを使いたいというケースがあったりします。
PlayではGoogle Guiceが導入されていますが、もう少し簡潔でScalaらしいDIコンテナはないものかと思っていたところ@taroleoさんがAirframeというライブラリを開発されていたのを思い出したので試してみました。
基本的にはフィールドインジェクションぽい感じで、DIするフィールドを以下のように宣言しておきます。
import wvlet.airframe._ class AccountController { val accountService = bind[AccountService] }
そしてDesign
というバインディングを定義するオブジェクトを用意します。GuiceのModule
相当のもののようです。
val design = newDesign .bind[AccountController].toInstance(new AccountControllerImpl())
以下のようにしてDI済みのインスタンスを取得することができます。
val session = design.newSession val controller = session.build[AccountController]
上記の例ではバインドするインスタンスをtoInstance
で指定しましたが、他にもtoSingleton
でシングルトンにしたり、to[T]
でコンクリートクラスの型を指定してバインドしたりといろいろできます。
同じ型のコンポーネントを複数使い分ける場合、Guiceなどでは名前を指定してDIしますが、Airframeではタグを使って区別することができます。READMEの例に少し手を加えたものですが、以下のような感じです。
import wvlet.obj.tag.@@ case class Fruit(name: String) trait Apple trait Banana trait TaggedBinding { val apple = bind[Fruit @@ Apple] val banana = bind[Fruit @@ Banana] } val design = newDesign .bind[Fruit @@ Apple].toInstance(Fruit("Apple")) .bind[Fruit @@ Banana].toInstance(Fruit("Banana"))
Scala用のDIライブラリにはAirframeの他にもSubCut、Scaladi、MacWireなどがあります。各ライブラリを詳細に比較したわけではないのですが、Airframeはこれらの中でも直感的ながら柔軟性も高く扱いやすいように感じます。なかなか良いフィーリングなのでもう少しいろいろ触ってみたいと思います。