Scala で DI (Effective Scala / Lift のDI編)


こんにちは、馬場です。

前回はScalaのDIのパターンとしては最も有名なCake Patternを紹介しました。今回はDIの他のパターンも紹介したいと思います。

Twitter でのScala DI

さて、少し前にTwitter がScala のベストプラクティスをgithub上に公開しました。DIについては、以下のように言っていますね(かなり意訳です)。

 Scala にmixinの仕組みを導入したのは、伝統的なDIの手法をやめたかったからだと思う。新方式の最たるものが「Cake pattern」だろう。
(略)
 けど、Scalaを利用するだけで、古典的DI手法の問題点はだいたい解決できると感じる。(略)だから、私たちはあまり継承せずに構造化しようと決めた。その方がモジュール性が高くテストしやすいプログラムができると思うから。

「伝統的なDIの手法」というのは、Springと同じようにコンストラクタで部品を設定していく方法です。取り替えたい部品のtraitを作成するところまではCake Pattenと一緒です。

  trait TwitterApi {

     def getProfile(twitterId:String):String

 }

ProfileServiceでTwitterApiを利用したいので、コンストラクタで渡します。

    class ProfileService(twitterApi:TwitterApi) {

          def count(twitterId:String) = twitterApi.getProfile(twitterId).length

    } 

Scalaですので、コンストラクタ引数でFactory メソッドを渡す事もできます。こうすることにより部品の生成/設定が柔軟にできます。

class ProfileService( connectTwitter: String => TwitterApi){
   
     def count(twitterId:String) = connectTwitter(authCode).getProfile(twitterId).length

}

コンテナにあたるobjectは以下のようになるでしょうか。

object ComponentRegistry{

    val twitterApi = new TwitterApiImpl()

    val profileService = new ProfileService(twitterApi)

}

コード中で

 val profileService = ComponentRegistry.profileService

のように、ComponentRegistryからprofileServiceを取得するところもCakePatternと同じです。このパターンを利用すれば、trait祭りが解消されるのでだいぶすっきりしていいですね。

Lift でのDI

CakePattern や Twitter のベストプラクティスでは、どのようにクラスをつくると取り替えやすいか、ということに注目していたと思います。対して、ComponentRegistryを高機能にして、場面ごとに適切なインスタンスを提供してもらうようにしよう、というのが、Liftです。

 Liftには、DI コンテナの基底となるSimpleInjectorというクラスがあります。SimpleInjector内では取り替え可能部品として利用したいクラスを以下のように宣言します。

object ComponentRegistry extends SimpleInjector{

   val twitterApi = new Inject(new TwiterApiImpl){}

}

TwitterApi そのものではなく、TwitterApiImplを引数にとるInjectというクラスを定義します。
TwitterApi を利用するProfileServiceでは以下のようにComponentRegistryからTwitterApiを取得します。

    class ProfileService {

          def count(twitterId:String) = ComponentRegistry.twitterApi.vend.getProfile(twitterId).length // vendでTwitterApiImplを取得する

    } 

さてここからがSimpleInjectorの便利なところなのですが、SimpleInjectorはスコープ内で提供するクラスを変更する機能を持っています。
この機能を利用すると、テストメソッドは以下のようになるでしょう。

class ProfileServiceTest extends UniFunSuite with ShouldMatchers {

   test("プロファイルの文字数を数える"){
          val twitterApi = Mockito.mock(classOf[TwitterApi])
          Mockito.when(twitterApi.getProfile("AyakoBaba")).thenReturn("馬場彩子のプロフィール")

           ComponentRetistry.twitterApi.doWith(twitterApi){
              val result = new ProfileService(twitterApi).getProfile("AyakoBaba")
              result should be (11)
           }
             
    }

}

InjectのdoWithメソッドを利用すれば、関数ブロック内ではComponentRegistry.twitterApi.vendでモックのtwitterApiを取得できます。

このSimpleInjectorは、Lift のWeb アプリケーションを採用しなくても単独で利用可能なクラスですので、興味がある方はぜひ。

最後に

 ここまでScalaでのDIの方法をいろいろ紹介してきましたが、いかがだったでしょうか。個人的には、Twitter方式が一番すっきり、クラスが利用している部品、構成がわかりやすくいいな、と思いましたが、そこは私がOOに慣れているからかもしれません。いずれにしろ、Twitterさんの言うとおり、DIはモジュラリティとテスタビリティの向上のためには導入するべき、と断言します!まだまだ定番の方法がないScalaですが、いろいろ模索していきたいと思います。


This entry was posted in 技術. Bookmark the permalink.