以前SpringFrameworkを用いた開発でDozerというライブラリを使用していたのですが、それが便利なライブラリだったので、現在使用しているPlayFrameworkでも使ってみようと思い、コードを書いてみました。作成したソースはGitHubに上げておきます。
環境
- PlayFramework2.5
- Java8
Dozerとは
オブジェクト間のデータをマッピングするライブラリです。MVCフレームワークでレイヤ間のデータの受け渡しをする時などに威力を発揮します。
まずはDozerを使用しない通常の実装を見てみます。現在のレイヤから次のレイヤにデータを渡すときに以下のようなコードをよく書くと思います。
// 現在のレイヤ
// 次のレイヤのビーンにデータを渡す
BeanB beanB = new BeanB();
beanB.field1 = beanA.field1;
beanB.field2 = beanA.field2;
beanB.field3 = beanA.field3;
.
.
.
// 次のレイヤの処理
service.run(beanB);
上記はシンプルなパターンですが、ビーンがセッターやゲッターを実装している場合や、ビーン間で型が違うために型変換を行っている場合は、可読性も悪くパラメータが多ければ多いほど冗長なコードになってしまいますね。さらにビーンにパラメータの増減が発生する場合、その度にマッピングの修正をしなければいけません。次はDozerを使用した例です。
// 現在のレイヤ
// Dozerによるビーンマッピング
Mapper mapper = new DozerBeanMapper();
BeanB beanB = mapper.map(beanA, BeanB.class);
// 次のレイヤの処理
service.run(beanB);
mapメソッドの第一引数にコピー元のオブジェクト、第二引数にはコピー先のクラスを渡すと、コピー元のデータを保持したコピー先クラスのオブジェクトが返却される。Dozerはコピー元の持つデータをコピー先の同じ名称のパラメータにマッピングしてくれるからです。さらに型変換サポートしている型であれば自動で型変換を行ってくれます。型変換サポートの詳細はこちらを参照してみてください。
Play導入の準備
ではDozerを使ってみようと思います。画面から名前と金額を入力し登録するだけのアプリを作ってみたので、そこにDozerを導入したいと思います。導入と言ってもbuild.sbtに追加するだけで使用できるので、特に難しいことはありません。
まずbuild.sbtに以下のように追記し、reloadとupdateを行います。
libraryDependencies += "net.sf.dozer" % "dozer" % "5.5.1"
あとはDozerを使用するレイヤでDozerBeanMapperを生成して使用すればいいんですけど、今回はDIで定義したいと思います。AbstractModuleを継承したMappingModuleクラスを作り、依存性の定義します。
// import省略
public class MappingModule extends AbstractModule {
@Override
protected void configure() {
bind(Mapper.class).to(DozerBeanMapper.class);
}
}
作成したMappingModuleをapplication.confでmodules登録します。
play.modules {
enabled += modules.MappingModule
}
インジェクト
各レイヤで使用するビーンは以下のように定義しています。
ビーン | レイヤ |
---|---|
Form | Client - Controller |
Dto | Controller - Service |
Model | Service - Repository |
Dozerを使用したいクラスにMapperをインジェクトします。下記はControllerクラスなので、FormからDtoにデータマッピングしています。
package controllers;
// import省略
public class IndexController extends Controller {
@Inject
private FormFactory formFactory;
@Inject
private Mapper mapper; // これ
@Inject
private IndexService service;
// 他メソッド省略
public Result post() {
Form<IndexForm> f = formFactory.form(IndexForm.class).bindFromRequest();
IndexDto dto = mapper.map(f.get(), IndexDto.class); // ここでマッピング
switch(f.get().action){
case "regist":
service.regist(dto);
break;
case "delete":
service.delete(dto);
break;
default:
}
return ok(views.html.index.render(f, dto));
}
}
FormにRequestBindしたあと、Dtoにデータマッピングし次のレイヤ(Service)に処理を移しています。FormとDtoはそれぞれ以下のように定義してあります。
package forms;
public class IndexForm {
/** 画面アクション */
public String action;
/** 名前 */
public String name;
/** 金額 */
public String amount;
/** 削除ID */
public String id;
}
package dtos;
// import省略
public class IndexDto {
/** 名前 */
public String name;
/** 金額 */
public BigDecimal amount;
/** 削除ID */
public Long id;
/** データリスト */
public List<Person> personList;
}
金額と削除IDは型が違いますが、Mapperの中で型変換を行ってくれます。
最後に
実際にサーバ起動し動作確認を行っているので、ソースはGitHubに上げておきます。
Dozerには異なる名前のフィールド間のマッピングや、複雑なマッピングを可能とするカスタムができるのですが、Playでの設定方法がわからず書けませんでした。おいおい調査し更新したいと思います。