DIについてあれこれ

Dependency Injectionとはコンポーネント間の依存関係をプログラムのソースコードから排除し、外部の設定ファイルなどで注入できるようにするソフトウェアパターンである

ってwikipedia先生が言ってました。

Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜 - Qiita を読んでいろいろ考えたので、なんで今さらって感じのことを書きます。

ScalaでDIというとDIコンテナとかCake PatternとかReader Monadとかって話になっちゃうんですが、これらはいかにかっこよくDIするかの話であって、別にこういった道具やパターンを使わなくてもDIは可能という話です。

Constructor Injection

簡単な例で考えます。今ここにUserRepositoryにべったり依存しているUserServiceがあります。

class UserRepository {
  def findAll = ???
}

class UserService {
  val userRepository = new UserRepository
  def getAllUser = userRepository.findAll
}

UserServiceの中でUserRepositoryをnewしているので良くない。UserServiceのテストするのにUserRepositoryのこと考えてテストしなきゃいけなくてめんどくさい。なので

class UserRepository {
  def findAll = ???
}

class UserService(userRepository: UserRepository) {
  def getAllUser = userRepository.findAll
}

というように、UserRepositoryを外から与えるようにしよう。こうすればUserRepositoryの代わりにMockUserRepositoryを使ったりして楽にテストができますね。ちなみにこれはconstructorから注入してるからconstructor injection。

ところでUserRepositoryをnewしてUserServiceに与えるのって誰がやるんだって話になりますが、手動でやるんですね。だからこれは手動DIと呼ぶことにしましょう。

DIを手動じゃなくて自動でやりたくなったらDIコンテナを使い始めます。DIコンテナの何が便利かっていうと手動でやっていたUserRepositoryをnewしてUserServiceにつっこむというのを勝手にやってくれるということです。かっこよく言うとオブジェクトのライフサイクル管理を任せられる、といったところでしょうか。これはこれでとても便利です。

ところでScalaではCake PatternとかでDIできるからDIコンテナなんて不要、と言う煽りをよく見るんですが、おかしいですよね。JavaだろうとPHPだろうとDIコンテナがなくてもDIできるし、DIコンテナがやってくれる仕事はCake Patternではやってくれません。

PlayでのDI

PlayではDIコンテナとしてGuiceを使っていますが、DIコンテナありきで話が進んだわけではありません。constructor injectionベースで疎結合にやっていこうぜという話が出発点でした。PlayはGuiceを使ってる、っていうのは正しいけれど、気持ちはconstructor injectionなのです。なんかそういう話を前に書いてました。

tototoshi.hatenablog.com

Minimal Cake

で、Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜 - QiitaのMinimal Cakeの話です。

Minimal CakeはCake Patternに制約を設けたデザインパターンとして紹介されていますが、なんかピンと来ませんでした。

でも作者の方のコメントはピンと来ました。

Scala - Minimal Cake Pattern のお作法 - Qiita

しばらくの間は Minimal Cake Pattern のメリットが明らかでなかったので、コンストラクタによる注入パターンも書かれ続けていました。しかし依存先モジュールが多いクラスが生まれてくると、だんだん Minimal Cake Pattern の便利さが分かってきました。コンストラクタでは引数の順番を気にしなければいけないのに、Minimal Cake Pattern だと MixIn の継承順序はどうでもいいのです。依存先クラスが10個以上あるクラスに対して正しい順番でコンストラクタ引数を与えるのは苦行でしかありませんでした 。

Scala界隈の一連のワイワイガヤガヤは忘れましょう。ただInjectionの仕方を工夫したと考えたほうがスムーズに頭に入る気がします。

以上とりとめのない話でした

scala用のfixtureライブラリ作った

昔作ったscalikejdbcのfixtureプラグイン、これ別にPlayのプラグインにすることはなかったなーとちょっと思ってたんですが、最近実際困りはじめたので、Playに依存しないfixtureライブラリが欲しくなったので作りました。

https://github.com/tototoshi/scala-fixture

依存しているライブラリはないので、SlickのプロジェクトでもScalikejdbcのプロジェクトでも使えます。

インストールは

libraryDependencies += "com.github.tototoshi" %% "scala-fixture" % "0.1.0"

使い方はscalikejdbc-fixtureとだいたい同じ。Ups, Downs と書いてたのが、SetUp,TearDownで動くようにしてます。(念のため?Ups, Downsでも動くようにしてます)

#!SetUp
INSERT INTO users(id, name) VALUES (1, 'user1');
#!TearDown
DELETE FROM users WHERE id = 1;

あと、1000件くらいINSERTしてやるぜ!ランダムなデータ突っ込むぜ!みたいなときのためにScalaのコードでもかけるようにしました。

import java.sql.Connection
import com.github.tototoshi.fixture.FixtureScript

class MyFixtureScript extends FixtureScript {

  override def setUp(connection: Connection): Unit = {
    connection.prepareStatement("insert into users(id, name) values (3, 'user3')").execute()
  }

  override def tearDown(connection: Connection): Unit = {
    connection.prepareStatement("delete from users where id = 3").execute()
  }

}

テストコードはこんなかんじになります。 beforeでsetUp, afterでtearDownすると良いでしょう。 詳しくはREADMEを読んでください。

import org.scalatest.{ BeforeAndAfter, FunSuite }
import com.github.tototoshi.fixture._

class FixtureTest extends FunSuite with BeforeAndAfter {

  val driver = "org.h2.Driver"
  val url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"
  val username = "sa"
  val password = ""

  val fixture = Fixture(driver, url, username, password)
    .scriptLocation("db/fixtures/default")
    .scriptPackage("com.example.fixtures")
    .scripts(Seq("script1.sql", "script2.sql", "MyFixtureScript"))

  before {
    fixture.setUp()
  }

  after {
    fixture.tearDown()
  }

  test("load fixtures") {
    // write tests
  }

}

Playに依存しないコードを書く

フレームワークの便利さとコードの柔軟性とのバランスの話。

PlayにはいわゆるMVC的な機能の他にいろいろと便利っぽいモジュールが付いていますが、 実際そいつらを使っていると依存関係や柔軟性の面でちょくちょく困ることがあります。 最近何度か困ったのはプロジェクトを分割する場面です。

Playでプロジェクトをスタートするときはwebアプリケーションのプロジェクトとしてスタートするわけで、 シンプルに次のような構成をとります。

.
├── app
├── build.sbt
├── conf
├── public
├── test
└── test

しかし、だんだんプロジェクトが進むにつれて、あ、webアプリだけじゃなくてバックエンドでバッチ動かす必要あるわ、となったりします。バッチからも今までに書いたコード使いたいな。じゃあ共通コードをcoreっていうサブプロジェクトに移そうか。coreはplayには依存させたくないなあ。

こんな構成にしたい。

.
├── batch
│   └── src
├── build.sbt
├── core
│   └── src
└── web
    ├── app
    ├── conf
    ├── public
    └── test

でもこれが意外にめんどうだったりします。

今まで書いたコードがPlayに依存していると、そのコードはbatchでは使うことができません。 たとえばPlayにはwsというHTTPクライアントライブラリが付属していますが、wsはplay.api.Applicationに依存しています。つまりPlayアプリケーションが起動している状態でなければ使えません。 従って、batchでもHTTPクライアントを使いたい場合はwsではなく他のHTTPクライアントへの置き換え作業が発生します。最初っからws使わなければよかったね...

wsのほかにも

あたりは動作するためにplay.api.Applicationが必要なのでめんどうです。

(追記・捕捉)
Play2.4からはDI前提のAPIにApplicationに依存しないものもあります。
ただしモジュールレベルでは依存関係があるので、結果的にはcoreをPlayに依存させることになります。
互換性のために以前のAPIを残しているからと思われるので今後改善される可能性はありそうです。

あと、configなども

// play.api.Play.currentはplay.api.Applicationのインスタンス
play.api.Play.current.configuration.get.....

というようにplay.api.Applicationをいじるコードを書いてしまいがちなので注意が必要です。 (それ以前にテストしにくいからやめたほうが良いですね)

設定ファイルを読み込むところは局所化して、必要な設定はコンストラクタ経由で渡すとかそういう手間のかけ方をするのが良いでしょう。

まとめ

  • コードの再利用を考えるとPlayに依存したコードをかけない場面は多い
  • ws, cache, dbはPlayにべったり依存している上、それを使ったコードは再利用したい場面が多いのでほいほい使わない
  • play.api.Application, play.api.play.current をあちこちで使わない

Play2.4のプラグインシステムにある欠陥について

私の理解が正しければ、Play2.4のプラグインシステムにはプラグインの起動順が制御できないという大きな欠陥があります。Play2.3以前はplay.pluginsファイルに優先度を記述するというイケてない感が漂うやり方ではありましたが、問題なく制御することができていました。

Play2.4からはプラグインをDIコンポーネントとして記述するようになりました。play.pluginsファイルはなくなり、優先度ではなくコード中に記述してあるコンポーネント間の依存関係を利用して起動順が結果的にうまくいくようになっています。

ただし全てのプラグインがコードを介して依存しているわけではありません。例えばflyway-playのようなデータベースマイグレーションを行うライブラリはscalikejdbc-fixtureのようにデータベースフィクスチャプラグインよりも先に起動する必要がありますが、コード上で依存させるわけにはいきません。flyway-playではなくplay-evolutionsを使うときや、マイグレーションツールを全く使わないという選択ができなくなるためです。

Playの開発チームとしてはプラグイン間にコードにはない単純な起動順という関係があるのは間違いであるという態度を取っています。 つまりflyway-playとscalikejdbc-fixtureの実装に責任があるとの指摘を受けました。 ちょっと何を言ってるのかわかりませんが、少なくとも2.3のプラグインからの移行先としてはこのようなケースは当然考慮されるべきです。そもそも移行期間すら設けられずに行われた変更なのです。

まとめるとPlay2.4のプラグインシステムではPlay2.3で行っていたプラグインの起動順制御ができないため、プラグインの組み合わせによってはPlay2.3から2.4への移行はできません。Play2.4の重大な欠陥と言えますが、1日やりとりしてもPlay開発チームと話がかみ合っていないので、修正される見込みはないかと思います。

https://github.com/playframework/playframework/pull/4960

追記

そのあと、一応の対処法は教えてもらいましたので書いておきます。 教わった例はFixtureコンポーネントのラッパーを書いて、それをMigrationsComponentに依存させろ、というもの。

class DBFixturesComponentWrapper @Inject() (migrations: MigrationsComponent) {
  val dbFixturesComponent = ???
}

これはMigrationsComponentをコンストラクタで受け取ってるのでMigrationが先に起動するわけですが、FixturesComponentはDIではなく手動でインスタンス化しています。 次のようなコードにするほうが自然に見えるかもしれません。DIで起動順を制御しようとするのは結局失敗だったということです。

class DBFixturesComponentWrapper @Inject() {
  val migrationsComponent = ???
  val dbFixturesComponent = ???
}

ビアバッシュメモ

ピザの量

  • 人数÷3枚
  • LまたはXL(3000-3500円くらい)
  • クアトロ3種類

飲み物

  • カクヤス
  • ビール、人数×1.5本 (350ml缶)
  • お茶、お茶、炭酸、非炭酸
  • 当日注文だと冷えてないので前日までに注文できると良い。

お金

  • 最初に集める。
  • 1人1000円だと足りない。1500円だとおつりがめんどくさい。よって2000円。
  • 2000円だとちょっと多いかもしれないので学生や発表者をひいきする。
  • 注文の量を増やすなら酒じゃなくて食べ物にしたほうが余らない。

その他

Typesafe が社名変えたがってる話

mixi 社はいつモンスターストライク社に変わるんだろうという冗談を言っていたら、なんと Typesaefe 社が社名を変えたいと言い始めました。最初エイプリルフール的なものかと思いましたが、冗談じゃないようです。冗談じゃないよ。

May 18, 2015 | What’s in a name? | Typesafe

自分は Typesafe 社の人間ではないし、人様の会社の社名変更に口を出すとかも変な話ですが、open process だ、意見をくれと言われたらそりゃ嫌って言うよ、だってめんどくさいもん!!

具体的に何がめんどうか、については瀬良さんのツイートをご覧ください。

↓ というわけで便利なボタンを設置しました。ご利用ください。

名前変えるな