33
15

More than 5 years have passed since last update.

Scala用DIコンテナ airframeの使い方

Last updated at Posted at 2018-09-08

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できるようにする


  1. AccountControllerでbindを使ってますが、多分これはコンストラクタ・インジェクションにするか、classをtraitに変えないとDIが失敗するかも。 

  2. 厳密には責務が違うので相当するといってはいけない気がするが、Guice脳にもわかりやすく…。 

  3. Sessionをfor式で合成できるようにReader化するものいいですね。type SessionRedader[A] = Reader[Session, A]とか 

33
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
15