========================= 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コミュニティサイト`_ の `バッチ設計と実装ガイド`_ を参照してください。 .. _`Asakusa Frameworkコミュニティサイト` : https://www.asakusafw.com/ .. _`バッチ設計と実装ガイド` : https://www.asakusafw.com/techinfo/methodology.html 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`_ についても、テスト時には単体でコンパイルして実行を行えるようになっています。 .. _dsl-userguide-operator-dsl: Operator DSL ============ Operator DSLは「 `演算子`_ 」と呼ばれるデータフロー処理の最小単位を記述するDSLです。 それぞれの演算子では単一のデータを表す「レコード」や、それらをグループ化した「グループ」に対する処理をJavaのプログラムとして記述できます。 演算子 ------ Asakusa DSLにおいて、演算子は「複数のデータセットを入力して加工し、複数のデータセットを出力するもの」といえます。 この演算子を組み合わせて大きなデータフローを構築していくのが、このフレームワークでの標準的な設計開発手法です。 演算子には「種類」というものがあり、それぞれ入出力や処理の内容に制限を与えています。 :doc:`operators` から演算子の種類を選択し、実際の処理内容を決めていきます。 それぞれの演算子は、Askausaが提供するOperator DSLを使って記述します。 DSLといっても難しいものではなく、通常のJavaのメソッド宣言に演算子用のメタデータを指定する形で記述できます。 このように演算子を定義するメソッドをAsakusaでは `演算子メソッド`_ とよび、それらを宣言しているクラスを `演算子クラス`_ と呼んでいます。 演算子クラス ~~~~~~~~~~~~ 演算子クラスは `演算子メソッド`_ を定義するためのJavaのクラスです。 基本的なJavaの *抽象クラス* ですが、以下のような制約があります。 * トップレベルクラスとして宣言する * ``public`` , ``abstract`` として宣言する * 型引数を宣言しない * 明示的な親クラスやインターフェースを指定しない * 明示的なコンストラクタを宣言しない * 演算子メソッド以外の ``public`` メソッドを宣言しない 上記に含まれない、たとえばフィールドの宣言などは自由に行えます。 以下は演算子クラスの例です。 .. code-block:: java package com.example.operator; public abstract class ExampleOperator { ... } .. hint:: Asakusa Framework ``0.10.0`` より、演算子クラスに ``abstract`` を指定するのは必須ではなくなりました。 ただし、演算子メソッドに ``abstract`` を指定するには、クラスにも ``abstract`` を指定する必要があります。 演算子メソッド ~~~~~~~~~~~~~~ 演算子メソッドはそれぞれの演算子を定義するメソッドで、Javaの公開メソッドに演算子注釈と呼ばれる注釈を指定したものです。 * 演算子注釈が一つだけ付与されている * 付与された演算子注釈に適した引数や返戻型が宣言されている * ``public`` として宣言する * ``static`` として宣言 *しない* * 演算子クラス内の他のあらゆる演算子メソッドと名前が衝突しない [#]_ 演算子の一覧や、演算子注釈については :doc:`operators` を参照してください。 以下は、演算子メソッドの例です。 .. code-block:: java public abstract class ExampleOperator { /** * レコードの値に100を設定する。 * @param hoge 更新するレコード */ @Update public void edit(Hoge hoge) { hoge.setValue(100); } ... } .. ** .. [#] この名前衝突の判定はアンダースコア、大文字、小文字を無視します。 .. _dsl-key-annotation: キー注釈 ~~~~~~~~ データモデルのグループ化条件やソート条件を記載するには、演算子の仕様に従って注釈 ``Key`` [#]_ をメソッド引数などに指定します。 この注釈には、それぞれ下記のような要素を記載できます。 .. list-table:: ``@Key`` の要素 :widths: 1 5 2 :header-rows: 1 * - 要素名 - 記載内容 - 例 * - ``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" }`` のようにカンマ区切りで指定します。 .. code-block:: java // 名前でグループ化 @Key(group = "name") // 名前と性別でグループ化 @Key(group = { "name", "sex" }) // 名前でグループ化し、年齢の昇順で整列 @Key(group = "name", order = "+age") // 名前でグループ化し、収入の昇順, 年齢の降順で整列 @Key(group = "name", order = { "+income", "-age" }) // 全てを単一のグループにまとめ、回数の降順で整列 @Key(group = {}, order = "-count") .. seealso:: キーの指定が必要な演算子については、 :doc:`operators` を参照してください。 .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.model.Key` .. note:: Asakusa Framework ``0.10.0`` より前のバージョンでは、 ``order`` での昇順・降順指定は「プロパティ名の *後* に ``ASC`` または ``DESC`` を指定する」という方式でした。 この方式は ``0.10.0`` 以降でも利用可能です。 演算子の多相化 ~~~~~~~~~~~~~~ 演算子メソッドは入出力するデータモデルに、クラス型以外にもインターフェース型を指定できます。 ただし、指定できるインターフェースは射影モデルのみで、演算子メソッドの型引数を宣言してその上限境界に指定します。 .. code-block:: java @Update public void example(T model) { model.setValue(100); } .. seealso:: 演算子の多相化について詳しくは :doc:`generic-dataflow` を参照してください。 また、射影モデルについては :doc:`../dmdl/user-guide` を参照してください。 フレームワークAPI ----------------- フレームワークAPIは、演算子メソッドの中で利用できるAsakusa Frameworkが提供するAPI群です。 これらのAPIはいずれも演算子クラスの外からは **利用できません** 。 .. attention:: 実装上の理由で、現時点のバージョン |version| では :ref:`partial_aggregation` を利用している演算子メソッドから、フレームワークAPIを利用できません。 詳しくは :doc:`operators` を参照してください。 .. note:: Asakusa DSLのうち、Batch DSLとFlow DSLで記述したJavaのプログラムはいずれもDSLの *コンパイル時に* 処理されます。 対して、Operator DSLで記述したプログラムはアプリケーションの実行時に処理されます。 フレームワークAPIはいずれもアプリケーションの実行時のみに有効で、コンパイル時には無効化されています。 上記の理由で、 `Flow DSL`_ や `Batch DSL`_ からこれらのAPIを利用できません。 .. _dsl-context-api: コンテキストAPI ~~~~~~~~~~~~~~~ コンテキストAPIは、バッチ起動時の引数を演算子内で利用するための仕組みを提供します。 バッチ起動時には文字列のキー名と値のペア (バッチ引数) を複数指定でき、コンテキストAPIを利用するとキー名に対応する値を演算子の中から参照できます。 このAPIは ``BatchContext`` [#]_ クラスのメソッドから利用します。 .. list-table:: コンテキストAPIのメソッド :widths: 3 7 :header-rows: 1 * - メソッド名 - 概要 * - ``get`` - 指定したキー名に対応する値を参照する また、バッチ引数以外にもあらかじめ宣言された変数を利用できます。 .. list-table:: あらかじめ宣言された変数 :widths: 2 8 :header-rows: 1 * - 変数名 - 概要 * - ``user`` - 現在のユーザー名。 * - ``batch_id`` - 実行中のバッチID。 同一の `バッチ`_ に対しては常に同じ値になる。 * - ``flow_id`` - 実行中のフローID。 同一の `ジョブフロー`_ に対しては常に同じ値になる。 * - ``execution_id`` - 現在の `ジョブフロー`_ に対する実行ID。 同一のバッチIDやフローIDに対しても、ジョブフローの実行のたびに変化する。 同一ジョブフローの実行中は必ず同じ値で、トランザクションを識別するために利用できる。 .. [#] :asakusafw-javadoc:`com.asakusafw.runtime.core.BatchContext` .. _dsl-report-api: レポートAPI ~~~~~~~~~~~ レポートAPIは、バッチ実行時に発生したエラーや警告などをレポートする仕組みを提供します。 標準的な実装では、レポートは実行エンジンのログ機構にリダイレクトされます。 このAPIは ``Report`` [#]_ のクラスメソッドから利用します。 .. list-table:: レポートAPIのメソッド :widths: 3 7 :header-rows: 1 * - メソッド名 - 概要 * - ``error`` - 「エラー」レベルのレポート * - ``warn`` - 「警告」レベルのレポート * - ``info`` - 「情報」レベルのレポート 致命的な状況に対するレポートの仕組みも用意していますが、このレポートによって処理の流れに影響が出ることはありません。 エラーによって処理を強制終了させたい場合などでは、ランタイム例外を演算子メソッドからスローするなどの方法が必要です。 .. [#] :asakusafw-javadoc:`com.asakusafw.runtime.core.Report` .. attention:: 特定のデータに対してレポートのみを行い、その結果を最終的に出力しない場合、コンパイラの最適化によって演算子の処理が省略されてしまう場合があります。 上記のような演算子メソッドには、最適化を抑止する注釈 ``Sticky`` を併せて指定してください。 .. hint:: ロギング演算子の利用も検討してください。 この演算子は内部的にこのレポートAPIを利用し、自動的に省略の最適化を抑止しています。 .. _dsl-userguide-operator-dsl-compiler: ユーティリティAPI ----------------- ユーティリティAPIは、Asakusa Frameworkが提供するユーティリティAPI群です。 演算子メソッド内での利用を想定しています。 オブジェクトの共有 ~~~~~~~~~~~~~~~~~~ 演算子メソッドは入力レコードやグループごとにフレームワークから呼び出され実行されますが、ある演算子メソッドの呼び出し時に生成したオブジェクトをキャッシュして他の呼び出し時に利用することで、効率的な処理を記述することが可能な場合があります。 ``Shared`` [#]_ クラスは演算子で利用する共有オブジェクトの管理機能を提供します。 利用方法はJavadocを参照してください。 .. [#] :asakusafw-javadoc:`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上に表示させています。 .. _`注釈プロセッサ`: https://www.jcp.org/en/jsr/detail?id=269 演算子実装クラス ~~~~~~~~~~~~~~~~ 演算子実装クラスは、 `演算子クラス`_ を継承した実装クラスです。 演算子クラスは抽象クラス ( ``abstract class`` ) として宣言し、いくつかの演算子メソッドは本体を持たない抽象メソッドとして宣言していました。 演算子クラスそのものは抽象クラスのためインスタンスを生成できず、実際に利用できないため、演算子実装クラスは具象クラスとして生成されます。 また、抽象メソッドとして宣言した演算子メソッドに対して、オーバーライドした具象メソッドを生成します。 演算子実装クラスは、もとの演算子クラスの末尾に ``Impl`` をつけた名前で生成されます。 演算子メソッドに対する単体テストを行いたい場合には、生成された演算子実装クラスをインスタンス化して行うことを推奨しています。 .. caution:: ここで生成される具象メソッドは、実行時に利用されないダミーの実装である場合があります。 また、生成される実装はコンパイラのバージョンが変わった際に内容が変更される場合もあります。 それらの演算子メソッドに対する単体テストは行うべきではありません。 .. _dsl-userguide-operator-factory: 演算子ファクトリ ~~~~~~~~~~~~~~~~ 演算子ファクトリは、 `演算子クラス`_ に宣言された演算子をFlow DSLから利用できるようにするためのクラスです。 このクラスには、次の2つの要素が宣言されます。 演算子オブジェクトクラス Flow DSLでは、データフロー上の演算子を表すために「演算子オブジェクト」というものを利用します。 これは、演算子のデータフロー内での接続状態を表し、さらにその演算子の出力を表す「ポート」をフィールドとして保持しています。 演算子オブジェクトクラスはこのオブジェクトの元になるクラスで、演算子ファクトリの内部クラスとして宣言されます。 演算子ファクトリメソッド 上記の演算子オブジェクトを生成するファクトリメソッドです。 このメソッドは、演算子への入力を表す「ポート」を引数にとります。 演算子ファクトリクラスは、もとの演算子クラスの末尾に ``Factory`` をつけた名前で生成されます。 また、演算子ファクトリメソッドはもとの演算子メソッドと同じ名前で、演算子オブジェクトクラスはもとの演算子メソッドをJavaのクラス名の規約に変換した名前 [#]_ がつけられます。 `演算子の多相化`_ を行っている場合、対応する演算子オブジェクトクラスとファクトリメソッドにはそれぞれもとの演算子メソッドで宣言した型引数が自動的に宣言されます。 .. [#] メソッド名の最初の文字を大文字に変換します フロー演算子 ~~~~~~~~~~~~ Operator DSLコンパイラは、 `フロー部品`_ に対する演算子 (フロー演算子) も生成します。 フロー部品には「 `演算子実装クラス`_ 」が不要であるため、「 `演算子ファクトリ`_ 」のみを生成します。 通常の演算子ファクトリとは次のような相違があります。 * 演算子ファクトリメソッド名は常に ``create`` * 演算子オブジェクトクラス名はフロー部品の名前と同じ .. seealso:: フロー演算子については :doc:`operators` を参照してください。 演算子とスレッド安全性 ~~~~~~~~~~~~~~~~~~~~~~ バッチアプリケーションの実行時において、演算子(実装)クラスのインスタンスは次の規則で生成しています。 * 同一のバッチ内の異なる箇所から演算子メソッドを起動する場合、そのインスタンスは必ず起動元ごとに異なる * 異なるスレッドから演算子メソッドを起動する場合、そのインスタンスは必ずスレッドごとに異なる つまり、バッチアプリケーションがある演算子メソッドを起動してから終了するまで、その演算子メソッドが属するインスタンスはそのメソッドのみが占有している状態になります。そのため、演算子クラスに宣言したインスタンスフィールドの状態は、演算子メソッドごとに独立したものになります。 なお、演算子クラスにクラス( ``static`` )フィールドを宣言した場合、そのフィールドは複数のスレッド・演算子メソッドから参照される場合があるため、スレッド安全性に注意が必要です。 なお、以下はスレッドや演算子メソッドをまたいで共有される場合があります。 * :doc:`ビュー ` オブジェクト ( ``View`` , ``GroupView`` ) とその内容 * 以下の演算子の「マスタデータ」に関するオブジェクト * :ref:`master-check-operator` * :ref:`master-branch-operator` * :ref:`master-join-update-operator` * :ref:`master-selection-support-operator` 上記に該当するオブジェクトは変更すべきではありません。 .. _dsl-userguide-flow-dsl: 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というデータソースを提供しています。 詳しくは :doc:`../directio/index` , :doc:`../windgate/index` をそれぞれ参照してください。 .. caution:: インポータ記述の中で定義するメソッドは、 `Batch DSLコンパイラ`_ の *コンパイル中に* 起動されます。 そのため、 `フレームワークAPI`_ はこの中では利用できません。 .. hint:: インポータ記述の多くは ``getDataSize()`` というメソッドを共通して持っています。 このメソッドを上書きし、適切なデータサイズを指定することで、コンパイラはそれをヒントに最適化を行います。 .. note:: インポータ記述はいずれも ``ImporterDescription`` [#]_ インターフェースの実装クラスとなります。 ただし、このインターフェースだけを実装してもデータソースを利用することはできません。 これらは、 `Operator DSLコンパイラ`_ のコンパイラプラグインを追加することで、新しいデータソースを利用できるようになります。 .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.external.ImporterDescription` エクスポータ記述 ~~~~~~~~~~~~~~~~ エクスポータ記述はジョブフローの結果を出力する先となるデータソースを記述するクラスです。 データソースごとに指定されたクラスを継承して、必要な情報を記載します。 Asakusa Frameworkは標準でDirect I/OやWindGateというデータソースを提供しています。 詳しくは :doc:`../directio/index` , :doc:`../windgate/index` をそれぞれ参照してください。 .. caution:: エクスポータ記述の中で定義するメソッドは、 `Batch DSLコンパイラ`_ の *コンパイル中に* 起動されます。 そのため、 `フレームワークAPI`_ はこの中では利用できません。 .. note:: エクスポータ記述はいずれも ``ExporterDescription`` [#]_ インターフェースの実装クラスとなります。 インポータ記述と同様に、このインターフェースだけを実装してもデータソースを利用することはできません。 .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.external.ExporterDescription` ジョブフロークラス ~~~~~~~~~~~~~~~~~~ それぞれのジョブフローは、データフローのベースクラスである ``FlowDescription`` [#]_ を継承したJavaのクラスとして宣言します。 このクラスには以下のような制約があります。 * ``public`` として宣言されている * ``abstract`` として宣言されて *いない* * ``FlowDescription`` を継承する * 注釈 ``JobFlow`` [#]_ を付与する * 型引数を宣言していない * 明示的なコンストラクターを一つだけ宣言する また、注釈 ``JobFlow`` の要素 ``name`` にこのバッチの名前を指定します。 ここで指定する名前は、 Javaの変数名のうち、ASCIIコード表に収まるもののみでなければなりません。 以下はジョブフロークラスの例です。 .. code-block:: java package com.example.business.jobflow; import com.asakusafw.vocabulary.flow.*; @JobFlow(name = "stock") public class StockJob extends FlowDescription { } .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.flow.FlowDescription` .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.flow.JobFlow` ジョブフローコンストラクタ ~~~~~~~~~~~~~~~~~~~~~~~~~~ ジョブフローの入出力は、ジョブフロークラスのコンストラクタで宣言します。 これには次のような制約があります。 * ``public`` として宣言されている * 型引数を宣言していない * ``In`` [#]_ 型の仮引数を一つ以上宣言し、それぞれ型引数にデータモデル型を指定する * ``Out`` [#]_ 型の仮引数を一つ以上宣言し、それぞれ型引数にデータモデル型を指定する * ``In`` , ``Out`` 以外の仮引数を宣言しない それぞれの ``In`` 型の引数は、ジョブフローへの1つ分の入力を表しています。 この仮引数には、注釈 ``Import`` [#]_ を付与し、要素 ``name`` に入力の名前を、要素 ``description`` に `インポータ記述`_ のクラスリテラルを指定します。 ここで指定したインポート処理の結果が、この入力を通して利用できます。 同様に、それぞれの ``Out`` 型の引数は、ジョブフローからの1つ分の出力を表しています。 この仮引数には、注釈 ``Export`` [#]_ を付与し、要素 ``name`` に出力の名前を、要素 ``description`` に `エクスポータ記述`_ のクラスリテラルを指定します。 この出力に対するジョブフローの結果が、エクスポート処理で書きだされます。 それぞれに指定する ``Import`` や ``Export`` にはそれぞれ次のような制約があります。 * 要素 ``name`` にはJavaの変数名のうち、ASCIIコード表に収まるもののみ指定できる * それぞれの要素 ``name`` に指定する文字列が重複しない * 要素 ``description`` に指定した記述と、型引数のデータモデルの型が一致する .. hint:: ``name`` が重複してはいけない範囲は、それぞれの ``Import`` と ``Export`` の中のみです。 ``Import`` と ``Export`` の組み合わせで重複しても構いません。 以下はジョブフローコンストラクタの例です。 .. code-block:: java In shipmentIn; In stockIn; Out shipmentOut; Out stockOut; /** * コンストラクタ。 * @param shipmentIn 処理対象の注文情報 * @param stockIn 処理対象の在庫情報 * @param shipmentOut 処理結果の注文情報 * @param stockOut 処理結果の在庫情報 */ public StockJob( @Import(name = "shipment", description = ShipmentFromDb.class) In shipmentIn, @Import(name = "stock", description = StockFromDb.class) In stockIn, @Export(name = "shipment", description = ShipmentToDb.class) Out shipmentOut, @Export(name = "stock", description = StockToDb.class) Out stockOut) { this.shipmentIn = shipmentIn; this.stockIn = stockIn; this.shipmentOut = shipmentOut; this.stockOut = stockOut; } .. ** .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.flow.In` .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.flow.Out` .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.flow.Import` .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.flow.Export` フロー記述メソッド ~~~~~~~~~~~~~~~~~~ データフローでの処理内容は、 ``FlowDescription`` クラスの ``describe`` メソッドをオーバーライドして記述します。 ここでは、コンストラクタで受け取った入出力と、 `Operator DSL`_ で記述した演算子を組み合わせてデータ処理の流れを記述します。 作成した演算子を利用するには、その演算子クラスに対応する `演算子ファクトリ`_ を経由します。 また、「コア演算子」という組み込みの演算子ファクトリも用意されています。 コア演算子については :doc:`operators` を参照してください。 以下は、フロー記述メソッドの例です。 .. code-block:: java In shipmentIn; In stockIn; Out shipmentOut; Out 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コンパイラ`_ を利用すると、フロー部品に対応するフロー演算子を自動的に生成します。 値引数を利用できる フロー部品には入出力以外に任意の引数を指定できます。 一部の値のみが異なる複数のデータフローをフロー部品として抽出すると、データフローの再利用性が高まります。 型引数を利用できる フロー部品は :doc:`generic-dataflow` に対応しています。 データフロー内で利用するデータモデルの種類を型引数として宣言でき、内部では多相化した演算子を利用できます。 .. note:: フロー部品はデータフローの構造化と再利用を意識して導入した仕組みです。 またフロー部品は単体テストの単位ともなるので、意味のある単位で構成することでデータフローのテストが容易になります。 フロー部品クラス ~~~~~~~~~~~~~~~~ それぞれのフロー部品は、 `ジョブフロー`_ と同様に ``FlowDescription`` [#]_ を継承したJavaのクラスとして宣言します。 このクラスには以下のような制約があります。 * ``public`` として宣言されている * ``abstract`` として宣言されていない * ``FlowDescription`` を継承する * 注釈 ``FlowPart`` [#]_ を付与する * 明示的なコンストラクターを一つだけ宣言する .. hint:: フロー部品クラスはジョブフロークラスと異なり、型引数の宣言が可能です。 詳しくは :doc:`generic-dataflow` を参照してください。 以下はフロー部品クラスの例です。 .. code-block:: java package com.example.business.flowpart; import com.asakusafw.vocabulary.flow.*; @FlowPart public class StockPart extends FlowDescription { } .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.flow.FlowDescription` .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.flow.FlowPart` フロー部品コンストラクタ ~~~~~~~~~~~~~~~~~~~~~~~~ フロー部品の入出力は、ジョブフローと同様にコンストラクタで宣言します。 これには次のような制約があります。 * ``public`` として宣言されている * 型引数を宣言していない * ``In`` [#]_ 型の仮引数を一つ以上宣言し、それぞれ型引数にデータモデル型または型変数を指定する * ``Out`` [#]_ 型の仮引数を一つ以上宣言し、それぞれ型引数にデータモデル型または型変数を指定する それぞれの ``In`` 型の引数は、フロー部品への1つ分の入力を表しています。 同様に、それぞれの ``Out`` 型の引数は、フロー部品からの1つ分の出力を表しています。 .. hint:: フロー部品のコンストラクタには、入出力以外にも任意の引数を利用できます。 以下はフロー部品コンストラクタの例です。 .. code-block:: java In shipmentIn; In stockIn; Out shipmentOut; Out stockOut; /** * コンストラクタ。 * @param shipmentIn 処理対象の注文情報 * @param stockIn 処理対象の在庫情報 * @param shipmentOut 処理結果の注文情報 * @param stockOut 処理結果の在庫情報 */ public StockPart( In shipmentIn, In stockIn, Out shipmentOut, Out stockOut) { this.shipmentIn = shipmentIn; this.stockIn = stockIn; this.shipmentOut = shipmentOut; this.stockOut = stockOut; } .. ** .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.flow.In` .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.flow.Out` フロー部品のフロー記述 ~~~~~~~~~~~~~~~~~~~~~~ フロー部品のフロー記述は、ジョブフローと同様です。 `フロー記述メソッド`_ を参照してください。 データフローのコンパイル ------------------------ Asakusa Frameworkでは、通常Flow DSLのプログラムを直接コンパイルしません。 これらはバッチに含めた状態でコンパイルされます。 詳しくは `Batch DSLコンパイラ`_ を参照してください。 なお、フロー部品を `Operator DSLコンパイラ`_ に掛けると「フロー演算子」を作成します。 これはジョブフローやフロー部品に、他のフロー部品を組み込むための演算子です。 フロー演算子については、 :doc:`operators` を参照してください。 .. _dsl-userguide-batch-dsl: Batch DSL ========= Batch DSLはデータフローを組み合わせて複雑なバッチ処理の流れを記述するDSLです。 それぞれのデータフローを処理する順序を、依存関係のグラフ構造で記述できます。 バッチ ------ バッチはBatch DSLに出現する唯一の要素で、「エンドユーザーから見たバッチ処理の単位」を表すことを想定しています。 `ジョブフロー`_ は外部システムからの入力を取り込んで、処理結果を出力するまでの一連の流れを表しています。 バッチはそれらをさらに組み合わせて、意味のある一連の処理を記述できます。 Batch DSLで記述する内容は、主に「ジョブフローの実行順序」です。 それぞれのジョブフローの実行順序を、ジョブフロー間の依存関係を元に記述します。 依存関係のあるジョブフローは、手前のジョブフローの処理が完了するまでブロックされ、それらがすべて終了したのちにジョブフローの処理が開始されます。 .. note:: Batch DSLではデータフロー以外の処理を連携できるようにする計画があります。 たとえば、外部システムからデータを取り込むようなスクリプトを後続のデータフロー処理に先立って起動するなどです。 バッチクラス ~~~~~~~~~~~~ それぞれのバッチは、バッチクラスのベースクラスである ``BatchDescription`` [#]_ を継承したJavaのクラスとして宣言します。 このクラスには以下のような制約があります。 * ``public`` として宣言されている * ``abstract`` として宣言されて *いない* * ``BatchDescription`` を継承する * 注釈 ``Batch`` [#]_ を付与する * 型引数を宣言していない * 明示的なコンストラクタを宣言しない また、注釈 ``Batch`` の要素 ``name`` にこのバッチの名前を指定します。 ここで指定する名前は、 Javaのパッケージ名のうち、ASCIIコード表に収まるもののみでなければなりません。 以下はバッチクラスを作成する例です。 .. code-block:: java package com.example.batch; import com.asakusafw.vocabulary.batch.*; @Batch(name = "example") public class ExampleBatch extends BatchDescription { } .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.batch.BatchDescription` .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.batch.Batch` バッチ注釈 ~~~~~~~~~~ バッチクラスに指定した注釈 ``@Batch`` には、 ``name`` 以外にも様々な属性を指定できます。 .. list-table:: ``@Batch`` の属性 :widths: 2 3 2 8 :header-rows: 1 * - 属性名 - 型 - 既定値 - 概要 * - ``name`` - 文字列 - なし - バッチの名前 (Batch ID) * - ``comment`` - 文字列 - ``""`` (空) - バッチのコメント * - ``parameters`` - ``Parameter[]`` の配列 - ``{}`` (空) - 利用可能なバッチ引数の一覧 (形式は後述) [#]_ * - ``strict`` - ``boolean`` - ``false`` - ``true`` を指定した場合に ``parameters`` に指定した引数以外を利用できなくなる 上記のうち ``parameters`` を指定すると、このバッチで利用可能なバッチ引数の詳細を指定できます。 さらに ``strict`` に ``true`` を指定すると、 ``parameters`` 以外のバッチ引数を指定できなくなります。 この ``parameters`` では注釈 ``@Parameter`` [#]_ を利用して個々のバッチ引数を指定します。 .. list-table:: ``@Parameter`` の属性 :widths: 2 2 2 8 :header-rows: 1 * - 属性名 - 型 - 既定値 - 概要 * - ``key`` - 文字列 - なし - バッチ引数のキー * - ``comment`` - 文字列 - ``""`` (空) - バッチ引数のコメント * - ``required`` - ``boolean`` - ``true`` - ``true`` ならば必須引数、 ``false`` ならば省略可能 * - ``pattern`` - 文字列 - ``".*"`` (すべて) - バッチ引数の値に指定可能な文字列を表す正規表現 上記のうち、 ``pattern`` には ``java.util.regex.Pattern`` 形式の正規表現を指定できます。 この ``pattern`` が省略された場合には、バッチ引数の値に全ての文字列を利用できます。 .. note:: :doc:`../cli/index` が提供する :program:`asakusa run` コマンドは、バッチ注釈を利用してバッチ引数のチェックを行います。 以下は、 ``@Batch`` を記述するサンプルです。 .. code-block:: java 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 { } .. [#] バッチ引数については `コンテキストAPI`_ も参照してください。 .. [#] :asakusafw-javadoc:`com.asakusafw.vocabulary.batch.Batch.Parameter` バッチ記述メソッド ~~~~~~~~~~~~~~~~~~ バッチの内容は、 ``BatchDescription`` クラスの ``describe`` メソッドをオーバーライドして記述します。 このメソッドの中には、ジョブフローの依存関係を記述してバッチ全体を構築するようなプログラムを書きます。 以下はバッチメソッドを記述する例です。 .. code-block:: java @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コンパイラは、バッチクラスから次のものを生成します。 * 外部入出力を行うための設定情報など * データフロー処理を行うプログラム群 * 上記の一連の流れを規定するワークフロー記述 .. _compiled-batch-application-components: Batch DSLコンパイラが生成するバッチアプリケーション ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Batch DSLコンパイラが生成するバッチアプリケーションには以下のものが含まれます。 外部入出力を行うための設定情報 Batch DSLコンパイラはコンパイル対象のバッチアプリケーションのジョブフロー記述の情報などから、Direct I/OやWindGateがデータの入出力を行うための設定情報を生成します。 この設定情報はバッチアプリケーション実行時にDirect I/OやWindGateが参照し、その設定内容に応じて入出力データを決定したり、入出力時に行われる制御(排他制御など)を行います。 データフロー処理を行うプログラム群 Batch DSLコンパイラはバッチアプリケーションに含まれるOperator DSLやFlow DSLの内容から、実行プラットフォーム向けのプログラム群を生成します。 Batch DSLコンパイラは実行プラットフォームごとに対応するコンパイラを提供します。 各コンパイラはコンパイル時の動作や生成プログラムの内容に関する設定を行うための「コンパイルオプション」を利用可能です。 各コンパイラによって利用できるコンパイルオプションは異なります。 各コンパイラのコンパイルオプションについては、以下のドキュメントを参照してください。 * :doc:`../spark/reference` - :ref:`spark-dsl-compiler-reference` * :doc:`../m3bp/reference` - :ref:`m3bp-dsl-compiler-reference` * :doc:`../mapreduce/reference` - :ref:`mapreduce-dsl-compiler-reference` ワークフロー記述 ワークフロー記述は、コンパイルされたバッチを実行する際に入出力や実行プラットフォーム向けのプログラムの実行順序を記述したものです。 これはワークフローエンジンごとに生成される記述で、対応するコンパイラプラグインをコンパイル時に指定します。 標準では、Batch DSLコンパイラはYAESSというジョブ実行ツールのためのワークフロー記述である「YAESSスクリプト」を生成します。 YAESSについては :doc:`../yaess/index` を参照してください。 モジュールの取り込み ~~~~~~~~~~~~~~~~~~~~ 開発環境の構成によっては、バッチアプリケーションを構成するモジュールを分割して管理したいケースがあります。例えば、以下のような場合です。 * 複数のプロジェクトでデータモデルの定義を共有する * 複数のプロジェクトでビジネスロジックを共有する * 外部入出力を含むジョブフローとそれ以外の部分を分離する * 一部の単体テストケースを分離して管理する このような場合、アプリケーションプロジェクトを分割し、それぞれのモジュールを個別に生成、管理します。 あるアプリケーションプロジェクトからモジュールの取り込みを行いたい場合、取り込まれる側のクラスライブラリ内に :file:`META-INF/asakusa/fragment` というファイル (以下、マーカーファイル) を含めた上で、コンパイラのクラスパスに上記クラスライブラリを追加してください。 .. attention:: マーカーファイルを含むクラスライブラリを取り込む際、同じパスのファイルが複数含められていると正しく動作しません。 .. hint:: マーカーファイルによる取り込みは :doc:`テストドライバー <../testing/index>` を利用する際にも有効です。 この場合、テストドライバーを起動した際のクラスパスに含められたクラスライブラリから、マーカーファイルを検索します。 なお、テストドライバーを実行する際に、起点となるジョブフローやバッチを含むクラスライブラリは自動的に取り込まれます。