バッチのドメイン言語

経験豊富なバッチアーキテクトにとって、Spring Batch で使用されるバッチ処理の全体的な概念は、馴染みがあり快適です。「ジョブ」と「ステップ」、および ItemReader と ItemWriter と呼ばれる開発者提供の処理ユニットがあります。ただし、Spring のパターン、操作、テンプレート、コールバック、イディオムのため、次の機会があります。

  • 関心事の明確な分離への遵守の大幅な改善。

  • インターフェースとして提供される明確に描かれたアーキテクチャ層とサービス。

  • すぐに使用でき、すぐに使用できる簡単でデフォルトの実装。

  • 拡張性が大幅に向上しました。

次の図は、何十年も使用されてきたバッチ参照アーキテクチャの簡略版です。バッチ処理のドメイン言語を構成するコンポーネントの概要を提供します。このアーキテクチャフレームワークは、過去数世代のプラットフォーム(COBOL/ メインフレーム、C/Unix、現在は Java/ 任意の場所)での数十年の実装を通じて実証された設計図です。JCL および COBOL の開発者は、C、C#、Java の開発者と同じくらい概念に慣れていると思われます。Spring Batch は、シンプルで複雑なバッチアプリケーションの作成に対応するために使用される、堅牢で保守可能なシステムに一般的に見られるレイヤー、コンポーネント、技術サービスの物理的な実装を提供します。

Figure 2.1: Batch Stereotypes
図 1: バッチステレオタイプ

上記の図は、Spring Batch のドメイン言語を構成する重要な概念を強調しています。ジョブには 1 つから多数のステップがあり、各ステップには正確に 1 つの ItemReader、1 つの ItemProcessor、1 つの ItemWriter があります。ジョブを立ち上げる必要があり(JobLauncher を使用)、現在実行中のプロセスに関するメタデータを(JobRepository に)格納する必要があります。

ジョブ

このセクションでは、バッチジョブの概念に関連するステレオタイプについて説明します。Job は、バッチプロセス全体をカプセル化するエンティティです。他の Spring プロジェクトで一般的であるように、Job は XML 構成ファイルまたは Java ベースの構成のいずれかと一緒に接続されます。この構成は、「ジョブ構成」と呼ばれる場合があります。ただし、次の図に示すように、Job は階層全体の最上位にすぎません。

Job Hierarchy
図 2: ジョブ階層

Spring Batch では、Job は Step インスタンスの単なるコンテナーです。フロー内で論理的に一緒に属する複数のステップを組み合わせて、再起動性など、すべてのステップに対してグローバルなプロパティの構成を可能にします。ジョブ構成には次が含まれます。

  • ジョブの単純な名前。

  • Step インスタンスの定義と順序。

  • ジョブが再開可能かどうか。

Java 構成を使用する場合、Spring Batch は、Job の上にいくつかの標準機能を作成する SimpleJob クラスの形式で Job インターフェースのデフォルトの実装を提供します。java ベースの構成を使用する場合、次の例に示すように、Job のインスタンス化にビルダーのコレクションが使用可能になります。

@Bean
public Job footballJob() {
    return this.jobBuilderFactory.get("footballJob")
                     .start(playerLoad())
                     .next(gameLoad())
                     .next(playerSummarization())
                     .build();
}

XML 構成を使用する場合、Spring Batch は、Job の上にいくつかの標準機能を作成する SimpleJob クラスの形式で Job インターフェースのデフォルトの実装を提供します。ただし、バッチ名前空間は、直接インスタンス化する必要性を抽象化します。代わりに、次の例に示すように、<job> 要素を使用できます。

<job id="footballJob">
    <step id="playerload" next="gameLoad"/>
    <step id="gameLoad" next="playerSummarization"/>
    <step id="playerSummarization"/>
</job>

JobInstance

JobInstance は、論理ジョブ実行の概念を指します。前の図の 'EndOfDay' Job など、一日の終わりに 1 回実行するバッチジョブを検討します。1 つの "EndOfDay" ジョブがありますが、Job の個々の実行は個別に追跡する必要があります。このジョブの場合、1 日に 1 つの論理 JobInstance があります。例: 1 月 1 日の実行、1 月 2 日の実行などがあります。1 月 1 日の実行が初めて失敗し、翌日に再度実行された場合、1 月 1 日の実行のままです。(通常、これは処理中のデータにも対応します。つまり、1 月 1 日の実行は 1 月 1 日のデータを処理します)。各 JobInstance は複数回実行できます(JobExecution については、この章で後ほど詳しく説明します)。特定の Job に対応し、JobParameters を識別する JobInstance は、一度に 1 つしか実行できません。

JobInstance の定義は、ロードされるデータにはまったく関係ありません。データのロード方法を決定するのは、ItemReader 実装次第です。例: EndOfDay シナリオでは、データが属する「発効日」または「スケジュール日」を示す列がデータにある場合があります。1 月 1 日の実行では 1 日からのデータのみがロードされ、1 月 2 日の実行では 2 日からのデータのみが使用されます。この決定はビジネス上の決定である可能性が高いため、決定するのは ItemReader に任されています。ただし、同じ JobInstance を使用すると、以前の実行からの「状態」(つまり、この章で後述する ExecutionContext)を使用するかどうかが決まります。新しい JobInstance を使用することは「最初から開始する」ことを意味し、既存のインスタンスを使用することは通常「中断したところから開始する」ことを意味します。

JobParameters

JobInstance とそれがジョブとどのように異なるかについて議論したため、確認する自然な質問は、「ある JobInstance を別の JobInstance とどのように区別するか」です。答えは JobParameters です。JobParameters オブジェクトは、バッチジョブの開始に使用される一連のパラメーターを保持します。次の図に示すように、これらは識別に使用することも、実行中の参照データとして使用することもできます。

Job Parameters
図 3: ジョブパラメーター

1 月 1 日と 1 月 2 日の 2 つのインスタンスがある上記の例では、実際には Job が 1 つだけですが、JobParameter オブジェクトが 2 つあります。1 つは 01-01-2017 のジョブパラメーターで開始され、もう 1 つは 01-02-2017 のパラメーターで開始。契約は JobInstance = Job + JobParameters を識別するように定義できます。これにより、開発者は、渡されるパラメーターを制御するため、JobInstance の定義方法を効果的に制御できます。

すべてのジョブパラメーターが JobInstance の識別にコントリビュートする必要はありません。デフォルトでは、そうします。ただし、このフレームワークでは、JobInstance の ID にコントリビュートしないパラメーターを使用して Job を送信することもできます。

JobExecution

JobExecution は、ジョブを実行する 1 回の試行の技術的概念を指します。実行は失敗または成功で終了する場合がありますが、実行が正常に完了するまで、特定の実行に対応する JobInstance は完了したとは見なされません。例として前述した EndOfDay Job を使用して、01-01-2017 の JobInstance が最初に実行されたときに失敗したと考えてください。最初の実行(01-01-2017)と同じ識別ジョブパラメーターで再度実行すると、新しい JobExecution が作成されます。ただし、まだ JobInstance は 1 つしかありません。

Job はジョブの内容と実行方法を定義し、JobInstance は主に正しい再起動セマンティクスを有効にするために、実行をグループ化する純粋な組織オブジェクトです。ただし、JobExecution は、実行中に実際に発生したことの主なストレージメカニズムであり、次の表に示すように、制御および永続化する必要のある多くのプロパティが含まれています。

表 1: JobExecution のプロパティ

プロパティ

定義

ステータス

実行のステータスを示す BatchStatus オブジェクト。実行中は、BatchStatus#STARTED です。失敗した場合、BatchStatus#FAILED です。正常に終了した場合は、BatchStatus#COMPLETED です

startTime

実行が開始された現在のシステム時刻を表す java.util.Date。ジョブがまだ開始されていない場合、このフィールドは空です。

endTime

成功したかどうかに関係なく、実行が終了した現在のシステム時刻を表す java.util.Date。ジョブがまだ終了していない場合、フィールドは空です。

exitStatus

実行結果を示す ExitStatus。最も重要なのは、呼び出し元に返される終了コードが含まれているためです。詳細については、第 5 章を参照してください。ジョブがまだ終了していない場合、フィールドは空です。

createTime

JobExecution が最初に保持された現在のシステム時刻を表す java.util.Date。ジョブはまだ開始されていない可能性があります(したがって開始時間はありません)が、常に createTime があります。これは、ジョブレベル ExecutionContexts を管理するためのフレームワークで必要です。

lastUpdated

JobExecution が最後に保存された時間を表す java.util.Date。ジョブがまだ開始されていない場合、このフィールドは空です。

executionContext

実行間で保持する必要があるユーザーデータを含む「プロパティバッグ」。

failureExceptions

Job の実行中に発生した例外のリスト。これらは、Job の障害中に複数の例外が発生した場合に役立ちます。

これらのプロパティは永続的であり、実行のステータスを完全に決定するために使用できるため、これらのプロパティは重要です。例: 01-01 の EndOfDay ジョブが 9:00 PM で実行され、9:30 で失敗すると、バッチメタデータテーブルに次のエントリが作成されます。

表 2: BATCH_JOB_INSTANCE

JOB_INST_ID

JOB_NAME

1

EndOfDayJob

表 3: BATCH_JOB_EXECUTION_PARAMS

JOB_EXECUTION_ID

TYPE_CD

KEY_NAME

DATE_VAL

IDENTIFYING

1

DATE

schedule.Date

2017-01-01

TRUE

表 4: 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

列名は、明確さとフォーマットのために省略または削除されている場合があります。

ジョブが失敗したため、「バッチウィンドウ」が閉じられるように、課題を特定するのに一晩かかったと仮定します。さらに、ウィンドウが 9:00 PM から開始すると仮定すると、01-01 のジョブは再び開始され、中断したところから開始して 9:30 で正常に完了します。翌日であるため、01-02 ジョブも実行する必要があり、その後すぐに 9:31 で開始され、10:30 で通常の 1 時間で完了します。2 つのジョブが同じデータにアクセスしようとしてデータベースレベルでのロックの課題を引き起こす可能性がない限り、JobInstance を次々に開始する必要はありません。Job をいつ実行するかを決定するのは、完全にスケジューラー次第です。それらは別個の JobInstances であるため、Spring Batch はそれらの同時実行を停止しようとしません。(別の JobInstance をすでに実行中に同じ JobInstance を実行しようとすると、JobExecutionAlreadyRunningException がスローされます)。次の表に示すように、JobInstance テーブルと JobParameters テーブルの両方に追加のエントリがあり、JobExecution テーブルに 2 つの追加のエントリがあります。

表 5: BATCH_JOB_INSTANCE

JOB_INST_ID

JOB_NAME

1

EndOfDayJob

2

EndOfDayJob

表 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

表 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

列名は、明確さとフォーマットのために省略または削除されている場合があります。

ステップ

Step は、バッチジョブの独立したシーケンシャルフェーズをカプセル化するドメインオブジェクトです。すべてのジョブは 1 つ以上のステップで完全に構成されます。Step には、実際のバッチ処理を定義および制御するために必要なすべての情報が含まれています。Step の内容は Job を作成する開発者の裁量にあるため、これは必然的に曖昧な記述です。Step は、開発者が望むほど単純でも複雑でもかまいません。単純な Step は、ファイルからデータベースにデータをロードし、コードをほとんどまたはまったく必要としない場合があります(使用する実装によって異なります)。より複雑な Step には、処理の一部として適用される複雑なビジネスルールがある場合があります。Job と同様に、Step には、次のイメージに示すように、一意の JobExecution と相関する個々の StepExecution があります。

Figure 2.1: Job Hierarchy With Steps
図 4: ステップを含むジョブ階層

StepExecution

StepExecution は、Step を実行する 1 回の試行を表します。JobExecution と同様に、Step が実行されるたびに新しい StepExecution が作成されます。ただし、失敗する前のステップが原因でステップの実行が失敗した場合、その実行は持続しません。StepExecution は、その Step が実際に開始されたときにのみ作成されます。

Step の実行は、StepExecution クラスのオブジェクトによって表されます。各実行には、対応するステップへの参照、JobExecution、コミットとロールバックのカウント、開始時間と終了時間などのトランザクション関連データが含まれます。さらに、各ステップの実行には ExecutionContext が含まれています。これには、統計情報や再起動に必要な状態情報など、開発者がバッチ実行で保持する必要があるデータが含まれています。次の表に、StepExecution のプロパティを示します。

表 8: StepExecution のプロパティ

プロパティ

定義

ステータス

実行のステータスを示す BatchStatus オブジェクト。実行中のステータスは BatchStatus.STARTED です。失敗した場合、ステータスは BatchStatus.FAILED です。正常に終了した場合、ステータスは BatchStatus.COMPLETED です。

startTime

実行が開始された現在のシステム時刻を表す java.util.Date。ステップがまだ開始されていない場合、このフィールドは空です。

endTime

成功したかどうかに関係なく、実行が終了した現在のシステム時刻を表す java.util.Date。ステップがまだ終了していない場合、このフィールドは空です。

exitStatus

実行結果を示す ExitStatus。最も重要なのは、呼び出し元に返される終了コードが含まれているためです。詳細については、第 5 章を参照してください。ジョブがまだ終了していない場合、このフィールドは空です。

executionContext

実行間で保持する必要があるユーザーデータを含む「プロパティバッグ」。

readCount

正常に読み取られたアイテムの数。

writeCount

正常に書き込まれたアイテムの数。

commitCount

この実行のためにコミットされたトランザクションの数。

rollbackCount

Step によって制御されるビジネストランザクションがロールバックされた回数。

readSkipCount

read が失敗し、結果としてアイテムがスキップされた回数。

processSkipCount

process が失敗し、結果としてアイテムがスキップされた回数。

filterCount

ItemProcessor によって「フィルタリング」されたアイテムの数。

writeSkipCount

write が失敗し、結果としてアイテムがスキップされた回数。

ExecutionContext

ExecutionContext は、開発者が StepExecution オブジェクトまたは JobExecution オブジェクトをスコープとする永続的な状態を保存する場所を可能にするために、フレームワークによって永続化および制御されるキー / 値ペアのコレクションを表します。Quartz に精通している人にとっては、JobDataMap と非常によく似ています。最適な使用例は、再起動を容易にすることです。例としてフラットファイル入力を使用して、個々の行を処理している間、フレームワークはコミットポイントで ExecutionContext を定期的に永続化します。そうすることで、実行中に致命的なエラーが発生した場合、電源が切れた場合でも、ItemReader はその状態を保存できます。必要なのは、次の例に示すように、読み取られた現在の行数をコンテキストに入れることだけで、残りはフレームワークが行います。

executionContext.putLong(getKey(LINES_READ_COUNT), reader.getPosition());

例として Job ステレオタイプセクションの EndOfDay の例を使用して、データベースにファイルをロードする "loadData" という 1 つのステップがあると仮定します。最初の失敗した実行後、メタデータテーブルは次の例のようになります。

表 9: BATCH_JOB_INSTANCE

JOB_INST_ID

JOB_NAME

1

EndOfDayJob

表 10: BATCH_JOB_EXECUTION_PARAMS

JOB_INST_ID

TYPE_CD

KEY_NAME

DATE_VAL

1

DATE

schedule.Date

2017-01-01

表 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

表 12: BATCH_STEP_EXECUTION

STEP_EXEC_ID

JOB_EXEC_ID

STEP_NAME

START_TIME

END_TIME

STATUS

1

1

データを読み込む

2017-01-01 21:00

2017-01-01 21:30

FAILED

表 13: BATCH_STEP_EXECUTION_CONTEXT

STEP_EXEC_ID

SHORT_CONTEXT

1

{piece.count=40321}

上記の場合、Step は 30 分間実行され、40,321 の「ピース」を処理しました。これは、このシナリオのファイル内の行を表します。この値は、フレームワークによって各コミットの直前に更新され、ExecutionContext 内のエントリに対応する複数の行を含むことができます。コミットの前に通知を受けるには、さまざまな StepListener 実装の 1 つ(または ItemStream)が必要です。これについては、このガイドで後ほど詳しく説明します。前の例と同様に、Job は翌日に再起動されると想定されています。再起動すると、最後の実行の ExecutionContext の値がデータベースから再構成されます。ItemReader を開くと、次の例に示すように、コンテキストに保存された状態があるかどうかを確認し、そこから自身を初期化できます。

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();
    }
}

この場合、上記のコードを実行すると、現在の行は 40,322 になり、Step は中断した場所から再開できます。ExecutionContext は、実行自体について永続化する必要がある統計にも使用できます。例: フラットファイルに複数の明細にわたって存在する処理のための受注が含まれている場合は、処理された受注の数 (読み取り行数とは大きく異なる) を格納して、本文で処理された受注の合計数とともに Step の最後にメールを送信できるようにする必要があります。フレームワークは、個々の JobInstance で正しくスコープを設定するために、開発者のためにこれを保存します。既存の ExecutionContext を使用すべきかどうかを判断することは非常に困難です。例: 上記の "EndOfDay" の例を使用すると、01-01 の実行が再度開始されると、フレームワークは、それが同じ JobInstance であり、個別の Step ベースであることを認識し、ExecutionContext をデータベースから取り出し、Step 自体に (StepExecution の一部として) 渡します。逆に、01-02 を実行すると、フレームワークはそれが別のインスタンスであることを認識するため、空のコンテキストを Step に渡す必要があります。フレームワークが開発者に対して行う決定には、適切なタイミングで状態が与えられることを保証するために、このような種類が数多くあります。また、StepExecution ごとに常に 1 つの ExecutionContext が存在することにも注意してください。ExecutionContext のクライアントは、共有鍵空間を作成するため注意が必要です。そのため、値を入力するときは、データが上書きされないように注意する必要があります。ただし、Step はコンテキスト内にデータをまったく格納しないため、フレームワークに悪影響を与えることはありません。

JobExecution ごとに少なくとも 1 つの ExecutionContext があり、各 StepExecution に 1 つあることに注意することも重要です。例: 次のコードスニペットを検討します。

ExecutionContext ecStep = stepExecution.getExecutionContext();
ExecutionContext ecJob = jobExecution.getExecutionContext();
//ecStep does not equal ecJob

コメントで記述されていたように、ecStep は ecJob と等しくありません。それらは 2 つの異なる ExecutionContexts です。Step を対象とするものは Step のすべてのコミットポイントで保存されますが、ジョブを対象とするものは Step の実行のたびに保存されます。

JobRepository

JobRepository は、上記のすべてのステレオタイプの永続化メカニズムです。JobLauncherJobStep 実装の CRUD 操作を提供します。Job が最初に起動されると、JobExecution がリポジトリから取得され、実行中に StepExecution と JobExecution の実装がリポジトリに渡されて永続化されます。

Spring Batch XML 名前空間は、次の例に示すように、<job-repository> タグを使用して JobRepository インスタンスを構成するためのサポートを提供します。

<job-repository id="jobRepository"/>

Java 構成を使用する場合、@EnableBatchProcessing アノテーションは、すぐに利用できる自動的に構成されるコンポーネントの 1 つとして JobRepository を提供します。

JobLauncher

JobLauncher は、次の例に示すように、指定された JobParameters のセットで Job を起動するための単純なインターフェースを表します。

public interface JobLauncher {

public JobExecution run(Job job, JobParameters jobParameters)
            throws JobExecutionAlreadyRunningException, JobRestartException,
                   JobInstanceAlreadyCompleteException, JobParametersInvalidException;
}

実装が JobRepository から有効な JobExecution を取得し、Job を実行することが期待されます。

アイテムリーダー

ItemReader は、一度に 1 項目ずつ Step の入力の取得を表す抽象概念です。ItemReader が提供できるアイテムを使い果たすと、null を返すことでこれを示します。ItemReader インターフェースとそのさまざまな実装に関する詳細は、リーダーとライターにあります。

アイテムライター

ItemWriter は、一度に 1 つのバッチまたはアイテムのチャンクである Step の出力を表す抽象化です。一般的に、ItemWriter は次に受信する入力を認識せず、現在の呼び出しで渡されたアイテムのみを認識します。ItemWriter インターフェースとそのさまざまな実装に関する詳細は、リーダーとライターにあります。

アイテムプロセッサー

ItemProcessor は、アイテムのビジネス処理を表す抽象概念です。ItemReader は 1 つのアイテムを読み取り、ItemWriter はそれらを書き込みますが、ItemProcessor は他のビジネス処理を変換または適用するためのアクセスポイントを提供します。アイテムの処理中に、アイテムが無効であると判断された場合、null を返すことは、アイテムを書き出すべきではないことを示します。ItemProcessor インターフェースの詳細については、リーダーとライターを参照してください。

バッチ名前空間

前述のドメイン概念の多くは、Spring ApplicationContext で構成する必要があります。標準の Bean 定義で使用できる上記のインターフェースの実装がありますが、次の例に示すように、構成を容易にするために名前空間が提供されています。

<beans:beans xmlns="http://www.springframework.org/schema/batch"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
   http://www.springframework.org/schema/beans
   https://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/batch
   https://www.springframework.org/schema/batch/spring-batch.xsd">

<job id="ioSampleJob">
    <step id="step1">
        <tasklet>
            <chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
        </tasklet>
    </step>
</job>

</beans:beans>

バッチ名前空間が宣言されている限り、任意の要素を使用できます。ジョブの設定に関する詳細は、ジョブの構成と実行にあります。Step の構成に関する詳細は、ステップの構成にあります。