Asakusa Framework : ケーススタディ:多対多のデータモデル結合

f:id:teppei-studio:20131112202621j:plain

Asakusa Frameworkシリーズ、ハマりやすいシリーズも一通りやった気がするので、ケーススタディを書いていきたいと思います。

尚、この記事は、
Asakusa Framework Advent Calendar 2013の二日目の記事としても書いています。全然人が集まりませんが、少しでも途切れないように、貢献したいと思います。

ケース内容

今回やってみたいと思うのでは、多対多のデータモデル結合です。
以下の図のようなイメージです。

f:id:teppei-studio:20131201162416p:plain

結合といえば、MasterJoinを利用したいところですが、MasterJoinは1対多の結合のみを前提としているので、今回のようなケースでは使えません。

でもこういうことをやりたい場面って結構ありますよね?

今回のケースでは、ExtendとConfluent、そしてFoldを組み合わせて使うことになります。

JobFlow設計

こちらの図に記載したものが、今回構築するJobFlowになります。

f:id:teppei-studio:20131201162421p:plain

①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));
	}
}

おしまい

今日お見せしたソースについては、一通り、
GitHubに挙げているので、ご確認ください。

さてさて、アドベントカレンダーはこのまま続くことができるかな…?