S-JIS[2013-11-09/2016-02-11] 変更履歴

Asakusa Framework 更新演算子

Asakusa FrameworkOperator DSLの更新演算子(@Update)のメモ。


概要

更新演算子は、レコードの内容(データモデルのプロパティーの値)を変更する演算子。
性能特性はExtract(旧ドキュメントではMap)。[/2016-02-11]

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

更新演算子は、データモデル内の値を変更するのに使う。
同一レコード内の他の値を使用(参照)することは出来るが、他のレコード(前後のレコードとか)を見ることは出来ない。

入力データモデルを別のデータモデルに変換して出力したい場合は変換演算子(@Convert)を使う。
複数レコードの集計をしたい場合は単純集計演算子(@Summarize)を使う。
(集計以外で)他のレコードを参照したい場合はグループ整列演算子(@GroupSort)を使う。


hogeというデータモデルの「value」項目に100をセットする例。
(この図はToad Editorを用いて作っています)

入力データ例   出力データ例
in
hoge
value
1000
12000
3100
out
hoge
value
100
100
100

example.dmdl(DMDL):

hoge = {

    value : INT;
};

ExampleOperator.java(Operator DSL):

import com.asakusafw.vocabulary.operator.Update;

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

	/**
	 * valueに100を設定する
	 * 
	 * @param hoge
	 *         更新するレコード
	 */
	@Update
	public void edit(Hoge hoge) {
		hoge.setValue(100);
	}
}

引数に更新対象のデータモデルを指定する。
戻り型は無し。

ExampleJob.java(Flow DSL):

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

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

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

		// valueに100を設定する
		Edit edit = operators.edit(this.in);

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

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

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

	@Update(outputPort = "out")

単体テスト

更新演算子の単体テストの実装例。

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 edit() {
		ExampleOperator operator = new ExampleOperatorImpl();

		Hoge hoge = new Hoge();

		operator.edit(hoge);

		assertThat(hoge.getValue(), is(100));
	}
}

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.Update;

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

	/**
	 * メッセージを設定する
	 * 
	 * @param errorRecord
	 *         更新するレコード
	 * @param message
	 *         設定するメッセージ
	 */
	@Update
	public void setMessage(ErrorRecord errorRecord, String message) {
		errorRecord.setMessageAsString(message);
	}
}

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

ExampleJob.java(Flow DSL):

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

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

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

		// エラー情報への変換
		Restructure<ErrorRecord> restructure = core.restructure(this.in, ErrorRecord.class);

		// メッセージを設定する
		SetMessage setMessage = operators.setMessage(restructure.out, "Hoge2エラー");

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

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


ExampleOperatorTest.java:

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

		ErrorRecord errorRecord = new ErrorRecord();
		String message = "Hoge2エラー";

		operator.setMessage(errorRecord, message);

		assertThat(errorRecord.getMessageAsString(), is(message));
	}

類似

更新演算子は、SQLのUPDATEに似ている。

UPDATE error_record
SET message = 'メッセージ';

ただ、AsakusaFWでは入力データから出力データを作り出すという仕組み(同一テーブルへの更新ではない)なので、SELECT-INSERTの方が近いかもしれない。

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

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

  case class Hoge(value: Int)

  def edit(hoge: Hoge) = hoge.copy(value = 100)

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

SQLのUPDATE文のイメージで言うなら、foreachメソッドになるか。

  case class ErrorMessage(code: String, var message: String)

  def setMessage(record: ErrorRecord, message: String) : Unit = {
    record.message = message
  }

  val list : List[ErrorRecord] = 〜
  list.foreach(setMessage(_, "メッセージ"))

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