Asakusa DSLユーザーガイド

この文書では、Asakusa DSLおよびDSLコンパイラの利用方法について紹介します。

DSLの概要

Asakusa DSLは次の3種類のDSLで構成されています。

Operator DSL
Javaでデータ操作の処理を「演算子」として記述する
Flow DSL
演算子を組み合わせて「データフロー」を記述する
Batch DSL
データフローを組み合わせて「業務バッチ」を記述する

アプリケーションの設計と実装

Asakusa DSLは、データフロー形式でのバッチ処理設計から素直な実装を作成できることを目標に設計しています。

データフローは大雑把にいえば、入力データをプロセスで加工して出力するという流れを取ります。 Flow DSL ではそのようなデータフローを直接記述でき、データを加工するプロセスは Operator DSL で通常のJavaを使って手続き的に記述できます。 そのため、設計の段階からデータフローや処理の単位を意識することで、その設計内容をインプットとしてAsakusa DSLを用いた実装をスムーズに行うことができるようになります。

Asakusa Frameworkを利用したバッチ処理の設計例については、 Asakusa Frameworkコミュニティサイトバッチ設計と実装ガイド を参照してください。

DSLコンパイラ

Asakusa DSLで記述したプログラムは、Asakusa Frameworkに付属のAsakusa DSLコンパイラを使ってHadoop(MapReduce)やSparkなどの各プラットフォーム(以下「実行プラットフォーム」)で実行可能なプログラムに変換します。

このコンパイラは2種類のコンパイラから構成されています。

Operator DSLコンパイラ
Operator DSL をコンパイルして、 Flow DSL で使える部品を生成するコンパイラ。 Javaの注釈プロセッサとして提供している。
Batch DSLコンパイラ

Batch DSL をコンパイルして、実行プラットフォーム向けのプログラムを生成するコンパイラ。 コマンドラインインターフェースを提供している。

Batch DSLコンパイラは実行プラットフォームごとに対応するコンパイラを提供します。

なお、DSLが3層であるのに対し、コンパイラは2層のみに対応しています。 直接のコンパイルを行わない Flow DSL についても、テスト時には単体でコンパイルして実行を行えるようになっています。

Operator DSL

Operator DSLは「 演算子 」と呼ばれるデータフロー処理の最小単位を記述するDSLです。 それぞれの演算子では単一のデータを表す「レコード」や、それらをグループ化した「グループ」に対する処理をJavaのプログラムとして記述できます。

演算子

Asakusa DSLにおいて、演算子は「複数のデータセットを入力して加工し、複数のデータセットを出力するもの」といえます。 この演算子を組み合わせて大きなデータフローを構築していくのが、このフレームワークでの標準的な設計開発手法です。

演算子には「種類」というものがあり、それぞれ入出力や処理の内容に制限を与えています。 演算子リファレンス から演算子の種類を選択し、実際の処理内容を決めていきます。

それぞれの演算子は、Askausaが提供するOperator DSLを使って記述します。 DSLといっても難しいものではなく、通常のJavaのメソッド宣言に演算子用のメタデータを指定する形で記述できます。

このように演算子を定義するメソッドをAsakusaでは 演算子メソッド とよび、それらを宣言しているクラスを 演算子クラス と呼んでいます。

演算子クラス

演算子クラスは 演算子メソッド を定義するためのJavaのクラスです。 基本的なJavaの 抽象クラス ですが、以下のような制約があります。

  • トップレベルクラスとして宣言する
  • public , abstract として宣言する
  • 型引数を宣言しない
  • 明示的な親クラスやインターフェースを指定しない
  • 明示的なコンストラクタを宣言しない
  • 演算子メソッド以外の public メソッドを宣言しない

上記に含まれない、たとえばフィールドの宣言などは自由に行えます。

以下は演算子クラスの例です。

package com.example.operator;

public abstract class ExampleOperator {
    ...
}

Hint

Asakusa Framework 0.10.0 より、演算子クラスに abstract を指定するのは必須ではなくなりました。 ただし、演算子メソッドに abstract を指定するには、クラスにも abstract を指定する必要があります。

演算子メソッド

演算子メソッドはそれぞれの演算子を定義するメソッドで、Javaの公開メソッドに演算子注釈と呼ばれる注釈を指定したものです。

  • 演算子注釈が一つだけ付与されている
  • 付与された演算子注釈に適した引数や返戻型が宣言されている
  • public として宣言する
  • static として宣言 しない
  • 演算子クラス内の他のあらゆる演算子メソッドと名前が衝突しない [1]

演算子の一覧や、演算子注釈については 演算子リファレンス を参照してください。

以下は、演算子メソッドの例です。

public abstract class ExampleOperator {

    /**
     * レコードの値に100を設定する。
     * @param hoge 更新するレコード
     */
    @Update
    public void edit(Hoge hoge) {
        hoge.setValue(100);
    }
    ...
}
[1]この名前衝突の判定はアンダースコア、大文字、小文字を無視します。

キー注釈

データモデルのグループ化条件やソート条件を記載するには、演算子の仕様に従って注釈 Key [2] をメソッド引数などに指定します。 この注釈には、それぞれ下記のような要素を記載できます。

@Key の要素
要素名 記載内容
group グループ化に利用するプロパティ名の一覧。 これらのフィールドが全て同じものでグループを構成する。 空の配列を指定すると全てを単一のグループにまとめる。 group = "name"
order 順序付けに利用するプロパティ名と、順序の一覧。 プロパティを一つも指定しない場合、順序は実装依存となる。 フィールド名の前に + (昇順)または - (降順)を指定する。 +, - を省略した場合、昇順に整列する。 order = "+age"

それぞれに指定するプロパティ名は、下記のいずれの形式も利用できます。

snake_case
すべての語を小文字で指定し、 _ (アンダースコア)で区切る。 DMDLの名前と同じ形式 (推奨)。
UPPER_CASE
すべての語を大文字で指定し、 _ (アンダースコア)で区切る。 データベースのカラム名でよく利用される形式。
camelCase (Lower Camel Case)
単語の先頭のみを大文字で指定し、先頭の単語だけすべて小文字で指定する。 Javaのフィールド名等の標準規約と同じ形式。
PascalCase (Upper Camel Case)
単語の先頭のみを大文字で指定する。 Javaのクラス名等の標準規約と同じ形式。

Note

このプロパティの命名規約により、利用可能なプロパティ名にいくつかの制限が設けられます。 具体的には、 HTMLString のよう形式のプロパティ名が期待した名前にならない、 value_0 のように単語の先頭がアルファベットでないものを正しく認識できない、などが挙げられます。

単一の演算子の中に複数の @Key を指定する場合には、次のことに注意して下さい。

  • それぞれのキーに出現する group の項目は、同じ個数でなければならない
  • group の各項目は、それぞれのキーにおいて以下のように計算を行う
    • 同じ位置のそれぞれの項目で等価比較を行う
    • 同じ位置のそれぞれの項目は、完全に同じ型でなければならない
  • order の項目については上記のような制約はない

それぞれの要素に複数の条件を指定するには、プロパティ名や順序を group = { "a", "b", "c" } のようにカンマ区切りで指定します。

// 名前でグループ化
@Key(group = "name")

// 名前と性別でグループ化
@Key(group = { "name", "sex" })

// 名前でグループ化し、年齢の昇順で整列
@Key(group = "name", order = "+age")

// 名前でグループ化し、収入の昇順, 年齢の降順で整列
@Key(group = "name", order = { "+income", "-age" })

// 全てを単一のグループにまとめ、回数の降順で整列
@Key(group = {}, order = "-count")

See also

キーの指定が必要な演算子については、 演算子リファレンス を参照してください。

[2]com.asakusafw.vocabulary.model.Key

Note

Asakusa Framework 0.10.0 より前のバージョンでは、 order での昇順・降順指定は「プロパティ名の ASC または DESC を指定する」という方式でした。 この方式は 0.10.0 以降でも利用可能です。

演算子の多相化

演算子メソッドは入出力するデータモデルに、クラス型以外にもインターフェース型を指定できます。 ただし、指定できるインターフェースは射影モデルのみで、演算子メソッドの型引数を宣言してその上限境界に指定します。

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

See also

演算子の多相化について詳しくは 多相データフロー を参照してください。 また、射影モデルについては DMDLユーザーガイド を参照してください。

フレームワークAPI

フレームワークAPIは、演算子メソッドの中で利用できるAsakusa Frameworkが提供するAPI群です。 これらのAPIはいずれも演算子クラスの外からは 利用できません

Attention

実装上の理由で、現時点のバージョン 0.10.2 では 部分集約 を利用している演算子メソッドから、フレームワークAPIを利用できません。 詳しくは 演算子リファレンス を参照してください。

Note

Asakusa DSLのうち、Batch DSLとFlow DSLで記述したJavaのプログラムはいずれもDSLの コンパイル時に 処理されます。 対して、Operator DSLで記述したプログラムはアプリケーションの実行時に処理されます。 フレームワークAPIはいずれもアプリケーションの実行時のみに有効で、コンパイル時には無効化されています。 上記の理由で、 Flow DSLBatch DSL からこれらのAPIを利用できません。

コンテキストAPI

コンテキストAPIは、バッチ起動時の引数を演算子内で利用するための仕組みを提供します。 バッチ起動時には文字列のキー名と値のペア (バッチ引数) を複数指定でき、コンテキストAPIを利用するとキー名に対応する値を演算子の中から参照できます。

このAPIは BatchContext [3] クラスのメソッドから利用します。

コンテキストAPIのメソッド
メソッド名 概要
get 指定したキー名に対応する値を参照する

また、バッチ引数以外にもあらかじめ宣言された変数を利用できます。

あらかじめ宣言された変数
変数名 概要
user 現在のユーザー名。
batch_id 実行中のバッチID。 同一の バッチ に対しては常に同じ値になる。
flow_id 実行中のフローID。 同一の ジョブフロー に対しては常に同じ値になる。
execution_id 現在の ジョブフロー に対する実行ID。 同一のバッチIDやフローIDに対しても、ジョブフローの実行のたびに変化する。 同一ジョブフローの実行中は必ず同じ値で、トランザクションを識別するために利用できる。
[3]com.asakusafw.runtime.core.BatchContext

レポートAPI

レポートAPIは、バッチ実行時に発生したエラーや警告などをレポートする仕組みを提供します。 標準的な実装では、レポートは実行エンジンのログ機構にリダイレクトされます。

このAPIは Report [4] のクラスメソッドから利用します。

レポートAPIのメソッド
メソッド名 概要
error 「エラー」レベルのレポート
warn 「警告」レベルのレポート
info 「情報」レベルのレポート

致命的な状況に対するレポートの仕組みも用意していますが、このレポートによって処理の流れに影響が出ることはありません。 エラーによって処理を強制終了させたい場合などでは、ランタイム例外を演算子メソッドからスローするなどの方法が必要です。

[4]com.asakusafw.runtime.core.Report

Attention

特定のデータに対してレポートのみを行い、その結果を最終的に出力しない場合、コンパイラの最適化によって演算子の処理が省略されてしまう場合があります。 上記のような演算子メソッドには、最適化を抑止する注釈 Sticky を併せて指定してください。

Hint

ロギング演算子の利用も検討してください。 この演算子は内部的にこのレポートAPIを利用し、自動的に省略の最適化を抑止しています。

ユーティリティAPI

ユーティリティAPIは、Asakusa Frameworkが提供するユーティリティAPI群です。 演算子メソッド内での利用を想定しています。

オブジェクトの共有

演算子メソッドは入力レコードやグループごとにフレームワークから呼び出され実行されますが、ある演算子メソッドの呼び出し時に生成したオブジェクトをキャッシュして他の呼び出し時に利用することで、効率的な処理を記述することが可能な場合があります。

Shared [5] クラスは演算子で利用する共有オブジェクトの管理機能を提供します。 利用方法はJavadocを参照してください。

[5]com.asakusafw.runtime.core.util.Shared

Operator DSLコンパイラ

Operator DSLコンパイラは作成した 演算子クラス をコンパイルして実行時に必要なクラスや Flow DSL に必要なクラスを生成します。

このコンパイラは、Javaの 注釈プロセッサ の仕組みの上に構築しています。 そのため、Operator DSLコンパイラとそれの依存ライブラリを javac コマンドのクラスパスに指定することで、自動的にOperator DSLコンパイラが起動します。

Attention

Operator DSLコンパイラは、後続のアプリケーション開発で必要なクラスを自動生成します。 プロジェクトをクリーンビルドする際には、必要なクラスが一時的に足りない状態であるため、コンパイル順序によっては「クラスが見つからない」等の警告メッセージが表示されることがあります。 しかし、javacには「ラウンド」という概念があり、現在の処理のラウンドでクラスが見つからなくても、コンパイル中に新しく生成されたソースプログラムを含めて次のラウンドでさらにコンパイルを実行します。 これによってコンパイルエラーにはなりませんが、他の場所でコンパイルエラーが発生した際に、上記に関するエラーメッセージが余計に表示されてしまう場合があるようです。

Note

Operator DSLコンパイラに注釈プロセッサの仕組みを採用した理由は、主にIDEとの親和性です。 注釈プロセッサはJavaコンパイラの一部のようにふるまうため、注釈プロセッサ内で発生したエラーをコンパイルエラーのようにIDE上に表示させています。

演算子実装クラス

演算子実装クラスは、 演算子クラス を継承した実装クラスです。 演算子クラスは抽象クラス ( abstract class ) として宣言し、いくつかの演算子メソッドは本体を持たない抽象メソッドとして宣言していました。

演算子クラスそのものは抽象クラスのためインスタンスを生成できず、実際に利用できないため、演算子実装クラスは具象クラスとして生成されます。 また、抽象メソッドとして宣言した演算子メソッドに対して、オーバーライドした具象メソッドを生成します。

演算子実装クラスは、もとの演算子クラスの末尾に Impl をつけた名前で生成されます。 演算子メソッドに対する単体テストを行いたい場合には、生成された演算子実装クラスをインスタンス化して行うことを推奨しています。

Caution

ここで生成される具象メソッドは、実行時に利用されないダミーの実装である場合があります。 また、生成される実装はコンパイラのバージョンが変わった際に内容が変更される場合もあります。 それらの演算子メソッドに対する単体テストは行うべきではありません。

演算子ファクトリ

演算子ファクトリは、 演算子クラス に宣言された演算子をFlow DSLから利用できるようにするためのクラスです。 このクラスには、次の2つの要素が宣言されます。

演算子オブジェクトクラス
Flow DSLでは、データフロー上の演算子を表すために「演算子オブジェクト」というものを利用します。 これは、演算子のデータフロー内での接続状態を表し、さらにその演算子の出力を表す「ポート」をフィールドとして保持しています。 演算子オブジェクトクラスはこのオブジェクトの元になるクラスで、演算子ファクトリの内部クラスとして宣言されます。
演算子ファクトリメソッド
上記の演算子オブジェクトを生成するファクトリメソッドです。 このメソッドは、演算子への入力を表す「ポート」を引数にとります。

演算子ファクトリクラスは、もとの演算子クラスの末尾に Factory をつけた名前で生成されます。 また、演算子ファクトリメソッドはもとの演算子メソッドと同じ名前で、演算子オブジェクトクラスはもとの演算子メソッドをJavaのクラス名の規約に変換した名前 [6] がつけられます。

演算子の多相化 を行っている場合、対応する演算子オブジェクトクラスとファクトリメソッドにはそれぞれもとの演算子メソッドで宣言した型引数が自動的に宣言されます。

[6]メソッド名の最初の文字を大文字に変換します

フロー演算子

Operator DSLコンパイラは、 フロー部品 に対する演算子 (フロー演算子) も生成します。 フロー部品には「 演算子実装クラス 」が不要であるため、「 演算子ファクトリ 」のみを生成します。 通常の演算子ファクトリとは次のような相違があります。

  • 演算子ファクトリメソッド名は常に create
  • 演算子オブジェクトクラス名はフロー部品の名前と同じ

See also

フロー演算子については 演算子リファレンス を参照してください。

演算子とスレッド安全性

バッチアプリケーションの実行時において、演算子(実装)クラスのインスタンスは次の規則で生成しています。

  • 同一のバッチ内の異なる箇所から演算子メソッドを起動する場合、そのインスタンスは必ず起動元ごとに異なる
  • 異なるスレッドから演算子メソッドを起動する場合、そのインスタンスは必ずスレッドごとに異なる

つまり、バッチアプリケーションがある演算子メソッドを起動してから終了するまで、その演算子メソッドが属するインスタンスはそのメソッドのみが占有している状態になります。そのため、演算子クラスに宣言したインスタンスフィールドの状態は、演算子メソッドごとに独立したものになります。

なお、演算子クラスにクラス( static )フィールドを宣言した場合、そのフィールドは複数のスレッド・演算子メソッドから参照される場合があるため、スレッド安全性に注意が必要です。

なお、以下はスレッドや演算子メソッドをまたいで共有される場合があります。

上記に該当するオブジェクトは変更すべきではありません。

Flow DSL

Flow DSLは演算子を組み合わせてデータフローの構造を記述するDSLです。 このDSLではデータフローの構造を非循環有向グラフ (Directed Acyclic Graph: DAG)を構造の通りにそのまま記述できます。

Flow DSLで記述できる構造は2種類あり、それぞれ異なる性質を持ちます。

ジョブフロー
外部システムからデータを取り出して、外部システムにデータを書き出すデータフロー。 データフローの入出力にはそれぞれ インポータ記述エクスポータ記述 を付与して外部と連携する方法を記述する。
フロー部品
データフローそのものを演算子として定義する。 ここで記述したデータフローは、Flow DSLで演算子として利用できる。

いずれの構造においても、Flow DSLではデータフローの入出力と演算子の入出力をつなぎ合わせて、データ処理の流れを表します。

ジョブフロー

ジョブフローはFlow DSLのトップレベルの要素で、外部システムからデータを読み出し、データを加工して、外部システムにデータを書き戻すという一連のデータ処理を記述できます。

外部システムとの連携は インポータ記述エクスポータ記述 でそれぞれ入出力方法を記述します。 また、外部入出力と Operator DSL で作成した演算子の入出力を フロー記述メソッド 内で組み合わせて、データフローの構造を記述します。

インポータ記述

インポータ記述はジョブフローの入力もととなるデータソースを記述するクラスです。 データソースごとに指定されたクラスを継承して、必要な情報を記載します。

Asakusa Frameworkは標準でDirect I/OやWindGateというデータソースを提供しています。 詳しくは データの直接入出力 - Direct I/O , 外部システムとの連携 - WindGate をそれぞれ参照してください。

Caution

インポータ記述の中で定義するメソッドは、 Batch DSLコンパイラコンパイル中に 起動されます。 そのため、 フレームワークAPI はこの中では利用できません。

Hint

インポータ記述の多くは getDataSize() というメソッドを共通して持っています。 このメソッドを上書きし、適切なデータサイズを指定することで、コンパイラはそれをヒントに最適化を行います。

Note

インポータ記述はいずれも ImporterDescription [7] インターフェースの実装クラスとなります。 ただし、このインターフェースだけを実装してもデータソースを利用することはできません。 これらは、 Operator DSLコンパイラ のコンパイラプラグインを追加することで、新しいデータソースを利用できるようになります。

[7]com.asakusafw.vocabulary.external.ImporterDescription

エクスポータ記述

エクスポータ記述はジョブフローの結果を出力する先となるデータソースを記述するクラスです。 データソースごとに指定されたクラスを継承して、必要な情報を記載します。

Asakusa Frameworkは標準でDirect I/OやWindGateというデータソースを提供しています。 詳しくは データの直接入出力 - Direct I/O , 外部システムとの連携 - WindGate をそれぞれ参照してください。

Caution

エクスポータ記述の中で定義するメソッドは、 Batch DSLコンパイラコンパイル中に 起動されます。 そのため、 フレームワークAPI はこの中では利用できません。

Note

エクスポータ記述はいずれも ExporterDescription [8] インターフェースの実装クラスとなります。 インポータ記述と同様に、このインターフェースだけを実装してもデータソースを利用することはできません。

[8]com.asakusafw.vocabulary.external.ExporterDescription

ジョブフロークラス

それぞれのジョブフローは、データフローのベースクラスである FlowDescription [9] を継承したJavaのクラスとして宣言します。 このクラスには以下のような制約があります。

  • public として宣言されている
  • abstract として宣言されて いない
  • FlowDescription を継承する
  • 注釈 JobFlow [10] を付与する
  • 型引数を宣言していない
  • 明示的なコンストラクターを一つだけ宣言する

また、注釈 JobFlow の要素 name にこのバッチの名前を指定します。 ここで指定する名前は、 Javaの変数名のうち、ASCIIコード表に収まるもののみでなければなりません。

以下はジョブフロークラスの例です。

package com.example.business.jobflow;

import com.asakusafw.vocabulary.flow.*;

@JobFlow(name = "stock")
public class StockJob extends FlowDescription {

}
[9]com.asakusafw.vocabulary.flow.FlowDescription
[10]com.asakusafw.vocabulary.flow.JobFlow

ジョブフローコンストラクタ

ジョブフローの入出力は、ジョブフロークラスのコンストラクタで宣言します。 これには次のような制約があります。

  • public として宣言されている
  • 型引数を宣言していない
  • In [11] 型の仮引数を一つ以上宣言し、それぞれ型引数にデータモデル型を指定する
  • Out [12] 型の仮引数を一つ以上宣言し、それぞれ型引数にデータモデル型を指定する
  • In , Out 以外の仮引数を宣言しない

それぞれの In 型の引数は、ジョブフローへの1つ分の入力を表しています。 この仮引数には、注釈 Import [13] を付与し、要素 name に入力の名前を、要素 descriptionインポータ記述 のクラスリテラルを指定します。 ここで指定したインポート処理の結果が、この入力を通して利用できます。

同様に、それぞれの Out 型の引数は、ジョブフローからの1つ分の出力を表しています。 この仮引数には、注釈 Export [14] を付与し、要素 name に出力の名前を、要素 descriptionエクスポータ記述 のクラスリテラルを指定します。 この出力に対するジョブフローの結果が、エクスポート処理で書きだされます。

それぞれに指定する ImportExport にはそれぞれ次のような制約があります。

  • 要素 name にはJavaの変数名のうち、ASCIIコード表に収まるもののみ指定できる
  • それぞれの要素 name に指定する文字列が重複しない
  • 要素 description に指定した記述と、型引数のデータモデルの型が一致する

Hint

name が重複してはいけない範囲は、それぞれの ImportExport の中のみです。 ImportExport の組み合わせで重複しても構いません。

以下はジョブフローコンストラクタの例です。

In<Shipment> shipmentIn;
In<Stock> stockIn;
Out<Shipment> shipmentOut;
Out<Stock> stockOut;

/**
 * コンストラクタ。
 * @param shipmentIn 処理対象の注文情報
 * @param stockIn 処理対象の在庫情報
 * @param shipmentOut 処理結果の注文情報
 * @param stockOut 処理結果の在庫情報
 */
public StockJob(
        @Import(name = "shipment", description = ShipmentFromDb.class)
        In<Shipment> shipmentIn,
        @Import(name = "stock", description = StockFromDb.class)
        In<Stock> stockIn,
        @Export(name = "shipment", description = ShipmentToDb.class)
        Out<Shipment> shipmentOut,
        @Export(name = "stock", description = StockToDb.class)
        Out<Stock> stockOut) {
    this.shipmentIn = shipmentIn;
    this.stockIn = stockIn;
    this.shipmentOut = shipmentOut;
    this.stockOut = stockOut;
}
[11]com.asakusafw.vocabulary.flow.In
[12]com.asakusafw.vocabulary.flow.Out
[13]com.asakusafw.vocabulary.flow.Import
[14]com.asakusafw.vocabulary.flow.Export

フロー記述メソッド

データフローでの処理内容は、 FlowDescription クラスの describe メソッドをオーバーライドして記述します。 ここでは、コンストラクタで受け取った入出力と、 Operator DSL で記述した演算子を組み合わせてデータ処理の流れを記述します。

作成した演算子を利用するには、その演算子クラスに対応する 演算子ファクトリ を経由します。 また、「コア演算子」という組み込みの演算子ファクトリも用意されています。 コア演算子については 演算子リファレンス を参照してください。

以下は、フロー記述メソッドの例です。

In<Shipment> shipmentIn;
In<Stock> stockIn;
Out<Shipment> shipmentOut;
Out<Stock> stockOut;

@Override
protected void describe() {
    CoreOperatorFactory core = new CoreOperatorFactory();
    StockOpFactory op = new StockOpFactory();

    // 処理できない注文をあらかじめフィルタリング
    CheckShipment check = op.checkShipment(shipmentIn);
    core.stop(check.notShipmentped);
    core.stop(check.completed);

    // 在庫引当を行う
    Cutoff cutoff = op.cutoff(stockIn, check.costUnknown);

    // 結果を書き出す
    shipmentOut.add(cutoff.newShipments);
    stockOut.add(cutoff.newStocks);
}

Caution

フロー記述メソッドは、 Batch DSLコンパイラコンパイル中に 起動されます。 そのため、 フレームワークAPI はこの中では利用できません。

Note

フロー記述メソッドの記述は、主にデータフローの設計書を意識しています。 設計書に記載されたデータフローの構造のうち、プロセスを演算子に置き換え、「この演算子の入力は、どこのデータを使えばいいか」ということを意識しながら演算子を配置していくことで、目的のデータフローを記述できます。 ただし、グラフ構造をテキストで記述するとやはり読みにくくなってしまうため、テキスト以外の記述方法も検討しています。

フロー部品

フロー部品は名前のとおり「データフローの部品」を定義する構造です。 ここで定義したデータフローは、ほかのデータフローから「フロー演算子」とよばれる演算子として利用できます。 フロー部品の中にフロー演算子を含めることもでき、複雑なデータフローを階層化して取り扱えます。

ジョブフローに対して、フロー部品は次のような特徴があります。

外部入出力を定義しない
フロー部品単体では外部入出力を定義できず、かならずいずれかのジョブフローの中で利用されることになります。 このため、ジョブフローで指定したインポートやエクスポートの指定は不要です。
フロー演算子を自動生成する
Operator DSLコンパイラ を利用すると、フロー部品に対応するフロー演算子を自動的に生成します。
値引数を利用できる
フロー部品には入出力以外に任意の引数を指定できます。 一部の値のみが異なる複数のデータフローをフロー部品として抽出すると、データフローの再利用性が高まります。
型引数を利用できる
フロー部品は 多相データフロー に対応しています。 データフロー内で利用するデータモデルの種類を型引数として宣言でき、内部では多相化した演算子を利用できます。

Note

フロー部品はデータフローの構造化と再利用を意識して導入した仕組みです。 またフロー部品は単体テストの単位ともなるので、意味のある単位で構成することでデータフローのテストが容易になります。

フロー部品クラス

それぞれのフロー部品は、 ジョブフロー と同様に FlowDescription [15] を継承したJavaのクラスとして宣言します。 このクラスには以下のような制約があります。

  • public として宣言されている
  • abstract として宣言されていない
  • FlowDescription を継承する
  • 注釈 FlowPart [16] を付与する
  • 明示的なコンストラクターを一つだけ宣言する

Hint

フロー部品クラスはジョブフロークラスと異なり、型引数の宣言が可能です。 詳しくは 多相データフロー を参照してください。

以下はフロー部品クラスの例です。

package com.example.business.flowpart;

import com.asakusafw.vocabulary.flow.*;

@FlowPart
public class StockPart extends FlowDescription {

}
[15]com.asakusafw.vocabulary.flow.FlowDescription
[16]com.asakusafw.vocabulary.flow.FlowPart

フロー部品コンストラクタ

フロー部品の入出力は、ジョブフローと同様にコンストラクタで宣言します。 これには次のような制約があります。

  • public として宣言されている
  • 型引数を宣言していない
  • In [17] 型の仮引数を一つ以上宣言し、それぞれ型引数にデータモデル型または型変数を指定する
  • Out [18] 型の仮引数を一つ以上宣言し、それぞれ型引数にデータモデル型または型変数を指定する

それぞれの In 型の引数は、フロー部品への1つ分の入力を表しています。 同様に、それぞれの Out 型の引数は、フロー部品からの1つ分の出力を表しています。

Hint

フロー部品のコンストラクタには、入出力以外にも任意の引数を利用できます。

以下はフロー部品コンストラクタの例です。

In<Shipment> shipmentIn;
In<Stock> stockIn;
Out<Shipment> shipmentOut;
Out<Stock> stockOut;

/**
 * コンストラクタ。
 * @param shipmentIn 処理対象の注文情報
 * @param stockIn 処理対象の在庫情報
 * @param shipmentOut 処理結果の注文情報
 * @param stockOut 処理結果の在庫情報
 */
public StockPart(
        In<Shipment> shipmentIn,
        In<Stock> stockIn,
        Out<Shipment> shipmentOut,
        Out<Stock> stockOut) {
    this.shipmentIn = shipmentIn;
    this.stockIn = stockIn;
    this.shipmentOut = shipmentOut;
    this.stockOut = stockOut;
}
[17]com.asakusafw.vocabulary.flow.In
[18]com.asakusafw.vocabulary.flow.Out

フロー部品のフロー記述

フロー部品のフロー記述は、ジョブフローと同様です。 フロー記述メソッド を参照してください。

データフローのコンパイル

Asakusa Frameworkでは、通常Flow DSLのプログラムを直接コンパイルしません。 これらはバッチに含めた状態でコンパイルされます。 詳しくは Batch DSLコンパイラ を参照してください。

なお、フロー部品を Operator DSLコンパイラ に掛けると「フロー演算子」を作成します。 これはジョブフローやフロー部品に、他のフロー部品を組み込むための演算子です。 フロー演算子については、 演算子リファレンス を参照してください。

Batch DSL

Batch DSLはデータフローを組み合わせて複雑なバッチ処理の流れを記述するDSLです。 それぞれのデータフローを処理する順序を、依存関係のグラフ構造で記述できます。

バッチ

バッチはBatch DSLに出現する唯一の要素で、「エンドユーザーから見たバッチ処理の単位」を表すことを想定しています。 ジョブフロー は外部システムからの入力を取り込んで、処理結果を出力するまでの一連の流れを表しています。 バッチはそれらをさらに組み合わせて、意味のある一連の処理を記述できます。

Batch DSLで記述する内容は、主に「ジョブフローの実行順序」です。 それぞれのジョブフローの実行順序を、ジョブフロー間の依存関係を元に記述します。 依存関係のあるジョブフローは、手前のジョブフローの処理が完了するまでブロックされ、それらがすべて終了したのちにジョブフローの処理が開始されます。

Note

Batch DSLではデータフロー以外の処理を連携できるようにする計画があります。 たとえば、外部システムからデータを取り込むようなスクリプトを後続のデータフロー処理に先立って起動するなどです。

バッチクラス

それぞれのバッチは、バッチクラスのベースクラスである BatchDescription [19] を継承したJavaのクラスとして宣言します。 このクラスには以下のような制約があります。

  • public として宣言されている
  • abstract として宣言されて いない
  • BatchDescription を継承する
  • 注釈 Batch [20] を付与する
  • 型引数を宣言していない
  • 明示的なコンストラクタを宣言しない

また、注釈 Batch の要素 name にこのバッチの名前を指定します。 ここで指定する名前は、 Javaのパッケージ名のうち、ASCIIコード表に収まるもののみでなければなりません。

以下はバッチクラスを作成する例です。

package com.example.batch;

import com.asakusafw.vocabulary.batch.*;

@Batch(name = "example")
public class ExampleBatch extends BatchDescription {

}
[19]com.asakusafw.vocabulary.batch.BatchDescription
[20]com.asakusafw.vocabulary.batch.Batch

バッチ注釈

バッチクラスに指定した注釈 @Batch には、 name 以外にも様々な属性を指定できます。

@Batch の属性
属性名 既定値 概要
name 文字列 なし バッチの名前 (Batch ID)
comment 文字列 "" (空) バッチのコメント
parameters Parameter[] の配列 {} (空) 利用可能なバッチ引数の一覧 (形式は後述) [21]
strict boolean false true を指定した場合に parameters に指定した引数以外を利用できなくなる

上記のうち parameters を指定すると、このバッチで利用可能なバッチ引数の詳細を指定できます。 さらに stricttrue を指定すると、 parameters 以外のバッチ引数を指定できなくなります。

この parameters では注釈 @Parameter [22] を利用して個々のバッチ引数を指定します。

@Parameter の属性
属性名 既定値 概要
key 文字列 なし バッチ引数のキー
comment 文字列 "" (空) バッチ引数のコメント
required boolean true true ならば必須引数、 false ならば省略可能
pattern 文字列 ".*" (すべて) バッチ引数の値に指定可能な文字列を表す正規表現

上記のうち、 pattern には java.util.regex.Pattern 形式の正規表現を指定できます。 この pattern が省略された場合には、バッチ引数の値に全ての文字列を利用できます。

Note

コマンドラインインターフェース - Asakusa CLI が提供する asakusa run コマンドは、バッチ注釈を利用してバッチ引数のチェックを行います。

以下は、 @Batch を記述するサンプルです。

package com.example.batch;

import com.asakusafw.vocabulary.batch.*;
import com.asakusafw.vocabulary.batch.Batch.*;

@Batch(
    name = "com.example",
    comment = "サンプル用のバッチ",
    parameters = {
        @Parameter(key = "date", comment = "業務日付", pattern = "\\d{4}-\\d{2}-\\d{2}"),
        @Parameter(key = "memo", comment = "実行メモ", required = false)
    },
    strict = true
)
public class ExampleBatch extends BatchDescription {

}
[21]バッチ引数については コンテキストAPI も参照してください。
[22]com.asakusafw.vocabulary.batch.Batch.Parameter

バッチ記述メソッド

バッチの内容は、 BatchDescription クラスの describe メソッドをオーバーライドして記述します。 このメソッドの中には、ジョブフローの依存関係を記述してバッチ全体を構築するようなプログラムを書きます。 以下はバッチメソッドを記述する例です。

@Override
protected void describe() {
    Work first = run(FirstFlow.class).soon();
    Work second = run(SecondFlow.class).after(first);
    Work para = run(ParallelFlow.class).after(first);
    Work join = run(JoinFlow.class).after(second, para);
    ...
}

バッチの内部で実行するジョブフローは、 BatchDescription クラスから継承した run() メソッドで指定します。 同メソッドには対象のジョブフロークラスのクラスリテラルを指定し、そのままメソッドチェインで soon()after() メソッドを起動します。

soon メソッドはバッチの内部で最初に実行されるジョブフローを表し、 after メソッドは依存関係にある処理を引数に指定して、それらの処理が全て完了後に実行されるジョブフローを表します。

Caution

バッチ記述メソッドは、 Batch DSLコンパイラコンパイル中に 起動されます。 そのため、 フレームワークAPI はこの中では利用できません。

Batch DSLコンパイラ

Batch DSLコンパイラは、バッチクラスから次のものを生成します。

  • 外部入出力を行うための設定情報など
  • データフロー処理を行うプログラム群
  • 上記の一連の流れを規定するワークフロー記述

Batch DSLコンパイラが生成するバッチアプリケーション

Batch DSLコンパイラが生成するバッチアプリケーションには以下のものが含まれます。

外部入出力を行うための設定情報

Batch DSLコンパイラはコンパイル対象のバッチアプリケーションのジョブフロー記述の情報などから、Direct I/OやWindGateがデータの入出力を行うための設定情報を生成します。

この設定情報はバッチアプリケーション実行時にDirect I/OやWindGateが参照し、その設定内容に応じて入出力データを決定したり、入出力時に行われる制御(排他制御など)を行います。

データフロー処理を行うプログラム群

Batch DSLコンパイラはバッチアプリケーションに含まれるOperator DSLやFlow DSLの内容から、実行プラットフォーム向けのプログラム群を生成します。

Batch DSLコンパイラは実行プラットフォームごとに対応するコンパイラを提供します。 各コンパイラはコンパイル時の動作や生成プログラムの内容に関する設定を行うための「コンパイルオプション」を利用可能です。

各コンパイラによって利用できるコンパイルオプションは異なります。 各コンパイラのコンパイルオプションについては、以下のドキュメントを参照してください。

ワークフロー記述

ワークフロー記述は、コンパイルされたバッチを実行する際に入出力や実行プラットフォーム向けのプログラムの実行順序を記述したものです。 これはワークフローエンジンごとに生成される記述で、対応するコンパイラプラグインをコンパイル時に指定します。

標準では、Batch DSLコンパイラはYAESSというジョブ実行ツールのためのワークフロー記述である「YAESSスクリプト」を生成します。

YAESSについては アプリケーションの実行 - YAESS を参照してください。

モジュールの取り込み

開発環境の構成によっては、バッチアプリケーションを構成するモジュールを分割して管理したいケースがあります。例えば、以下のような場合です。

  • 複数のプロジェクトでデータモデルの定義を共有する
  • 複数のプロジェクトでビジネスロジックを共有する
  • 外部入出力を含むジョブフローとそれ以外の部分を分離する
  • 一部の単体テストケースを分離して管理する

このような場合、アプリケーションプロジェクトを分割し、それぞれのモジュールを個別に生成、管理します。

あるアプリケーションプロジェクトからモジュールの取り込みを行いたい場合、取り込まれる側のクラスライブラリ内に META-INF/asakusa/fragment というファイル (以下、マーカーファイル) を含めた上で、コンパイラのクラスパスに上記クラスライブラリを追加してください。

Attention

マーカーファイルを含むクラスライブラリを取り込む際、同じパスのファイルが複数含められていると正しく動作しません。

Hint

マーカーファイルによる取り込みは テストドライバー を利用する際にも有効です。 この場合、テストドライバーを起動した際のクラスパスに含められたクラスライブラリから、マーカーファイルを検索します。

なお、テストドライバーを実行する際に、起点となるジョブフローやバッチを含むクラスライブラリは自動的に取り込まれます。