Subscribed unsubscribe Subscribe Subscribe

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

Play Scala

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

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が必要なのでめんどうです。

あと、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のプラグインシステムにある欠陥について

Play Scala

私の理解が正しければ、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 = ???
}

Scala関西 Summit 2015 でflyway-playについて話しました

Flyway Scala Play Java

先週大阪行ってきました。

内容としては、flyway-play、というかFlywayの紹介っぽくなってScala成分薄めになりました。

ビアバッシュメモ

ピザの量

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

飲み物

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

お金

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

その他

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

Scala

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

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

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

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

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

名前変えるな

WEB+DB Press 86 に PHP での画像処理についての記事を書きました

PHP

4/23 発売の WEB+DB Press Vol.86 に PHP での画像処理についての記事を書きました。 画像をこねくり回すという内容ではなく、Web アプリで画像を扱うときに気にするべきことを広く浅くカバーしようという内容です。基本的な Imagick (imagemagick) の使い方から、有名な最適化 Tips、それからジョブキューを活用した負荷対策や、サムネイルの動的生成などについて実際に pixiv で使われている技術をベースに解説しています。PHP に限らずこれから画像を扱うシステムを作ろうとしているけれど、ちょっと自信がないなあという人にはおすすめの内容です。是非手に取ってみてください。

WEB+DB PRESS Vol.86

WEB+DB PRESS Vol.86

Slick のコードを生成する sbt プラグインを作りました

Scala Play sbt

Slick コードを生成のイブラリがあり、公式のほうにいくつか使い方のサンプルがありますが、毎回コピペするのもなあと思って sbt プラグインにしました。今のところ Slick 2.1.0 に依存しています。Slick 3.0.0 が出たらそれ用のバージョンも出そうと思います。

インストールは plugins.sbt に addSbtPlugin を加えた上で、コード生成に使う JDBC ドライバを追加します。

// plugins.sbt

addSbtPlugin("com.github.tototoshi" % "sbt-slick-codegen" % "0.1.0")

// Database driver
// For example, when you are using PostgreSQL
libraryDependencies += "org.postgresql" % "postgresql" % "9.4-1201-jdbc41

build.sbt の設定は以下のような感じです。

// build.sbt
import scala.slick.codegen.SourceCodeGenerator
import scala.slick.{ model => m }

// required
slickCodegenSettings

// required
// Register codegen hook
sourceGenerators in Compile <+= slickCodegen

// required
slickCodegenDatabaseUrl := "jdbc:postgresql://localhost/example"

// required
slickCodegenDatabaseUser := "dbuser"

// required
slickCodegenDatabasePassword := "dbpassword"

// required (If not set, postgresql driver is choosen)
slickCodegenDriver := scala.slick.driver.PostgresDriver

// required (If not set, postgresql driver is choosen)
slickCodegenJdbcDriver := "org.postgresql.Driver"

// optional but maybe you want
slickCodegenOutputPackage := "com.example.models"

// optional, pass your own custom source code generator
slickCodegenCodeGenerator := { (model: m.Model) => new SourceCodeGenerator(model) }

// optional
// For example, to exclude flyway's schema_version table from the target of codegen
slickCodegenExcludedTables in Compile := Seq("schema_version")

sourceGenerators in Compile に加えることでコンパイルの前にコード生成が走ります。この設定では毎回コード生成が走りますが、毎回同じファイルが生成されるため、再コンパイルされることはありません。コード生成自体の時間が気になる場合は slickCodegen タスクにキャッシュを入れればよいと思います。sbt の FileFunction.cached とか使えば簡単に実装できると思います。

デフォルトでは Slick の SourceCodeGenerator によりソースコード生成が行われますが、以前このブログにも書きましたが、java.sql.Timestamp じゃなくて joda-time を使いたい、などということがあると思います。そのときは slickCodegenCodeGenerator にカスタマイズした SourceCodeGenerator を設定します。

  slickCodegenCodeGenerator := { (model:  m.Model) =>
      new SourceCodeGenerator(model) {
        override def code =
          "import com.github.tototoshi.slick.PostgresJodaSupport._\n" + "import org.joda.time.DateTime\n" + super.code
        override def Table = new Table(_) {
          override def Column = new Column(_) {
            override def rawType = model.tpe match {
              case "java.sql.Timestamp" => "DateTime"
              case _ =>
                super.rawType
            }
          }
        }
      }
    }

データベースマイグレーション共存させる

本編ここから。Slick に限った話ではないですが、すでにデータベースにあるテーブルを使ってコード生成を行うという仕組みは、データベースマイグレーションツールとの相性が非常に悪いです。play-evolutions や play-flyway は Play アプリケーションの起動時にデータベースマイグレーションを行うため、コード生成とマイグレーションデッドロックしてどうしようもありません。

今のところは dev-mode のデータベースマイグレーションは play-flyway ではなく、Flyway の sbt プラグインを使うことで回避しています。テストのときなどの Slick のコード生成が終わった後では play-flyway も使えるようになります。ちょっとダサい気がしているけれど、追加する設定も少ないし、まあ良いかという感じです。sbt ではなく Flyway のコマンドラインツールを使うというのでも良いと思います。

困ったことに Flyway の sbt プラグインを使ったからといってこの問題とさよならできるわけではありませんでした。実はこのプラグインコンパイルの後にマイグレーションを走らせる作りになっているため、Flyway 用に別の空プロジェクトを使って、そこでマイグレーションを行う、というようにしました。flyway という空プロジェクトを作ったら、マイグレーションflyway/flywayMigrate というコマンドで実行できます。

設定を見てもらうのが早いと思うので、サンプルプロジェクトにまとめて置きました。

sbt-slick-codegen-example/build.sbt at 7e14ae7e47539a3faa2556620a4afdce716261bf · tototoshi/sbt-slick-codegen-example · GitHub

回避に回避を重ねましたが、最終的な設定としては一応理解可能なレベルにまとめられたのではないかと思います。最初はサブプロジェクトで FakeApplication を使って無理やり Play.start してマイグレーションを走らせて...みたいなことをしていた(意味わかるでしょうか)のですがそれよりはずっと良いと思います。