ScalaでDI (Cake Pattern 導入編)

こんにちは、馬場です。

唐突ですが、現在Play! + Scalaで開発しています。
それまではほとんどJava (たまにRuby/Rails)で作っていたので、本格的にサービスをScalaで作るのは初めて。
慣れないながらもすごく楽しくやっていましたが、プロジェクト開始早々でてきた不満。それは

「DIしたい…」

なぜなら「テストが面倒だから」。
データベースアクセス、webapiの呼び出し、メール送信と外部リソースへアクセスする処理のオンパレード。テストのたびに実行していたらスローテストに陥ることは目に見えています。リファクタリング/パフォーマンスチューニング/ライブラリのアップデートに耐えるためにも、ここはぜひともDIを導入しておきたい。

というわけで、Scala アプリケーションへのDIの導入体験を紹介したいと思います。

Java で DI

まずはJavaのDIコンテナSpringの実装例をみていきましょう。
例えば、Web APIを呼び出すようなプログラムでは、実際にWeb APIを呼び出す処理を切り出し、インスタンス生成時に設定するようにします。

TwitterApi は「取り替え可能」になるようにインタフェースにします。

本番で利用する、実際にWeb APIの呼び出しを行うクラスを作成します。このクラスはインタフェースを実装します。

TwitterApiImplを生成したり、ProfileServiceにそれをsetするのはDIコンテナのSpringの役割です。
以下のように、どのようなインスタンスをProfileServiceに設定するのか、XMLに記述します。

コード中では、

のようにProfileServiceを取得します。こうしておけばWeb APIの認証などの情報が変わった場合でも、ProfileServiceのコードを変更せずに、
設定ファイルだけを編集すれば、変更されたWeb API呼び出しクラスをProfileServiceで利用する事ができます。

さらにUnitTest では、簡単にtwitterApiをモックに差し替えることができます。

Mockitoはモックを作成するためのJava ライブラリです。

Web APIを呼び出す処理は設定などが複雑になりますし、公開サービスなら特に実行のタイミングごとに結果が異なったりしがちです。
ここで確認したいのは「APIから取得した文字の数を返しているか」というところで、API呼び出しの部分ではありません。
テストしたい処理にフォーカスすれば、API呼び出しはモックで十分。また実際に外部のWeb APIを呼び出すのとは違い、テストも瞬殺で終わります。

Scala で DI - CakePattern

Javaの開発で何度も助けられたDI。どうにかこのプロジェクトにもDIを導入したいと考えましたが、DI嫌いのPlay! frameworkにはそのような仕組みはありません。Scala といえどもJVM言語なので、Spring / Guice などのJava のDIフレームワークも利用できます。ですが、ここはせっかく(?)なのでScalaらしい方法で実装したい。ということで、バクの本にもちらっと書いてある「Cake Pattern」をやってみることにしました。
Cake Pattern は、Scala のデザインパターンで、構造がケーキのように水平に何段にも重ねたようにも、垂直にきりだしたようにもみえるのでそのように名付けられたのですが、自分でいっていてもよくわからないので、とにかくコードをみてみましょう!

まず、場面ごと(本番とテスト)で切り替えたいWeb APIの呼び出し処理は、traitで宣言します。

その後すぽっとComponent traitで取り囲みます。TwitterApiはTwitterApiComponentが提供します。

次にProfileServiceを作成します。ProfileServiceはTwitterApiを利用したいので、このクラスをすぽっとComponent traitで取り囲み、さらにTwitterApiComponent を自己参照します。これにより内部のProfileServiceクラスでは、TwitterApiが提供するtwitterApiが利用できます。

本番で利用する、実際にWeb APIの呼び出しを行うクラスを作成します。TwitterApiを継承するTwitterApiImplクラスを作成し、これもTwitterApiComponentを継承したComponent traitですぽっと囲みます。

さて、最後にコンテナにあたるobjectを作成します(objectにするのはsingletonにしたいから)。このComponentRegistryは、ProfileServiceComponent および TwitterApiComponentImpl を継承しています。

コード中では、

のように、ComponentRegistryからprofileServiceを取得します。profileService内部で利用するtwitterApiはComponentRegistry で生成しているTwitterApiImplになります。
テストでは以下のようにテストクラスでProfileServiceComponentとTwitterServiceComponentをwithします。twitterApiをモックに置き換えてテストを実施することもできます。

Cake Pattern ってどうなんだろう。

なんとかCake PatternでDIを実装することができました。DIがなかったころよりテストが早いので、私比3倍快適な開発ライフ。Play! Frameworkの人とは意見が違いますが、ある程度の規模と複雑さがあるアプリケーションの開発にはDIはなくてはならないもの、という感じました。

それでは、Cake Patternはどうだったんだろう、と振り返ると…
DIコンテナの設定が、SpringのようにXMLではなく通常のScalaのプログラムである、という点はよかったです。設定にミスがあればコンパイル時に発見できますし、ただのプログラムなのでインスタンスの生成方法を柔軟に記述することができます。Springは高機能で自動バインディングなどもあり楽なのですが、テンプレートと少しちがったことをやろうとするとどうしたらよいかわからない複雑さがありますし、XMLの設定ファイルを編集するのも気が重いです。maven と sbt の印象の違いに似ている、というか。
ただ当然、いくつか運用上気になることはあります。まず、開発が進むにつれ関係しあうクラスがどんどん増えていき、気づけばComponentRegistry で Component trait の with が祭りになってしまったこと。何段ケーキなんだよ、という感じです。途中で、ComponentRegistry自体をいくつかのtraitに分割しましたが、規模がもう少し大きくなると改めて課題になるかもしれません。もうひとつは、ちょっとわかりにくいというところです。この開発プロジェクトでは、途中から人がちょっとずつ増えていったのですが、Cake Patternは開発でつまづく箇所堂々第2位だったと思います(1位はまた機会があればお話します) 。一見して、Component traitですぼっと取り囲む構造にしている意図がわからないんですよね。これは人が増えるごとにCake Patternについて改めて説明しなかったからかもしれませんが。次来た人にはこのBlogを読んでもらうことにします。

次回予告

今回はCake Patternについてみてきましたが、ScalaのDIのパターンは他にもまだ(少しだけ)あります。
次回は、TwitterがEffective Scalaで紹介しているパターン、および Lift フレームワークでのDIの実装についてみていきたいと思います。

Comments are closed, but you can leave a trackback: Trackback URL.