原文はこちら。
The original article was written by Mitia Alexandrov (a software developer at Oracle, working on Project Helidon).
https://medium.com/helidon/helidon-and-jbatch-2826eec478a2
この記事では、EE環境を完全にはサポートしていない環境で、HelidonとJBatchを併用してバッチジョブを実行する方法を紹介します。
Jakarta Batch(JBatch)の公式サイトの説明によると、JBatchの仕様は以下のように定められています。
The Jakarta Batch project describes the XML-based job specification language (JSL), Java programming model, and runtime environment for batch applications for the Java platform.
The specification ties together the Java API and the JSL (XML) allowing a job designer to compose a job in XML from Java application artifacts and conveniently parameterize them with values for an individual job. This structure promotes application reuse of artifacts across different jobs.
The specification allows the flexibility for batch jobs to be scheduled or orchestrated in any number of ways, and stops short of defining any APIs or constructs regarding scheduling or orchestration of multiple or repeated jobs.
(Jakarta Batchプロジェクトでは、XMLベースのジョブ仕様言語(JSL)、Javaプログラミングモデル、およびJavaプラットフォーム用のバッチアプリケーション用ランタイム環境を説明しています。
この仕様は、Java APIとJSL(XML)を結びつけることで、ジョブ設計者がJavaアプリケーションの成果物からXMLでジョブを構成し、個々のジョブ用の値でそれらを便利にパラメータ化できるようにしています。この構造により、異なるジョブ間でのアプリケーションの成果物の再利用を促進します。
この仕様では、バッチジョブのスケジューリングやオーケストレーションの方法に柔軟性を持たせており、複数のジョブや繰り返されるジョブのスケジューリングやオーケストレーションに関するAPIや構造を定義することは控えています。)
https://projects.eclipse.org/projects/ee4j.batch
JBatchの用途は非常に多岐にわたります。実質的にエンタープライズ(EE)の世界では、何百万ものサーバーで何百万ものバッチジョブが実行されています。JBatch仕様は、このようなタイプのタスクを、Java/Jakarta EEの世界のエンタープライズ・ソリューション間で可搬性を持たせるために作成されました。
そして、これは単なる仕様であり、完全な実装ではありません。各ベンダーは独自の実装を提供しなければなりませんが、仕様自体は独立したものではありません。非常に具体的で、例えばJTAやJPAなどの他の仕様に大きく依存しています。つまり、JBatchジョブを実行したいのであれば、完全なEE仕様をサポートするEnterprise Serverが必要になります。
しかし、すべてがマイクロサービスに移行し、そこでは起動時間とリソース消費が重要です。そのため、EE仕様の完全なサポートは事実上不可能です。でもそうでしょうか?
例えばHelidonは、Nettyを中心に構築されており、Jakartaの仕様を完全にはサポートしていませんが、マイクロサービスの世界では申し分ありません。
もし、EE環境を完全にはサポートしていない環境でバッチジョブを実行する必要があるとしたら?実はHelidonには、そのためのソリューションがあるのです。
説明のためのちょっとしたサンプルを作ってみましょう。
Let’s create a small example demonstrating this
Helidonは完全なEEコンテナではないため、どうすればjBatchを利用すればよいのでしょうか。これには、JBatchのいわゆるスタンドアロン(SE)実装の1つを使用します。これは最も一般的な方法とはいえませんが、今回の場合には有効な方法です。この例では、IBM JBatchの実装を使用します。
<dependency>
<groupId>com.ibm.jbatch</groupId>
<artifactId>com.ibm.jbatch.container</artifactId>
<version>1.0.3</version>
</dependency>
JPAはデフォルトでは利用できないため、組み込みDBの derby
も必要です。
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.13.1.1</version>
</dependency>
ではジョブを追加していきましょう。このサンプルでは、以下の MyItemReader
、MyItemProcessor
、そしてMyItemWriter
を使います。JBatchの可能なすべての使用法を示すために、1つのMyBatchlet
も使用します。
ここでは、入力情報のユニットを作成します。
public class MyInputRecord {
private int id;
public MyInputRecord() {
}
public MyInputRecord(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "MyInputRecord: " + id;
}
}
出力情報の単位である MyOutputRecord
は同じように見えます。
MyItemReader
は以下のようになります。
public class MyItemReader extends AbstractItemReader {
private final StringTokenizer tokens;
public MyItemReader() {
tokens = new StringTokenizer("1,2,3,4,5,6,7,8,9,10", ",");
}
@Override
public MyInputRecord readItem() {
if (tokens.hasMoreTokens()) {
return new MyInputRecord(Integer
.valueOf(tokens.nextToken()));
}
return null;
}
}
MyItemProcessor
は次の簡単な操作を実行します。
public class MyItemProcessor implements ItemProcessor {
@Override
public MyOutputRecord processItem(Object t) {
System.out.println("processItem: " + t);
return (((MyInputRecord) t).getId() % 2 == 0) ? null
: new MyOutputRecord(((MyInputRecord) t).getId() * 2);
}
}
そしてMyItemWriter
は結果を出力します。
public class MyItemWriter extends AbstractItemWriter {
@Override
public void writeItems(List list) {
System.out.println("writeItems: " + list);
}
}
最後に、ただ完了するだけのMyBatchlet
です。
public class MyBatchlet extends AbstractBatchlet {
@Override
public String process() {
System.out.println("Running inside a batchlet");
return "COMPLETED";
}
}
これらをすべてジョブ記述子のxmlファイルに記述します。
<?xml version="1.0" encoding="UTF-8"?>
<job id="myJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd" version="1.0">
<step id="step1" next="step2">
<chunk item-count="3">
<reader ref="io.helidon.jbatch.example.jobs.MyItemReader"/>
<processor ref="io.helidon.jbatch.example.jobs.MyItemReader"/>
<writer ref="io.helidon.jbatch.example.jobs.MyItemWriter"/>
</chunk>
</step>
<step id="step2" >
<batchlet ref="io.helidon.jbatch.example.jobs.MyBatchlet"/>
</step>
</job>
ご覧のように、2ステップのジョブができあがります。最初のステップでは、MyItemReader
、MyItemProcessor
、そしてMyItemWriter
を使い、最後のステップではMyBatchlet
を使っています。
また、ここでEEとスタンドアロンの実装の大きな違いがわかります。ref
プロパティでは、io.helidon.jbatch.example.jobs.MyItemReader
のように完全修飾名を指定する必要があります。そうしないと動作しません。
では、上記のジョブを起動するエンドポイントを作りましょう。
@Path("/batch")
public class BatchResource {
private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
@GET
@Produces(MediaType.APPLICATION_JSON)
public JsonObject executeBatch() throws Exception{
BatchSPIManager batchSPIManager =
BatchSPIManager.getInstance();
batchSPIManager
.registerPlatformMode(BatchSPIManager.PlatformMode.SE);
batchSPIManager.registerExecutorServiceProvider(new
HelidonExecutorServiceProvider());
JobOperator jobOperator = getJobOperator();
Long executionId =
jobOperator.start("myJob", new Properties();
JobExecution jobExecution =
jobOperator.getJobExecution(executionId);
List<StepExecution> stepExecutions =
jobOperator.getStepExecutions(executionId);
List<String> executedSteps = new ArrayList<>();
for (StepExecution stepExecution : stepExecutions) {
executedSteps.add(stepExecution.getStepName());
}
return JSON.createObjectBuilder()
.add("Steps executed", Arrays.toString(executedSteps.toArray()))
.add("Status", jobExecution.getBatchStatus().toString())
.build();
}
}
HelidonはJBatchに対して、スタンドアロン(SE)モードで実行することを指定します。また、HelidonExecutorServiceProvider
を登録しますが、これは以下のように非常に小さなものです。
public class HelidonExecutorServiceProvider implements ExecutorServiceProvider {
@Override
public ExecutorService getExecutorService() {
return Executors.newFixedThreadPool(2);
}
}
この例では、2つのスレッドを持つFixedTheadPool
のような非常に小さなものでOKです。このプロバイダーを使って、JBatch エンジンにどの ExecutorSevice
を使用するかを正確に伝えます。
これで完成です。
Let’s run the code
mvn package
java -jar target/helidon-jbatch-example.jar
エンドポイントを呼び出します。
curl -X GET http://localhost:8080/batch
以下のようなログが出力されます。
processItem: MyInputRecord: 1
processItem: MyInputRecord: 2
processItem: MyInputRecord: 3
writeItems: [MyOutputRecord: 2, MyOutputRecord: 6]
processItem: MyInputRecord: 4
processItem: MyInputRecord: 5
processItem: MyInputRecord: 6
writeItems: [MyOutputRecord: 10]
processItem: MyInputRecord: 7
processItem: MyInputRecord: 8
processItem: MyInputRecord: 9
writeItems: [MyOutputRecord: 14, MyOutputRecord: 18]
processItem: MyInputRecord: 10
Running inside a batchlet
そして、以下の結果が得られます。
{"Steps executed":"[step1, step2]","Status":"COMPLETED"}
この結果から、バッチジョブの呼び出しおよび実行が成功したことがわかります。
Conclusion
これで、Helidonが完全なEEコンテナでなくてもJBatchを利用できることがおわかりいただけたかと思います。
このコードでもっと遊んでみたいという方は、以下のリポジトリからどうぞ。
Helidon + jBatch
https://github.com/dalexandrov/helidon-jbatch