Asakusa Framework : ケーススタディ:多対多のデータモデル結合
Asakusa Frameworkシリーズ、ハマりやすいシリーズも一通りやった気がするので、ケーススタディを書いていきたいと思います。
尚、この記事は、
Asakusa Framework Advent Calendar 2013の二日目の記事としても書いています。全然人が集まりませんが、少しでも途切れないように、貢献したいと思います。
これまでのAsakusa Framework関連記事
ケース内容
今回やってみたいと思うのでは、多対多のデータモデル結合です。
以下の図のようなイメージです。
結合といえば、MasterJoinを利用したいところですが、MasterJoinは1対多の結合のみを前提としているので、今回のようなケースでは使えません。
でもこういうことをやりたい場面って結構ありますよね?
今回のケースでは、ExtendとConfluent、そしてFoldを組み合わせて使うことになります。
JobFlow設計
こちらの図に記載したものが、今回構築するJobFlowになります。
①Extend
まずは元のモデルである、ModelAとModelBをExtend(拡張)します。これは、データモデルのプロパティを増加させて、増加先のモデルと同じ名前のプロパティの値を元のモデルからとってきます。
②Confluent
次に、拡張したModelAB同士のデータを、Confluent(合流)します。
③Fold
すると、キーが重複したレコードができあがるので、それをFold(畳み込み)してあげます。キーが重複していないレコードはそのままになります。
JobFlow実装
Asakusa Frameworkが素晴らしいところは、この設計のまんま実装に落とせることです。
JobFlowクラスのdescribeメソッドだけ見てみます。
@Override protected void describe() { MainOperatorFactory op = new MainOperatorFactory(); CoreOperatorFactory cp = new CoreOperatorFactory(); //①まずはインプットしたModelAとModelBをそれぞれModelABにExtend(拡張) Extend<ModelAb> extendedA = cp.extend(modelA, ModelAb.class); Extend<ModelAb> extendedB = cp.extend(modelB, ModelAb.class); //②Extendした両モデルを、Confluent(合流) Confluent<ModelAb> confluented = cp.confluent(extendedA.out, extendedB.out); //③ConfluentしたモデルをFold(畳み込み) Folded folded = op.folded(confluented.out); modelAB.add(folded.out); }
設計のまんまですね。
Operator実装
今回利用した3つの演算子のうち、FoldだけOperatorの実装が必要です。どう畳み込むか実装するわけですね。以下のように実装しました。
@Fold public void folded(@Key(group="id")ModelAb left, ModelAb right){ //leftにModelA、rightにModelBが入っている場合 if(left.getDataBOption().isNull() && right.getDataAOption().isNull()){ left.setDataB(right.getDataB()); //leftにModelB、rightにModelAが入っている場合 }else if(left.getDataAOption().isNull() && right.getDataBOption().isNull()){ left.setDataA(right.getDataA()); //どちらでもない(ModelA、ModelB両方入っている、あるいは入っていない)はありえない }else{ throw new Error("ModelA、ModelB両方入っている、あるいは入っていない"); } }
お分かりのように、プロパティひとつひとつの処理が必要になるので、如何せん、プロパティ数が多くなった時に面倒なことになりますので注意が必要です。
Operatorのテスト
これまでOperatorのテストについて書いてこなかったので、ここで簡単に触れます。
以下のように、通常のJUnitと同じように実装が可能です。
package teppeistudio.operator; import org.junit.Test; import static org.junit.Assert.assertThat; import static org.hamcrest.Matchers.is; import teppeistudio.modelgen.dmdl.model.ModelAb; public class MainOperatorTest { @Test public void left_is_A_and_right_is_B(){ MainOperatorImpl op = new MainOperatorImpl(); ModelAb left = new ModelAb(); left.setId(1); left.setNameAsString("name"); left.setDataA(1); ModelAb right = new ModelAb(); right.setId(1); right.setNameAsString("name"); right.setDataB(2); op.folded(left, right); assertThat(left.getId(), is(1)); assertThat(left.getNameAsString(), is("name")); assertThat(left.getDataA(), is(1.0)); assertThat(left.getDataB(), is(2.0)); } @Test public void left_is_B_and_right_is_A(){ MainOperatorImpl op = new MainOperatorImpl(); ModelAb left = new ModelAb(); left.setId(1); left.setNameAsString("name"); left.setDataB(1); ModelAb right = new ModelAb(); right.setId(1); right.setNameAsString("name"); right.setDataA(2); op.folded(left, right); assertThat(left.getId(), is(1)); assertThat(left.getNameAsString(), is("name")); assertThat(left.getDataA(), is(2.0)); assertThat(left.getDataB(), is(1.0)); } }