https://docs.spring.io/spring-batch/4.1.x/reference/html/domain.html#domainLanguageOfBatch
https://qiita.com/kagamihoge/items/12fbbc2eac5b8a5ac1e0 俺の訳一覧リスト
1. The Domain Language of Batch
何らかの経験のあるバッチアーキテクトにとって、Spring Batchのバッチ処理のコンセプトはお馴染みのものです。"Jobs"、"Steps"、開発者が用意する処理単位のItemReader
とItemWriter
があります。ただ、Springのパターン, operations, テンプレート、コールバック、イディオムを使えるので、以下も可能です。
- 関心事の分離による品質向上。
- インターフェースによるサービスとレイヤーの明確化。
- シンプルなデフォルト実装により、そのままの設定ですぐ動かせる。
- 拡張性の大幅な機能強化。
以下の図はここ数十年使われ続けてきたバッチのリファレンスアーキテクチャを単純化したものです。バッチ処理のドメイン言語を構成するコンポーネントの概略を示しています。このアーキテクチャフレームワークの設計はここ数世代の複数プラットフォーム(COBOL/Mainframe, C/Unix, and now Java/anywhere)の実装を通して実証されてきました。JCLとCOBOLの開発者は、C, C#, Java開発者と同様に、このコンセプトに慣れていると思われます。Spring Batchは、広範なバッチアプリケーション開発に使われるメンテナンス性がありロバストなシステムで、広く見られるサービス・コンポーネント・レイヤの実装を提供します。また、非常に複雑な要求を扱うための拡張とインフラも備えています。
Figure 1. Batch Stereotypes
上図はSpring Batchのドメイン言語を構成する中核コンセプトを表しています。Jobは一つか複数のstepを持ち、各stepはItemReader
, ItemProcessor
, ItemWriter
を一つ持ちます。jobは(JobLauncher
)で実行し、現在実行中のプロセスのメタデータは(JobRepository
)に保存されます。
1.1. Job
このセクションではbatch jobのコンセプトに関する解説をします。Job
はバッチ処理全体をカプセル化するエンティティです。他のSpringプロジェクト同様に、Job
はXMLかJavaのどちらかの設定でワイヤリングします。この設定は"job configuration"と呼ばれる事が多いです。なお、以下の図で示すように、Job
は階層全体のトップに居ます。
Figure 2. Job Hierarchy
Spring Batchでは、Job
はStep
インスタンスのコンテナです。jobは複数のstepを持ち、これらはフロー内の論理的なまとまりを構成し、リスタートなど全てのstepにグローバルなプロパティ設定があります。job設定は以下があります。
- job名。
Step
インスタンスの定義と順序。- jobがリスタート可能かどうか。
Spring BatchのデフォルトJob実装はSimpleJob
クラスで、Job
にいくつかの標準機能を追加しています。Java設定の場合、Job
のインスタンス化に利用可能なビルダーが複数あり、以下はその例です。
@Bean public Job footballJob() { return this.jobBuilderFactory.get("footballJob") .start(playerLoad()) .next(gameLoad()) .next(playerSummarization()) .end() .build(); }
1.1.1. JobInstance
JobInstance
はjob実行に相当します。いま、一日の終わりに一度だけ実行する、前述の図の'EndOfDay' Job
、などのバッチjobを考えます。jobは'EndOfDay'の1つですが、Job
の個々の実行はそれぞれ別個に扱う必要があります。このjobの場合、1日1つのJobInstance
になります。例えば、1/1の実行、1/2の実行、になりま1す。1/1の実行が初回は失敗したが翌日再実行する場合、1/1の実行を再度走らせます(通常、バッチ実行は処理するデータと対応関係があるので、1/1の実行は1/1のデータを処理します)。よって、JobInstance
は複数の実行を持ちうる(JobExecution
はこの賞の後半で解説します)事になり、特定のJob
に対応するJobInstance
は一つだけで、JobParameters
である特定時点のJobInstance
を実行します。
JobInstance
定義はロードされるデータとは完全に無関係です。データのロード方法はItemReader
の実装次第です。たとえば、EndOfDayのケースだと、 'effective date'や'schedule date'を指すデータ列が考えらます。よって、1/1の実行は1/1のデータのみロードし、1/2は1/2のデータのみロードします。これは仕様次第で、ItemReader
で実装します。ただし、同一JobInstance
を使用すると、以前の実行の'state'(ExecutionContext
の事でこの章の後半で解説)かどうかが決まります。新規のJobInstance
は'最初から開始'を意味し、既存インスタンスは基本的には'抜けた場所から開始'を意味します。
1.1.2. JobParameters
JobInstance
とそのJobとの違いを解説したので、"JobInstance
の区別方法は?"という疑問が自然に浮かぶと思われます。答えはJobParameters
です。JobParameters
オブジェクトはバッチjobに使うパラメータを保持します。識別に使われる他、以下の図のように、実行中の参照データとしても使われます。
Figure 3. Job Parameters
先の例では、二つのインスタンスがあり、一つは1/1でもう一つが1/2、Job
1つだけですが、JobParameter
オブジェクトは2つあります。前者はパラメータ01-01-2017で開始しており、後者は01-02-2017で開始しています。よって、定義としてはJobInstance
= Job
+ JobParameters
になります。これにより、開発者はJobInstance
の定義を、渡すパラメータによって、制御します。
※ JobInstance
の識別にすべてのjobパラメータが必要なわけではありません。By default, they do so. ただし、このフレームワークでは、JobInstance
の識別に使用しないパラメータでJob
のサブミットが可能です。
1.1.3. JobExecution
JobExecution
は1回のjob実行に相当します。実行は失敗か成功で終了しますが、ある実行が正常終了しない限り、対応するJobInstance
は完了したとは見なされません。例としてEndOfDayのJob
で解説すると、01-01-2017のJobInstance
が初回実行時に失敗した場合を考えます。初回実行(01-01-2017)と同一のjob parametersで再度実行すると、JobExecution
を新規に作成します。ただし、JobInstance
は1つのままです。
Job
はjobの中身と実行方法を定義し、JobInstance
は実行をグループ化するオブジェクトで、これは主に正しくリスタートするためです。一方で、JobExecution
は、実行中に発生した事を格納する役割があり、制御と永続化されるプロパティがあります。
Table 1. JobExecution Properties
Property | Definition |
---|---|
Status | 実行ステータスを示すBatchStatus 。実行時、BatchStatus#STARTED 。失敗、BatchStatus#FAILED 。正常終了、BatchStatus#COMPLETED |
startTime | 実行開始時の現在システム時刻のjava.util.Date 。jobがまだ開始していない場合は空。 |
endTime | 実行終了時の現在システム時刻のjava.util.Date 。成功かどうかは無関係。jobがまだ終了していない場合は空。 |
exitStatus | 実行結果を示すExitStatus 。呼び出し元に返される終了コードなので、最も重要。詳細は5章参照。jobがまだ終了していない場合は空。 |
createTime | JobExecution を最初に永続化した時点の現在システム時刻のjava.util.Date 。jobが未開始の場合がある(その場合startTimeも空)が、jobレベルのExecutionContexts を管理する上でフレームワークはcreateTimeを必要とします。 |
lastUpdated | JobExecution を最後の永続化した時間のjava.util.Date 。jobが未開始の場合は空。 |
executionContext | 複数のexecution間で永続化の必要があるユーザデータを持つ、プロパティの入れ物。 |
failureExceptions | Job 実行中に発生した例外リスト。Job の失敗時に複数の例外が発生した場合に役立ちます。 |
これらプロパティは永続化され、実行ステータスの決定に使われる点が重要です。例えば、1/1のEndOfDay jobが9:00 PMに実行して9:30に失敗した場合、以下のエントリがメタデータテーブルに作られます。
Table 2. BATCH_JOB_INSTANCE
JOB_INST_ID | JOB_NAME |
---|---|
1 | EndOfDayJob |
Table 3. BATCH_JOB_EXECUTION_PARAMS
JOB_EXECUTION_ID | TYPE_CD | KEY_NAME | DATE_VAL | IDENTIFYING |
---|---|---|---|---|
1 | DATE | schedule.Date | 2017-01-01 | TRUE |
Table 4. BATCH_JOB_EXECUTION
JOB_EXEC_ID | OB_INST_ID | START_TIME | END_TIME | STATUS |
---|---|---|---|---|
1 | 1 | 2017-01-01 21:00 | 2017-01-01 21:30 | FAILED |
※カラム名は説明のために削除したり簡略化しているものがあります。
いま、jobが失敗し、問題解決には一晩かかるとし、バッチウィンドウ('batch window')は終了しています。次に、ウィンドウ開始を9:00 PM、1/1のjobを再度開始、前回失敗箇所から開始して9:30に正常終了しました。この時すでに翌日になっており、1/2のjobも走らせる必要があるので、その直後の9:31に開始して通常の1時間で10:30に完了しました。1つのJobInstance
を連続実行する事に特に要求事項は無いですが、2つのjobが同一データにアクセスする可能性が無い限りにおいてであり、これはDBレベルでロックを引き起こしかねないためです。いつJob
を実行するかどうかはスケジューラーに完全に依存します。別々のJobInstances
なので、Spring Batchはコンカレントな実行を止めません。(同一のJobInstance
が実行中の場合、JobExecutionAlreadyRunningException
をスローします。)最終的に、以下表のように、JobInstance
とJobParameters
テーブルに行が追加され、JobExecution
に2行追加されます。
Table 5. BATCH_JOB_INSTANCE
JOB_INST_ID | JOB_NAME |
---|---|
1 | EndOfDayJob |
2 | EndOfDayJob |
Table 6. BATCH_JOB_EXECUTION_PARAMS
JOB_EXECUTION_ID | TYPE_CD | KEY_NAME | DATE_VAL | IDENTIFYING |
---|---|---|---|---|
1 | DATE | schedule.Date | 2017-01-01 00:00:00 | TRUE |
2 | DATE | schedule.Date | 2017-01-01 00:00:00 | TRUE |
3 | DATE | schedule.Date | 2017-01-02 00:00:00 | TRUE |
Table 7. BATCH_JOB_EXECUTION
JOB_EXEC_ID | JOB_INST_ID | START_TIME | END_TIME | STATUS |
---|---|---|---|---|
1 | 1 | 2017-01-01 21:00 | 2017-01-01 21:30 | FAILED |
2 | 1 | 2017-01-02 21:00 | 2017-01-02 21:30 | COMPLETED |
3 | 2 | 2017-01-02 21:31 | 2017-01-02 22:29 | COMPLETED |
※カラム名は説明のために削除したり簡略化しているものがあります。
1.2. Step
Step
は、バッチjobのシーケンシャルで独立したフェーズ、をカプセル化したドメインオブジェクトです。よって、すべてのJobは必ず1つ以上のstepで構成します。Step
には実際のバッチ処理を定義したり制御するのに必要なすべての情報を持ちます。これは全く持って曖昧な説明ですが、Step
の中身は開発者が書くJob
次第なためです。Step
は開発者次第でシンプルなものから複雑なものまで作成出来ます。シンプルなStep
はファイルからDBにデータをロードし、コードは全く書かないか少量で済みます(実装依存)。複雑なStep
は、何らかの処理の一部となる複雑なビジネスルールを持つものが考えられます。Job
同様、Step
は複数のStepExecution
を持ち、1つのJobExecution
と関連を持ちます。以下がイメージ図です。
igure 4. Job Hierarchy With Steps
1.2.1. StepExecution
StepExecution
はStep
の1回分の実行を表現したものです。JobExecution
同様に、Step
が実行される度に新規にStepExecution
を生成します。もしstepが実行に失敗するとき、原因がそのstepより前にある場合、そのstepのexecutionは永続化しません。StepExecution
はStep
が実際に開始した後にだけ生成します。
Step
の実行はStepExecution
クラスのオブジェクトで表現します。それぞれの実行は、対応するstep・JobExecution
・トランザクション関連データ、このデータはコミット・ロールバックカウント・開始終了時刻など、を持ちます。また、それぞれのstep executionはExecutionContext
を持ち、ここには開発者がバッチ実行中に永続化しておきたい任意のデータ、例えば統計やリスタートに必要な状態、を持たせます。以下はStepExecution
のプロパティリストです。
Table 8. StepExecution Properties
Property | Definition |
---|---|
Status | 実行ステータスを示すBatchStatus オブジェクト。実行中BatchStatus.STARTED 。失敗BatchStatus.FAILED 。正常終了BatchStatus.COMPLETED 。 |
startTime | 実行開始時の現在システム時刻のjava.util.Date 。stepがまだ開始していない場合は空。 |
endTime | 実行終了時の現在システム時刻のjava.util.Date 。成功かどうかは無関係。stepがまだ終了していない場合は空。 |
exitStatus | 実行結果を示すExitStatus 。呼び出し元に返される終了コードなので、最も重要。詳細は5章参照。jobがまだ終了していない場合は空。 |
executionContext | 複数のexecution間で永続化の必要があるユーザデータを持つ、プロパティの入れ物。 |
readCount | 正常に読み込んだアイテム数。 |
writeCount | 正常に書き込んだアイテム数。 |
commitCount | この実行でコミットしたトランザクション数。 |
rollbackCount | Step で制御するビジネストランザクションのロールバック数。 |
readSkipCount | read が失敗した回数。アイテムスキップとなる。 |
processSkipCount | process が失敗した回数。アイテムスキップとなる。 |
filterCount | ItemProcessor がフィルターしたアイテム数。 |
writeSkipCount | write が失敗した回数。アイテムスキップとなる。 |
1.3. ExecutionContext
ExecutionContext
はフレームワークが制御および永続化するkey/valueのコレクションで、開発者が永続化状態を保存する場所です。これはStepExecution
もしくはJobExecution
スコープになります。Quartzで言うところのJobDataMapです。最も良く使われる例としてはリスタートの調整です。入力としてフラットファイルを取る例で言うと、個々の行を処理中、フレームワークは定期的にコミットポイントでExecutionContext
を永続化します。これにより、ItemReader
で実行中に致命的エラーが発生してもその状態を保存します。これを実装するには、以下例のように、コンテキストに読み込んだ現在行をputすると、あとはフレームワークがやってくれます。
executionContext.putLong(getKey(LINES_READ_COUNT), reader.getPosition());
例としてJob
セクションのEndOfDayサンプルの場合、'loadData'というファイルをDBにロードするstepが一つあるとします。最初の実行が失敗後、メタデータテーブルは以下の例になります。
Table 9. BATCH_JOB_INSTANCE
JOB_INST_ID | JOB_NAME |
---|---|
1 | EndOfDayJob |
Table 10. BATCH_JOB_EXECUTION_PARAMS
JOB_INST_ID | TYPE_CD | KEY_NAME | DATE_VAL |
---|---|---|---|
1 | DATE | schedule.Date | 2017-01-01 |
Table 11. BATCH_JOB_EXECUTION
JOB_EXEC_ID | JOB_INST_ID | START_TIME | END_TIME | STATUS |
---|---|---|---|---|
1 | 1 | 2017-01-01 21:00 | 2017-01-01 21:30 | FAILED |
Table 12. BATCH_STEP_EXECUTION
STEP_EXEC_ID | JOB_EXEC_ID | STEP_NAME | START_TIME | END_TIME | STATUS |
---|---|---|---|---|---|
1 | 1 | loadData | 2017-01-01 21:00 | 2017-01-01 21:30 | FAILED |
Table 13. BATCH_STEP_EXECUTION_CONTEXT
STEP_EXEC_ID | SHORT_CONTEXT |
---|---|
1 | {piece.count=40321} |
上の場合、Step
は30分実行し、このシナリオにおけるファイル行であるところの40,321 'pieces'を処理しています。この値はフレームワークがコミット直前に更新します。また、`この値にはExecutionContext
の複数エントリに対応する形で複数行を含められます。コミット前に通知を受けるには何らかのStepListener
実装(かItemStream
)が必要で、詳細は本が意図の後半で扱います。前述の例同様、ここでもJob
を翌日にリスタートする、とします。リスタートすると、最終実行時のExecutionContext
の値をDBから再構成します。ItemReader
がopenすると、以下例のように、コンテキストに保存した状態があればチェックしてその値で初期化します。
if (executionContext.containsKey(getKey(LINES_READ_COUNT))) { log.debug("Initializing for restart. Restart data is: " + executionContext); long lineCount = executionContext.getLong(getKey(LINES_READ_COUNT)); LineReader reader = getReader(); Object record = ""; while (reader.getPosition() < lineCount && record != null) { record = readLine(); } }
この例の場合、上のコードを実行すると、カレントの行は40322になり、Step
は前回失敗箇所からの再実行が可能となります。また、ExecutionContext
にはその実行に関して永続化したい統計情報にも使えます。例えば、フラットファイルが複数行に存在する処理命令(orders for processing)を含むケースでは、処理した命令数(読み込んだ行数とは異なる)を保存し、Step
の最後に合計処理命令数をメールで送るような処理が可能です。それぞれのJobInstance
で正確なスコープにするため、フレームワークがそれらの情報を保存します。すでに在るExecutionContext
が使われるかどうかを知ることは困難です。たとえば、上記の 'EndOfDay' の例で言うと、1/1を2回目に実行するとき、フレームワークは同一JobInstance
とStep
であると解釈し、DBからExecutionContext
を取得し、その取得データを(StepExecution
の一部として)Step
に渡します。一方で、1/2実行時には、フレームワークは別のインスタンスであると解釈し、よって空のコンテキストをStep
に渡します。There are many of these types of determinations that the framework makes for the developer, to ensure the state is given to them at the correct time. また、ある時点において1つのStepExecution
には1つのExecutionContext
だけが存在する点は重要です。ExecutionContext
を使う際には注意が必要で、このインスタンスは共有のキースペース(a shared keyspace)を生成するためです。このため、データを上書きしないような注意が必要です。ただし、Step
がコンテキストに何もデータを保存しないのであれば、フレームワークには何も影響を与えません。
1つのJobExecution
に少なくとも1つのExecutionContext
、それぞれのStepExecution
ごとに1つのExecutionContext
、が存在する点も重要です。例えば、以下のコード例を考えます。
ExecutionContext ecStep = stepExecution.getExecutionContext();
ExecutionContext ecJob = jobExecution.getExecutionContext();
//ecStepとecJobは異なる
コメントにあるように、ecStep
とecJob
は異なります。この場合は2つの異なるExecutionContexts
が存在します。1つはStep
のスコープでStep
のコミットポイントごとにセーブし、一方、Jobのスコープの方はStep
実行の間でセーブします。
1.4. JobRepository
JobRepository
は上述のすべての機能のための永続化メカニズムです。JobLauncher
, Job
, Step
実装で用いるためのCRUD操作を提供します。Job
をラウンチする時、JobExecution
をリポジトリから取得し、また、実行中には、StepExecution
とJobExecution
の実装をリポジトリに渡すことで永続化をします。
java configurationを使う場合、@EnableBatchProcessing
アノテーションのデフォルト設定でJobRepository
が自動的に登録されます。
1.5. JobLauncher
JobLauncher
は、以下例のように、JobParameters
を渡してJob
を起動するためのインタフェースです。
public interface JobLauncher { public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException; }
このインタフェース実装はJobRepository
からJobExecution
を取得してJob
を実行します。
1.6. Item Reader
ItemReader
は、1回に1アイテム、Step
での入力を取得するためのインタフェースです。ItemReader
が読み込み可能なアイテムが無くなった事を通知するにはnull
を返します。ItemReader
インタフェースの詳細と各種実装についてはReaders And Writersを参照してください。
1.7. Item Writer
ItemWriter
は、1回に1チャンクか1バッチ、Step
での出力を行うためのインタフェースです。通常、ItemWriter
は次に受け取るであろう入力に関しては関知せず、現在の呼び出し中に渡されたアイテムのみ処理します。ItemWriter
インタフェースの詳細と各種実装についてはReaders And Writersを参照してください。
1.8. Item Processor
ItemProcessor
はアイテムにビジネス処理をを行うためのインタフェースです。ItemReader
は1アイテム読み込み、ItemWriter
はそれらの書き込み、ItemProcessor
はそれ以外のビジネス処理の適用や変換処理のアクセスポイントとなります。なお、アイテム処理中に、アイテムがvalidで無い場合、null
を返すとそのアイテムは書き込みません。ItemProcessor
インタフェースの詳細と各種実装についてはReaders And Writersを参照してください。