EBeanでもユニットテストの導入はスマートです。そう、Lombokならね。
Play framework 2.x Java and 1.x Advent Calendar 2013*1の6日目です。
お題は、Lombokを使ってPlay2 Javaのユニットテストを書きやすくできますよという話。
ケース
Play2 Java使っている方なら当然、永続化はEBeanですよね。そうでしょうそうでしょう。では、例としてこんな感じのテーブル
CREATE TABLE items ( id serial NOT NULL PRIMARY KEY, name text NOT NULL );
を、こういうエンティティクラス
@Entity @Table(name="items") public class Item extends Model { @Id @NotNull public Integer id; @NotNull public String name; }
でO/Rマッピングしたとしましょう。
そして、このエンティティクラスを使って要求を処理するビジネスロジックを書きます。ビジネスロジックにはユニットテストも書きましょう。
さて、ユニットテストはDBの現在状態に依存したりDBに変更を加えたりしてほしくありません。
ではテストのときだけインメモリのH2を使おうかというと、ここで定義したテーブルには出てこないのですがスキーマの他の部分ではPostgreSQLに依存した設計をしてしまっていてH2では代わりにならない状況です。困りました。
ここで我々はこう考えます。
「そうだ、エンティティクラスというフレームワーク独自のクラスにビジネスロジックが依存してしまっているのがそもそもおかしいんだ。ビジネスロジック層はEBeanのエンティティクラスそのものではなくそこから抽出したインターフェースを扱うようにしないと」
正論ですね。しかし手を動かそうとしたらすぐ固まります。
「フィールドはインターフェースに抽出できないじゃないの…」
ここ泣くところです。せっかくEBeanはJava Beansと決別してあのgetter/setterのいらないプログラミングを提供してくれました。なのにインターフェースの抽出をしようとするとJavaは冷酷にもgetter/setterを使ったアクセスしかないよねと迫ってくるのです。
醜く変貌したエンティティクラスを一応書きました*2。ご覧ください。
@Entity @Table(name="items") public class Item extends Model implements IItem { @Id @NotNull protected Integer id; @NotNull protected String name; @Override public Integer getId() { return id; } @Override public void setId(Integer id) { this.id = id; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } }
そこでLombok
しかしです。絶望だらけのこの世にもLombokがある。
Lombokをご存じない方はこの記事
とかこのプレゼン資料
これを使えば、getter/setterはコードに書かなくてもいいということです。これってつまり、エンティティクラスを単純に保ったままインターフェースが抽出できるってことじゃない? いいじゃん!
コンパイラに介入するという点ではEBeanもそうなので*3、てっきりEBeanとLombokは共存できないものと思い込んでいました。最近まで。ところがやってみたらあっさり共存できてしまった次第。もう絶望する必要なんて、ない。
導入方法
Play 2.0/2.1ならBuild.scala、2.2ならbuild.sbtで依存にLombokを追加します。
"org.projectlombok" % "lombok" % "1.12.2",
実はこれだけでビルドファイル的には準備完了なのですがIDEが理解してくれないといろいろ赤波線とかあれですから、プロジェクトファイルを作り直してIDEにもLombokライブラリを参照してもらいます。
【プロンプト】 play idea
ここではIntelliJ IDEAを想定していますが、IntelliJ IDEAの場合さらに、プラグインとしてLombokをリポジトリから探してインストールします。
改善後
あの醜かったエンティティクラスはこう書き換えられます。
@Entity @Table(name="items") @Data public class Item extends Model implements IItem { @Id @NotNull public Integer id; @NotNull public String name; }
最初のクラスに比べると@Data
の記述が加わっただけですね。シンプル!
EBeanの崇高な理想は守られました!
その他
インターフェースの抽出をした場合、ファクトリメソッドの書き方に困る場合があります。
public List<Item> fetchAll();
なんてメソッドをインターフェース抽出にともなって
public List<IItem> fetchAll();
に書き換えようとすると、その内部処理で詰まります。なぜならList<Item>
はList<IItem>
にキャストできないから。
こういうときは、
で書いたように
public List<? extends IItem> fetchAll();
明日は
@aimluck_iwasakiさんがサブプロジェクトや運用の話題をとのことです。