Scala用のDIライブラリAirframeを試してみた

ScalaではJavaと違ってDIコンテナの必要性を感じることがあまりないのですが、フレームワークを作っているとユーザコードにフレームワークが提供するコンポーネントを供給したり、フレームワーク自体を拡張するための拡張ポイントを提供するためにDIコンテナ的なものを使いたいというケースがあったりします。

PlayではGoogle Guiceが導入されていますが、もう少し簡潔でScalaらしいDIコンテナはないものかと思っていたところ@taroleoさんがAirframeというライブラリを開発されていたのを思い出したので試してみました。

github.com

基本的にはフィールドインジェクションぽい感じで、DIするフィールドを以下のように宣言しておきます。

import wvlet.airframe._

class AccountController {
  val accountService = bind[AccountService]
}

そしてDesignというバインディングを定義するオブジェクトを用意します。GuiceModule相当のもののようです。

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の他にもSubCutScaladiMacWireなどがあります。各ライブラリを詳細に比較したわけではないのですが、Airframeはこれらの中でも直感的ながら柔軟性も高く扱いやすいように感じます。なかなか良いフィーリングなのでもう少しいろいろ触ってみたいと思います。