PHPer はじめました 【2年ぶり 2度目】

xuwei_k にこんなこと言われてしまったので世の中が厳しい話をします。

この 2 年くらいは Java とか Scala とか Ruby とか Python とか bash とか awk とかちょっと Perl とか書いてたんですが、転職して今週から PHP を書いてます。PHPStorm 買いました。(サムライズムさんあざーす)
ちなみに前前職では PHP とか Python とか Java でしたし、まあいろいろですね。


Scala ではなく PHP であることに関して、特に不満はないというか、面接のときも Scala 書きたいですとか、Scala 書かせますみたいな話はいっさいしてなくて、「嫌いな食べ物は?」「え...ブロッコリーです」みたいな感じでしたので



こういうわけではないです。


最近は Scala の仕事もちらほら聞くけれど、なんで Scala で転職しなかったかというと Scala に限らず自分にとって魅力的な仕事を見つけるのは難しいというそれだけです。まあ Scala 書きたい欲みたいなものは趣味でいろいろ書いて十分発散してるので別にいいかなって感じです。


実際 PHP がだめかと言うと、好きで書く言語ではないと思いますし、いまいち感はあるけれど仕事の道具としては悪くはないと思っています。ここは意見が分かれるところだし、それについて議論するのはまずアルコールが入ったほうが楽しいです。


ちなみに前職を退職した理由については、やめた人がなに言っても説得力ないのでノーコメントですが、Scala が書きたい人がいたら紹介します。転職先がどこなのかは言っていいのかよくわかんないので言いませんが、PHP が書きたい人がいたら(ry


プログラマ各位においては、仕事の不満とか人生の辛さとか世の中の厳しさとか、そういうものを PHP にぶつけないようお気をつけください。同僚が PHP を dis っていたら「そんな事言わせちゃってごめんな」と抱きしめてあげてください。いいですか、Scala 書こうが PHP 書こうが世の中は厳しいのです。

Mac でファイルの変更を検知する

Linux の inotify 的なものが Mac にはないけれど、OS XAPI を使えばできるらしい。で、それをお手軽に叩けるようにしたのが fswatch。

https://github.com/alandipert/fswatch

インストール

$ brew install fswatch

使い方

$ fswatch-run [監視対象のパス] [実行スクリプト]

その他の方法

  • launchd
  • jnotify
  • etc

ScalaでWebAppの開発に必要なN個のこと

PerlでWebAppの開発に必要なN個のこと - Islands in the byte streamScala 版です。


あるプログラミング言語で実際にWebAppを開発できるようになるまで、何が必要だろうか。言語仕様の習得は終えているとしよう。おそらく、最低限以下のような知識が必要だと思われる。とりあえずScalaについて知っていることを書いた。

パッケージマネージャ

sbt が全てやってくれる。Scala のバージョン管理も、ライブラリのバージョン管理も。
http://www.scala-sbt.org/


Maven や gradle を使用することもできる。

アプリケーションサーバ

Scala の Web アプリは 2 つのタイプに分けられる。JavaServlet API を使用するものと、使用しないものである。どのフレームワーク、ライブラリを使うかによって変わってくる。


Servlet API を直接または間接的に使用する場合は Tomcat や Jetty などのサーブレットコンテナが使用される。
http://tomcat.apache.org/
http://www.eclipse.org/jetty/


Servlet API を使用しないものの多くは Akka や Netty、または Netty のラッパーである finagle を利用し、独自に実装されている。
http://akka.io/
http://netty.io/
https://github.com/twitter/finagle


PSGI、Rack のようなものはまだない。http4s がそれを目的に開発されているようだ。
https://github.com/http4s/http4s

ルーティングとリクエストパラメータの処理

最もとっつきやすいのは scalatra だろう。Servlet API の上に RubySinatra ライクな DSL を構築したものだ。
http://scalatra.org/


unfiltered や Akka ベースの spray も人気がある。
http://unfiltered.databinder.net/Unfiltered.html
http://spray.io/

データベース

ここが一番の悩みどころだ。

Scala からデータベースへ接続するのには JDBC を利用するのが一般的で、JDBC をベースに様々な ORM や DSL が作られてきた。

Scala の総本山 Typesafe が開発しているのは Slick だ。データベースのテーブルを Scala のコレクションのように扱うことができる。
http://slick.typesafe.com/


ORM としては Squeryl も人気がある。
http://squeryl.org/


私のおすすめは scalikejdbc だ。
https://github.com/seratch/scalikejdbc


JDBC を利用しないものでは postgresql-async, mysql-async がある。non-blocking な DB アクセスに興味があればチェックしておくと良いだろう。
https://github.com/mauricio/postgresql-async

ビューのレンダリング

XML,HTML についてはごく単純な場合は ScalaXML リテラル で事足りてしまう。


複雑になってきたら Scalate を使うと良い。Jade, SSP, Mustache, Scaml などの記法に対応している。
http://scalate.fusesource.org/


Play の Scala Template に慣れているのであれば Twirl という選択肢もある。
https://github.com/spray/twirl


Jsonレンダリングには Json4s が便利だ。
https://github.com/json4s/json4s

HTTPクライアント

dispatch が有名だ。
https://github.com/dispatch/dispatch
https://github.com/dispatch/reboot


dispatch の API はかなり奇妙なので好き嫌いが別れる。
気に入らなければ Apache の HTTP クライアントや、AsyncHttpClient など、Java のライブラリを選んで使えば良い。
dispatch もそれらを利用して実装されている。

http://hc.apache.org/
https://github.com/AsyncHttpClient/async-http-client

テストフレームワーク

ScalaTest、Specs2 が双璧をなしている。
私は Scalatest が好きだ。

http://www.scalatest.org/
http://etorreborre.github.io/specs2/


実行は sbt の test コマンドで行う。

WAF

以前は Lift が一番人気だったが今では Play 2 が取って代わった。
http://www.playframework.com/
Play 2 を使用すれば上に挙げたモジュールの多くは必要なくなる。




以上。他にもあれば指摘してほしい。

play-flyway 0.2.0 をリリースしました

Flyway の Play プラグイン、play-flyway の 0.2.0 をリリースしました。

https://github.com/tototoshi/play-flyway

インストール

Build.scala or build.sbt

libraryDependencies += "com.github.tototoshi" %% "play-flyway" % "0.2.0"

play.plugins

1000:com.github.tototoshi.play2.flyway.Plugin

主な変更内容

新たに管理ページのようなものを追加しました。/@flyway/${dbname} というパスにアクセスすると現在の DDL の適用状況を画面で確認することができます。また、ページ上部のリンクをクリックすることで migrate 以外の操作もできるようになりました。今回はとりあえず clean と init をつけてみました。


Evolutions に疲れたら一度お試しください。

Scala のモデルクラスでプライマリキーとかをどう扱うかという話

お悩み相談です。

Java とか Ruby、少なくとも ActiveRecord とか Hibernate とかではあまり気にならない話です。

Scala で例えば Slick や Anorm, scalikejdbc などのクエリのサポートのみでモデルクラスの設計はユーザーに任されているものだと、プライマリキーなどのデータベースにレコードを保存した時点で値が決まるフィールドの型をどうすべきか悩みます。

例えば次のような user テーブルについて考えてみます。id カラムがプライマリキーで、データベースの自動採番を利用します。また、created_at は省略するとデフォルト値をデータベースから取得します。

-- postgresql

CREATE TABLE user(
  id serial PRIMARY KEY,
  firstName VARCHAR(30) NOT NULL,
  lastName VARCHAR(30) NOT NULL,
  age INTEGER NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT current_timestamp
)

さて、このテーブルに対する Entity と Dao のインタフェースがどうなるか考えてみます。一番単純で、理想的と思うのはこうです。

// Entity
case class User(
  id: Int,
  firstName: String,
  lastName: String,
  age: Int,
  createdAt: DateTime
)

// Dao
object UserDao {
    
  def find(id: Int): Option[User] = ???

  def create(user: User): User = ???

  def update(user: User): User = ???

  def delete(user: User): Unit = ???

}

User のそれぞれのフィールドの型はデータベースの型と一対一に対応させます。そして UserDao のメソッドの引数は全て User 型です。
ですが、この API はうまくいきません。なぜなら User の id フィールドはデータベースに保存しないので、User#create に渡す User クラスのインスタンスが作れないからです。

Pk 型を使う

Play の Anorm では Pk 型というプライマリキーを表す型が用意されています。
Pk[T] は Option に似たコンテナで、Option[T] に Some[T] と None の 2 種類があるように、Pk[T] には Id[T] と NotAssigned の 2 種類があります。実装も単に Option をラップしただけです。

abstract class Pk[+ID] {

  def toOption: Option[ID] = this match {
    case Id(x) => Some(x)
    case NotAssigned => None
  }

  def isDefined: Boolean = toOption.isDefined
  def get: ID = toOption.get
  def getOrElse[V >: ID](id: V): V = toOption.getOrElse(id)
  def map[B](f: ID => B) = toOption.map(f)
  def flatMap[B](f: ID => Option[B]) = toOption.flatMap(f)
  def foreach(f: ID => Unit) = toOption.foreach(f)

}

case class Id[ID](id: ID) extends Pk[ID] {
  override def toString() = id.toString
}

case object NotAssigned extends Pk[Nothing] {
  override def toString() = "NotAssigned"
}


これを使って User エンティティ を定義しなおしてみましょう。

case class User(
  id: Pk[Int],
  firstName: String,
  lastName: String,
  age: Int,
  createdAt: DateTime
)

これで、データベースに保存する前のユーザーも、保存した後のユーザーも User クラスで表せるようになります。
UserDao#create は id が NotAssigned な User クラスを受け取り、データベースに保存し、id がセットされた User クラスを返すように実装します。
使い方は次のようになります。

val u = User(NotAssigned, "John", "Lennon", 30, DateTime.now)

val u2 = UserDao.craete(u)

println(u2.get) //=> 1 (保存した後は、プライマリキーがセットされた値になっている)

で、この解決法を最初見たときはなるほどと思いましたが、使ってみるとすぐにイマイチだと気づきます。

まず、データベースに保存しないと値が決まらないのはなにもプライマリキーだけではありません。今まで User の cratedAt フィールドはアプリケーションコードの方でセットしていましたが、これをデータベースへ保存するときにデータベースから取得する方針に変えたらどうなるでしょう。
プライマリキー意外にも Pk 型みたいなのが欲しいですね。createdAt に Pk 型を使うのはあまりにも変なので、Option を使うことにしてみましょうか。

case class User(
  id: Pk[Int],
  firstName: String,
  lastName: String,
  age: Int,
  createdAt: Option[DateTime]
)

createdAt を Option[DateTime] にしてみましたが、これでは createdAt が Nullable という風に見えますね。だめだこりゃ。
だからといって自分で Pk 型みたいなのを別途作るのもめんどう。それからそもそもの話、内部でデータベースをどう使ってるかという細かい話題が表に出てきすぎている感があります。

そして、Pk 型にはもう一つ欠点があります。いちいちプライマリキーの値が Id[T] なのか Assigned かを気にしてコードを書かなければいけないのです。データベースから SELECT したら Id[T] と決まっているのに Pk[T] という文脈で扱わなければいけないのは(無駄に)すごく面倒です。面倒過ぎるので残念な気持ちになりながら Pk#get を使うことになります。


というわけで、Pk 型はやめときましょう。

でも、一体どうするのがいいんだろう。。。というのが以下、本題です。

Dao#create の API を変える

User クラスは「データベースに保存されているユーザーのデータ」を表すことと考え、Dao#create で User 型を引数にとるのをやめます。

だからといって次のように保存する前だけ別の型にするのは堅(型)苦しい気がします。

object UserDao {

  def find(id: Int): Option[User] = ???

  def create(user: NotPersistedUser): User = ???

  def update(user: User): User = ???

  def delete(user: User): Unit = ???

}

なので次のようにするのが妥当ではないでしょうか。

object UserDao {
  ...
  def create(firstName: String, lastName: String, age: Int, createdAt: DateTime): User = ???
  ...
}

欠点はフィールドの値が増えるとだんだんしんどくなることです。引数が増えると引数の順番を間違えてしまうなどのミスも起きますし。名前付き引数とかを使えばミスは減りますが、冗長さが増します。

適当にデフォルト値を用意する

プライマリキーのデフォルト値を適当に 0 とか決めてやればとりあえず User のインスタンスを作れます。欠点は「気持ち悪い」こと。「0 がデフォルト」という実装のためだけの謎ルールが生まれること。

val u = User(0, "John", "Lennon", 30, DateTime.now)

UserDao.create(u)

エンティティを case class にするのを諦める

案2 を少し進める感じです。ミュータブルにはなるけれど、「0がデフォルト」のルールは隠蔽できます。Squeryl っぽい感じですね。

class User(
  val id: Int,
  var firstName: String,
  var lastName: String,
  var age: Int,
  var createdAt: DateTime) {

  def this() = this(0, "", "", 0, DateTime.now)

}
val u = new User()
u.lastName = "John"
u.firstName = "Lennon"
u.age = 30
UserDao.create(u)

案外これが一番使いやすいかも。。。Scala っぽくないけど。

まとめ

細かいっちゃ細かいですが、いつも気になるので書いてみました。ご意見募集中です。

play-ascii-art-plugin 0.2.0 をリリースしました

https://github.com/tototoshi/play-ascii-art-plugin

Play の起動・終了時にアスキーアートを表示させるジョークプラグイン play-ascii-art-plugin の 0.2.0 ををリリースしました。

play dist でのデプロイに対応しました。詳しくは前回のエントリを参照してください。
それに伴い設定の仕方が少し変わって、今まではファイルパスを指定していたのがリソース名を指定するようになっています。

あと headless で実行するようにとか、デフォルトの画像を入れたりとか修正しています。