多相データフロー

概要

多相データフローはAsakusa DSLの拡張機能です。多相データフローにより、複数のデータモデルに対する共通の処理をまとめて定義できるようになります。

複数のデータモデルに対して共通の処理を実装するような場合、データモデルの種類ごとに個別のデータフローや演算子を定義するかわりに多相データフローを利用することで効率的に実装を行うことが出来ます。

射影モデル

多相データフローでは具体的なデータモデルを取り扱うのではなく、「射影モデル」というデータモデルの一部を投影した汎用的なデータ構造を取り扱います。

射影モデルは各データモデルが共通して持つプロパティの一覧を抽出した部分的なデータ構造を表し、次のような特徴を持っています。

  • 射影モデルのプロパティは、抽出元の同じプロパティと常に同じ値を持つ
  • 射影モデルのプロパティを変更すると、抽出元の同じプロパティも同様に変更される

このような射影モデルに対するデータフローや演算子を定義することで、同じ射影モデルを抽出できる複数のデータモデルに対して汎用的な処理を記述できます。

この文書では以後、通常のデータモデルを単に「データモデル」とよび、データモデルの射影を表すモデルを「射影モデル」と呼ぶことにします。 また、データモデルの一部を射影モデルとして取り出すことを、データモデルを射影モデルに「投影する」と呼ぶことにします。

Asakusa DSLでの射影モデル

Asakusa DSLでは、データモデルをJavaのクラスで表し、射影モデルをJavaのインターフェースで表します。 また、それぞれのデータモデルの実体(instance)は、データモデルに対応するJavaのクラスをインスタンス化したオブジェクトとして表します。

データモデルと射影モデルの関係は、Javaの継承関係で表します。 つまり、データモデルをある射影モデルに投影できる場合、データモデルに対応するクラスは、その射影モデルに対応するインターフェースを実装(implements)することになります。

あるデータモデルの実体を射影モデルの実体に投影する操作は、対応するオブジェクトの型を、データモデルの型から射影モデルの型に拡大変換することで表します。 つまり、Asakusa DSLにおいて、射影モデルの実体は投影元のデータモデルの実体と一致します。

射影モデルを定義する方法は、 DMDLユーザーガイド を参照して下さい。

Attention

このような実装のため、Asakusa DSLでデータモデルを射影モデルに投影するには、あらかじめデータモデルが対象の射影モデルを実装している必要があります。 継承関係にないデータモデルと射影モデルは、たとえ両者のプロパティの構造が同じであっても投影できません。

Operator DSLの多相化

多相データフローの導入により、Operator DSLで定義する演算子が射影モデルを取り扱えるようになります。

射影モデルを取り扱う演算子を、以後「多相演算子」と呼びます。

多相演算子

多相演算子は、従来の演算子メソッドの宣言をJavaの総称メソッドとして宣言します。

総称メソッドの型引数には、射影モデルを表す型を上限境界に指定して、その型引数を演算子メソッドの引数に利用します。

@Update
public <T extends PModel>
void example(T model) {
     model.setValue(100);
}

型引数をとる演算子メソッドは、Operator DSLコンパイラによって多相演算子として取り扱われます。

上記の演算子は、 PModel という射影モデルのインターフェースを実装したすべてのデータモデルに対して利用できます。

多相演算子の制限

多相演算子には次のような制限があります。

  • 入出力の引数型に射影モデルを直接指定できない。必ず型変数の上限境界に指定する必要がある
  • 出力に型変数を利用する場合、入力の型から一意に推論できるデータモデル型である必要がある

上記の制約により、入力と出力の型に直接の関係が無いような演算子については、 出力に射影モデルを指定できません。これは、以下のような演算子が該当します。

  • 変換演算子
  • マスタ結合演算子
  • 単純集計演算子

合成射影

データモデルを複数の種類の射影モデルに投影することもできます。

これら複数の射影モデルを合成した射影を利用する場合、型引数の上限境界に複数の射影モデルを同時に指定します。

@Update
public <T extends A & B>
void example(T model) {
     model.setA(100);
     model.setB(200);
}

このとき、型変数 T は「 AB のプロパティをすべて利用できる型」として合成した射影モデルのように取り扱えます。 また、この演算子に利用できるデータモデルは、 AB のどちらにも投影可能でなければなりません。

キー項目の指定

型変数を利用した射影モデルの引数に、キー項目の情報を表す @Key 注釈を指定する場合、そこに指定できるプロパティは必ず射影に含まれていなければなりません。

以下は、 @Key 注釈を指定する例です。 プロパティ keyPKey または PValue のいずれかに宣言されている必要があります。

@MasterJoinUpdate
public <P extends PKey & PValue>
void branchProjection(
          @Key(group = "key") P mst,
          @Key(group = "key") TxData tx) {
     tx.setValue(mst.getValue());
}

上記のマスタつき更新演算子は、マスタの入力に PKeyPValue をどちらも実装したデータモデルを指定できます。

Flow DSLの多相化

多相データフローの導入により、Flow DSLでは多相演算子を利用できるようになります。 さらに、フロー部品そのものを多相演算子として利用できるようにもなります。

演算子オブジェクトのパラメータ化

Operator DSLで多相演算子を作成した場合、Flow DSLでもその演算子オブジェクトに型引数を指定します。 この型引数には、それぞれ射影モデルが上限境界として設定されていて、実際のデータモデルの型を型引数に指定することで、「そのデータモデルに対する演算子」として利用できるようになります。

例として、データモデルの型 DModel と、そこから投影できる射影モデル PModel について考えます。

次のような PModel に対する更新演算子をOperator DSLで定義したとします。

@Update
public <P extends PModel>
void updateProjection(P model) {
     model.setProjectiveMember(100);
}

Flow DSLでは、上記の演算子に対して DModel 型の入力を利用できます。

In<DModel> in;
Out<DModel> out;
...
UpdateProjection<DModel> op = factory.updateProjection(in);
out.add(op.out);

なお、更新演算子は入力と出力に同じ型をとるような演算子です。 このため、 DModel を入力にとる更新演算子の出力は、同じように DModel となります。

Note

入力のみを多相化する演算子では、型変数の定義は本来不要です。 これは、型変数が出力の型文脈を表現するためにのみ利用されるため、出力が多相化されていない限り、演算子オブジェクトが型引数を伴う必要は本来ありません。

今回は規則の簡単化のため、そのような場合においても演算子オブジェクトは演算子メソッドで宣言された全ての型引数を引き継ぐものとします。

フロー部品の多相化

Flow DSLで定義されたフロー部品は、他のフローから演算子として利用できます。 フロー部品そのものを多相化する場合には、多相演算子とほとんど同様の方法でフロー部品クラスを総称化します。

多相化したフロー部品は、コンストラクタの引数に型引数を利用できます。 これにより、データフローの一部を操作対象の射影モデルに対して定義できるようになります。 なお、このように多相化されたフロー部品を「多相フロー部品」とよびます。

例として、これまでと同様にデータモデルの型 DModel と、そこから投影できる射影モデル PModel について考えます。 多相フロー部品を定義するには、フロー部品クラスそのものを総称クラスとして宣言し、型引数の上限境界に射影モデルを指定します。

@FlowPart
public class FlowProjection<P extends PModel> extends FlowDescription {
     In<P> in;
     Out<P> out;
     public FlowProjection(In<P> in, Out<P> out) {
          ...
     }
     ...
}

上記のフロー部品の内部では、型変数 P を利用してデータフローを記述できます。 これはつまり、 PModel を利用する多相演算子をこのデータフローから利用できます。

たとえば、以下のような多相化された更新演算子があるとします。

@Update
public <P extends PModel>
void updateProjection(P model) {
     model.setProjectiveMember(100);
}

フロー部品の describe メソッドでは、上記の多相演算子を次のように利用できます。

In<P> in;
Out<P> out;
...
@Override protected void describe() {
     UpdateProjection<P> op = factory.updateProjection(in);
     out.add(op.out); // UpdateProjection<T>.out : Source<T>
}

なお、多相フロー部品は他のデータフローから多相演算子として利用できます。

In<DModel> in;
Out<DModel> out;
...
FlowProjection<DModel> op = factory.create(in);
out.add(op.out); // FlowProjection<T>.out : Source<T>

多相フロー部品の出力型

フロー部品では、入力または java.lang.Class 型の引数のいずれかで指定した型変数を出力でも利用できます [1]

たとえば、次のようなフロー部品を記述できます。

@FlowPart
public class GenericWithClass<
        A extends Hoge,
        B extends Foo> extends FlowDescription {

    private In<A> in;
    private Out<B> out;
    private Class<B> type;

    public GenericWithClass(In<A> in, Out<B> out, Class<B> type) {
        this.in = in;
        this.out = out;
        this.type = type;
    }

    @Override
    protected void describe() {
        CoreOperatorFactory c = new CoreOperatorFactory();
        out.add(c.project(in, type));
    }
}

上記の出力 out は型変数 B を利用しています。 通常の多相演算子ではこの型変数 B は入力にも利用されていなければなりませんが、多相フロー部品の場合には代わりに Class<B> type で利用しているためエラーとなりません。

Note

java.lang.Class を利用した多相フロー部品は、拡張演算子や射影演算子との連携を考えて設計されています。 これらの演算子については 演算子リファレンス を参照してください。

[1]通常の多相演算子での制約については、 多相演算子の制限 を参照してください。