S-JIS[2013-11-09/2018-06-26] 変更履歴

Asakusa Framework 変換演算子

Asakusa FrameworkOperator DSLの変換演算子(@Convert)のメモ。


概要

変換演算子は、入力データを別のデータモデルに変換する演算子。
性能特性はExtract(旧ドキュメントではMap)。[/2016-02-11]

入力
ポート数
入力データモデル
の制約
イメージ 出力
ポート数
出力データモデル
の制約
入力1レコード
に対する
出力レコード数
1   2 out   1レコード。
original inと同じ
データモデル
1レコード。

変換したデータはoutから出力される。
同時に、変換前のデータそのものがoriginalから出力される。 というか、Convert演算子の中で元のデータを変更してもよく、それがoriginalから出力される。[/2018-08-26]

originalを使わない場合は停止演算子(stop)に接続する。
※AsakusaFW 0.10.1から、originalを使わない場合はstop演算子に渡さなくてもよくなった。[2018-08-26]


変換演算子は、データモデルの種類を変更するのに使う。
ただし、データモデルのプロパティーを増減させる目的なら、再構築演算子(restructure)を使う。
複数種類のデータモデルを出力したいなら抽出演算子(@Extract)を使う。

別のデータ種類に変換するのではなく内容(値)を変えたいだけなら、更新演算子(@Update)を使う。
複数レコードの集計をしたい場合は単純集計演算子(@Summarize)を使う。
(集計以外で)他のレコードを参照したい場合はグループ整列演算子(@GroupSort)を使う。


hogeというデータモデルをfooデータモデルに変換する例。
(この図はToad Editorを用いて作っています)

Toad Editorでは、停止演算子(stop)を丸の中に×(バツ印)の入った図形で表している。
(AsakusaFW 0.10.1から、originalを使わない場合はどこにも結線する(stop演算子に渡す)必要が無くなった。[2018-08-26]

入力データ例   出力データ例
in
hoge
value
1000
12000
3100
out
foo
value
1
12
3
original
hoge
value
1000
12100
2000

example.dmdl(DMDL):

hoge = {
    value : INT;
};

foo = {
    value : INT;
};

ExampleOperator.java(Operator DSL):

import com.asakusafw.vocabulary.operator.Convert;

import com.example.modelgen.dmdl.model.Foo;
import com.example.modelgen.dmdl.model.Hoge;
public abstract class ExampleOperator {

	private final Foo foo = new Foo();

	/**
	 * HogeをFooに変換する
	 * 
	 * @param hoge
	 *         変換するレコード
	 * @return 変換後のレコード
	 */
	@Convert
	public Foo toFoo(Hoge hoge) {
		foo.reset(); // 初期化

		foo.setValue(hoge.getValue() / 1000);
		return foo;
	}
}

引数に入力のデータモデルを指定する。
戻り値には変換後のデータモデルを指定する。

Operatorクラスに定義したメソッドはスレッドセーフになる(別スレッドから同時に呼ばれることは無い)ので、フィールドにオブジェクトを定義して使い回してもよい。
(ただし、以前のレコードの値の保持には使えない。実行時には各マシンに分散して処理されるので、レコードの入力順も どのレコードが来るのかも保証されない為)

なお、返したオブジェクトは次に使うときは内容が破壊されている(可能性がある)ので、毎回初期化する必要がある。[2014-12-21]
Resultにaddしたオブジェクトが破壊される可能性があるのと同じ理由)

ExampleJob.java(Flow DSL):

import com.example.modelgen.dmdl.model.Hoge;

import com.example.operator.ExampleOperatorFactory;
import com.example.operator.ExampleOperatorFactory.ToFoo;
	private final In<Hoge> in;

	private final Out<Foo> out;
	@Override
	public void describe() {
		ExampleOperatorFactory operators = new ExampleOperatorFactory();
		CoreOperatorFactory core = new CoreOperatorFactory();

		// HogeをFooに変換する
		ToFoo toFoo = operators.toFoo(this.in);
		core.stop(toFoo.original); // オリジナルは使わないので停止演算子へ接続

		this.out.add(toFoo.out);
	}

Flow DSLでは、自分が作ったOperatorのFactoryクラス(AsakusaFWのコンパイラーによって生成される)を使用する。
メソッド名はOperatorクラスに書いたメソッド名と同じ。
戻り値の型はAsakusaFWのコンパイラーによって生成されたクラス。(メソッド名を先頭が大文字のキャメルケースに変換したもの)
出力ポートの名前のデフォルトはoutとoriginal。

出力ポート名を変えたい場合は@Convertアノテーションで指定できる。

	@Convert(originalPort = "original", convertedPort = "out")

AsakusaFW 0.10.1から、originalは結線しなくても(stop演算子に渡さなくても)よくなった。[2018-08-26]
実際、originalはstop以外に渡した事は一度も無いし^^;


単体テスト

変換演算子の単体テストの実装例。

ExampleOperatorTest.java:

package com.example.operator;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;

import com.example.modelgen.dmdl.model.Hoge;
/**
 * {@link ExampleOperator}のテスト.
 */
public class ExampleOperatorTest {

	@Test
	public void toFoo() {
		ExampleOperator operator = new ExampleOperatorImpl();

		Hoge hoge = new Hoge();
		hoge.setValue(123);

		Foo foo = operator.toFoo(hoge);

		assertThat(foo.getValue(), is(123));
	}
}

Operatorのテストクラスは、通常のJavaのJUnitのテストケースクラスとして作成する。

テスト対象のOperatorクラス自身は抽象クラスだが、Operatorクラス名の末尾に「Impl」の付いた具象クラスがAsakusaFWによって生成されるので、それを使う。


値引数の例

変換演算子では、変更元のデータモデルの他にも引数を指定することが出来る。
こうすると、引数の内容分だけ異なる同じ演算子(処理内容)をいくつかの場所で使用できるようになる。

Hoge2というデータモデルをErrorRecordデータモデルに変換し、「message」項目に引数で指定したメッセージを設定する例。

example.dmdl(DMDL):

hoge2 = {

    "コード"
    code : TEXT;
};

error_record = {

    "コード"
    code : TEXT;

    "メッセージ"
    message : TEXT;
};

ExampleOperator.java(Operator DSL):

import com.asakusafw.vocabulary.operator.Convert;

import com.example.modelgen.dmdl.model.ErrorRecord;
import com.example.modelgen.dmdl.model.Hoge2;
public abstract class ExampleOperator {

	/**
	 * エラー情報に変換する
	 * 
	 * @param hoge
	 *         変換するレコード
	 * @param message
	 *         メッセージ
	 * @return エラー情報レコード
	 */
	@Convert
	public ErrorRecord toErrorRecord(Hoge2 hoge, String message) {
		ErrorRecord record = new ErrorRecord();

		record.setCode(hoge.getCode());
		record.setMessageAsString(message);

		return record;
	}
}

第1引数は通常の変換演算子と同じく変更元のデータモデル。
第2引数以降を通常のJavaメソッドと同様に自由な引数とすることが出来る。(ただしプリミティブのみ)

ExampleJob.java(Flow DSL):

import com.asakusafw.vocabulary.flow.util.CoreOperatorFactory;

import com.example.modelgen.dmdl.model.ErrorRecord;
import com.example.modelgen.dmdl.model.Hoge2;

import com.example.operator.ExampleOperatorFactory;
import com.example.operator.ExampleOperatorFactory.ToErrorRecord;
	@Override
	public void describe() {
		ExampleOperatorFactory operators = new ExampleOperatorFactory();
		CoreOperatorFactory core = new CoreOperatorFactory();

		// エラー情報に変換する
		ToErrorRecord toErrorRecord = operators.toErrorRecord(this.in, "Hoge2エラー");
		core.stop(toErrorRecord.original); // オリジナルは使わないので停止演算子へ接続

		this.out.add(toErrorRecord.out);
	}

演算子を使用するFlow DSLで、引数に具体的な値を指定する。


ExampleOperatorTest.java:

	@Test
	public void toErrorRecord() {
		ExampleOperator operator = new ExampleOperatorImpl();

		Hoge2 hoge = new Hoge2();
		hoge.setCodeAsString("abc");
		String message = "Hoge2エラー";

		ErrorRecord record = operator.toErrorRecord(hoge, message);

		assertThat(record.getCodeAsString(), is("abc"));
		assertThat(record.getMessageAsString(), is(message));
	}

他の演算子による代替

変換演算子は、再構築更新で同様の処理を行うことが出来る。

同名のプロパティーを移送する処理が多いなら、こちらの方がいいかもしれない。
(変換演算子は移送を全てプログラマーが実装する必要があるが、再構築演算子では移送処理を自動生成してくれるので)

または、変換後のデータモデルが変換元のデータモデルと関係が薄いとき(プロパティー名が一致している程度のとき)は再構築演算子の方が良いかもしれない。(汎用のエラーテーブルへ変換するとか)
変換演算子ではデータ移送を全てコーディングしなければならないので、色々な種類のデータモデルを入力にするのは難しい。
再構築演算子はその辺りがやりやすい。


類似

変換演算子は、SQLのSELECT-INSERTに似ている。

INSERT out (code, message)
SELECT code, 'メッセージ' FROM in;

あるいは、Scalamapメソッドに似ている。

  case class Hoge(value: Int)
  case class Foo(value: Int)

  def toFoo(hoge: Hoge) = Foo(hoge.value)

  val in : List[Hoge] = 〜
  val out: List[Foo]  = in.map(toFoo)

Operator DSLへ戻る / AsakusaFW目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま