ScalaでDIコンテナというとGoogle Guiceを使っている人が多いと思うけど、僕は最近airframeを使い始めました。
何がどう違うのというのは以下の記事を読んで欲しい。個人的には、Scalaで使うための設計が前提になっているかが大きいと思っています。Guiceでもいいけど、無邪気に使っているとJava前提のライブラリはハマることもあるし、Scalaの独自機能に対するケアもないので…。
使い方はQuick Startを読んでください。たけぞえさんのブログ記事 「Scala用のDIライブラリAirframeを試してみた」 でも紹介されています。1
以上、終了でもよかったのですが、日本語で簡単に解説しておきます。
最低限知っておくとよいのは、Design, Sessionです。あとは、DIコンテナがサポートするLife Cycleはドキュメントをみてください。DesignとSessionだけ、公式ドキュメントのコードを例に説明します。
コンストラクタ・インジェクション
インジェクションのパターンには、以下の二つがあります。
- コンストラクタ・インジェクション
- イン・トレイト・インジェクション
以下はコンストラクタ・インジェクションの例です。アノテーションは不要です。
import wvlet.airframe._
class MyApp(val config:AppConfig)
case class AppConfig(appName:String)
イン・トレイト・インジェクションは以下のようにtraitのフィールドに対するインジェクションですが、具体的には最後に紹介します。
class Database(name:String, conn:Connection)
trait DatabaseService {
val db = bind[Database]
}
Design
GuiceのAbstractModule#configure内で記述するバインディングはDesignを使うとできます。newDesignの後に続けてbindとかtoInstanceとか、GuiceのDSLと似ています。戻り値もDesignになるので、状況に応じてDI設定を追加したり削除したりできます。
Design#bind[A].
の後続で使えるメソッドはGuice同様様々あります。詳しくはドキュメントを参照してください。
// Define a design
val d: Dession = newDesign
.bind[AppConfig].toInstance(AppConfig("Hello Airframe!"))
Session
GuiceのInjectorに相当するのがSessionです2。Sessionを使うには以下が必要です。
- Sessionがなければ Design#newSessionで生成する
- Sessionを開始するために、Session#startを呼ぶ
- Sessionを停止するために、Session#shutdownを呼ぶ
つまり、Sessionの生成・開始・停止が必要になります。当然、これらのマニュアル操作を行ってもよいですが、以下の便利な高階メソッドで自動化できます。3
Design#build[A](body: A => Any): Any
Design#withSession[U](body: Session => U): U
// Create MyApp. AppConfig in the design will be used
d.build[MyApp]{ app => // new MyApp(AppConfig("Hello Airframe!"))
// Do something with app
// e.g.) println(app.config.appName)
}
// Session will be closed here
val appName = d.withSession{ session =>
session.build[MyApp].config.appName
}
イン・トレイト・インジェクション
最後にイン・トレイト・インジェクションを説明します。traitが持つフィールドを初期化する際にbindを使うだけです。クラスでは使えないので注意。
import wvlet.airframe._
class Database(name:String, conn:Connection)
trait DatabaseService {
val db = bind[Database]
}
val d = newDesign
.bind[Connection].to[ConnectionImpl]
d.withSession { session =>
// Creates a new DatabaseService with ConnectionImpl
val service = session.build[DatabaseService]
}
// [DON'T DO THIS] You cannot use bind[X] inside classes:
class A {
val a = bind[B] // [Error] Because class A can't find the current session
}
まとめ
airframeは、Guiceの知識があれば簡単に始められて、Guice以上にシンプルにコードが書けます。ここでは紹介しなかったのですが、タイプエリアスやジェネリック(高階型も対応)のサポートもあります。お勧めです。
次は、airframeを使った、高階型を持つ型のDIについて書きます。
追記: 書きました → 高階型の型パラメータを持つDDDリポジトリをDIできるようにする