CLOVER🍀

That was when it all began.

Amazon ECS CLIを使って、ローカルでタスクを実行する

これは、なにをしたくて書いたもの?

Amazon ECS CLIを使うと、Amazon ECSのタスク定義をローカルで実行できるらしいので、ちょっと試してみようかなと。

Amazon ECS CLI

Amazon ECS CLIは、ローカルからAmazon ECS上でのアプリケーションの構築、リリース、運用ができるとされているツールです。

Amazon ECS コマンドラインインターフェースの使用 - Amazon Elastic Container Service

GitHubリポジトリは、こちら。

GitHub - aws/amazon-ecs-cli: The Amazon ECS CLI enables users to run their applications on ECS/Fargate using the Docker Compose file format, quickly provision resources, push/pull images in ECR, and monitor running applications on ECS/Fargate.

このAmazon ECS CLIを使って、task-definition.jsonまたはdocker-compose.ecs-local.ymlからAmazon ECSタスク定義を実行できる
ようです。

ecs-cli local - Amazon Elastic Container Service

Running Tasks Locally

今回は、こちらを簡単に試してみましょう。

環境

今回の環境は、こちら。

$ docker version
Client: Docker Engine - Community
 Version:           20.10.16
 API version:       1.41
 Go version:        go1.17.10
 Git commit:        aa7e414
 Built:             Thu May 12 09:17:23 2022
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.16
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.17.10
  Git commit:       f756502
  Built:            Thu May 12 09:15:28 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.4
  GitCommit:        212e8b6fa2f44b9c21b2798135fc6fb7c53efc16
 runc:
  Version:          1.1.1
  GitCommit:        v1.1.1-0-g52de29d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Docker Composeも使うようです。

$ docker compose version
Docker Compose version v2.6.0

もっとも、この実行形態ではAmazon ECS CLIは使えなかったのですが…。後に少し細工をします。

Amazon ECS CLIをインストールする

まずは、Amazon ECS CLIをインストールします。

Amazon ECS CLI をインストールします。 - Amazon Elastic Container Service

Windows、Linux、macOSに対応しているようです。

ダウンロード。

$ sudo curl -Lo /usr/local/bin/ecs-cli https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-linux-amd64-latest

実行権限の付与。

$ sudo chmod +x /usr/local/bin/ecs-cli

署名の検証についても書かれていますが、今回はパスしておきます。

タスク定義の用意

Amazon ECS CLIを使ってローカルでタスク定義を実行するには、タスク定義を記述したtask-definition.jsonかDocker Composeの
構成ファイルが必要になるようです。

もっとも、task-definition.jsonを使っても結局Docker Composeの構成ファイルを生成するようなのですが。

とりあえず、タスク定義としてこのようなものを用意。

task-definition.json

{
  "containerDefinitions": [
    {
      "name": "nginx",
      "image": "nginx:1.22.0",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 80,
          "hostPort": 80
        }
      ],
      "links": [
        "mysql"
      ],
      "memory": 256
    },
    {
      "name": "mysql",
      "image": "mysql:8.0.29",
      "environment": [
        {
          "name": "MYSQL_ROOT_PASSWORD",
          "value": "password"
        }
      ]
    }
  ],
  "family": "webdb"
}

nginxとMySQLを含めていますが、特に関連はありません…。

Amazon ECS CLIを使って、ローカルでタスクを実行する

では、Amazon ECS CLIを使って、ローカルでAmazon ECSのタスク定義からコンテナを実行していきたいと思います。

手順的にはecs-cli local createでtask-definition.jsonからdocker-compose.ecs-local.ymlを生成するか、ecs-cli local upで
task-definition.jsonからdocker-compose.ecs-local.ymlを生成しつつ実行する方法があるようです。

とりあえず、ドキュメントとヘルプを見てみましょう。

ecs-cli local createは、Amazon ECSタスク定義からDocker Composeの構成ファイルを作成するコマンドです。

ecs-cli local create - Amazon Elastic Container Service

$ ecs-cli local create --help
NAME:
   ecs-cli local create - Creates a Compose file from an ECS task definition.

USAGE:
   ecs-cli local create [command options] [arguments...]

OPTIONS:
   --task-def-file value, -f value    Specifies the filename value that contains the task definition JSON to convert to a Docker Compose file. If one is not specified, the ECS CLI will look for task-definition.json.
   --task-def-remote value, -t value  Specifies the full Amazon Resource Name (ARN) or family:revision value of the task definition to convert to a Docker Compose file. If you specify a task definition family without a revision, the latest revision is used.
   --output value, -o value           Specifies the local filename value to write the Docker Compose file to. If one is not specified, the default is docker-compose.ecs-local.yml.
   --force                            Overwrite output docker compose file if it exists. Default compose file is docker-compose.ecs-local.yml.
   --use-role                         Uses the task role ARN instead of temporary credentials.

ecs-cli local upは、タスク定義からコンテナをローカルで実行するコマンドです。

ecs-cli local up - Amazon Elastic Container Service

$ ecs-cli local up --help
NAME:
   ecs-cli local up - Runs containers locally from an ECS Task Definition. NOTE: Creates a docker-compose file in current directory and a ecs-local-network if one doesn't exist.

USAGE:
   ecs-cli local up [command options] [arguments...]

OPTIONS:
   --task-def-compose value, -c value  Specifies the filename value that contains the Docker Compose content to run locally.
   --task-def-file value, -f value     Specifies the filename value containing the task definition JSON to convert and run locally.  If one is not specified, the ECS CLI will look for task-definition.json.
   --task-def-remote value, -t value   Specifies the full Amazon Resource Name (ARN) or family:revision value of the task definition to convert and run locally. If you specify a task definition family without a revision, the latest revision is used.
   --output value, -o value            Specifies the local filename value to write the Docker Compose file to. If one is not specified, the default is docker-compose.ecs-local.yml.
   --override value                    Specifies the local Docker Compose override filename value to use.
   --force                             Overwrite output docker compose file if it exists. Default compose file is docker-compose.ecs-local.yml.

まずはecs-cli local createを実行してみます。特にファイル名を指定しない場合は、task-definition.jsonを使うようです。

$ ecs-cli local create
INFO[0000] Reading task definition from /path/to/task-definition.json

INFO[0000] Successfully wrote docker-compose.ecs-local.yml
INFO[0000] Successfully wrote docker-compose.ecs-local.override.yml

そして、Docker Composeの構成ファイルが作成されました。

docker-compose.ecs-local.yml

version: "3.4"
services:
  mysql:
    environment:
      AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: /creds
      ECS_CONTAINER_METADATA_URI: http://169.254.170.2/v3
      MYSQL_ROOT_PASSWORD: password
    image: mysql:8.0.29
    labels:
      ecs-local.task-definition-input.type: local
      ecs-local.task-definition-input.value: /path/to/task-definition.json
    networks:
      ecs-local-network: null
  nginx:
    environment:
      AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: /creds
      ECS_CONTAINER_METADATA_URI: http://169.254.170.2/v3
    image: nginx:1.22.0
    labels:
      ecs-local.task-definition-input.type: local
      ecs-local.task-definition-input.value: /path/to/task-definition.json
    links:
    - mysql
    networks:
      ecs-local-network: null
    ports:
    - target: 80
      published: 80
networks:
  ecs-local-network:
    external: true

docker-compose.ecs-local.override.yml

version: "3.4"
services:
  mysql:
    logging:
      driver: json-file
  nginx:
    logging:
      driver: json-file

Amazon ECS CLIで使えるDocker Composeの構文は、こちらのようです。

Docker Compose ファイル構文の使用 - Amazon Elastic Container Service

続いて、ecs-cli local upしてみます。

$ ecs-cli local up

すると、docker-compose.ecs-local.ymlの上書きを聞かれます。

docker-compose.ecs-local.yml file already exists. Do you want to write over this file? [y/N]

結局のところ、必要なのはタスク定義というよりDocker Composeの構成ファイルなわけですね。

で、進めようとするとdocker-composeがないということでエラーになりました。

FATA[0000] docker-compose up failed to start due to:
exec: "docker-compose": executable file not found in $PATH

あくまでdocker-composeが必要なようです…。

仕方がないので、こんなスクリプトを作成。

/usr/local/bin/docker-compose

#!/bin/bash

docker compose "$@"

実行権限を付与。

$ sudo chmod a+x /usr/local/bin/docker-compose

気を取り直して、実行。

$ ecs-cli local up
INFO[0000] Reading task definition from /path/to/task-definition.json
 
INFO[0000] Successfully wrote docker-compose.ecs-local.yml 
INFO[0000] Successfully wrote docker-compose.ecs-local.override.yml 
INFO[0000] The network ecs-local-network already exists 
INFO[0000] The amazon-ecs-local-container-endpoints container already exists with ID fc9f664c8ea0c9b34f8782b47af7b1d51ff4c88799f54241fa37cdf16027cfea 
INFO[0000] Started container with ID fc9f664c8ea0c9b34f8782b47af7b1d51ff4c88799f54241fa37cdf16027cfea 
FATA[0000] Failed to create a SSM client to decrypt secrets due to 
failed to create a new AWS session due to Set a region using ecs-cli configure command with the --region flag or AWS_REGION environment variable or --profile flag: Set a region using ecs-cli configure command with the --region flag or AWS_REGION environment variable or --profile flag

リージョンの指定が必要みたいですね。

AWS_REGIONを指定して実行。

$ AWS_REGION=ap-northeast-1 ecs-cli local up

今度は、うまくいきました。

$ AWS_REGION=ap-northeast-1 ecs-cli local up
INFO[0000] Reading task definition from /path/to/task-definition.json

docker-compose.ecs-local.yml file already exists. Do you want to write over this file? [y/N]
y
INFO[0001] Successfully wrote docker-compose.ecs-local.yml
INFO[0001] docker-compose.ecs-local.override.yml already exists, skipping write.
INFO[0001] The network ecs-local-network already exists
INFO[0001] The amazon-ecs-local-container-endpoints container already exists with ID fc9f664c8ea0c9b34f8782b47af7b1d51ff4c88799f54241fa37cdf16027cfea
INFO[0001] Started container with ID fc9f664c8ea0c9b34f8782b47af7b1d51ff4c88799f54241fa37cdf16027cfea
INFO[0001] Using docker-compose.ecs-local.yml, docker-compose.ecs-local.override.yml files to start containers
[+] Running 2/2
 â ¿ Container ecs-cli-local-mysql-1  Started                                                                                                                               3.4s
 â ¿ Container ecs-cli-local-nginx-1  Started                                                                                                                               2.1s

起動しているコンテナの確認。

$ ecs-cli local ps
CONTAINER ID        IMAGE               STATUS              PORTS                               NAMES                    TASKDEFINITION
779cd34ab1e5        nginx:1.22.0        Up 4 minutes        0.0.0.0:80->80/tcp, :::80->80/tcp   /ecs-cli-local-nginx-1   /path/to/task-definition.json
e4bb51990b0a        mysql:8.0.29        Up 4 minutes        :0->3306/tcp, :0->33060/tcp         /ecs-cli-local-mysql-1   /path/to/task-definition.json

nginxについては、ポートマッピングをしていたのでローカルポートでアクセスできます。

$ curl -i localhost
HTTP/1.1 200 OK
Server: nginx/1.22.0
Date: Thu, 02 Jun 2022 16:40:51 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Mon, 23 May 2022 23:59:19 GMT
Connection: keep-alive
ETag: "628c1fd7-267"
Accept-Ranges: bytes

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

ちなみに、docker container psで見るとamazon-ecs-local-container-endpointsというコンテナがあったりします。

$ docker container ps
CONTAINER ID   IMAGE                                         COMMAND                  CREATED             STATUS             PORTS                               NAMES
779cd34ab1e5   nginx:1.22.0                                  "/docker-entrypoint.…"   5 minutes ago       Up 5 minutes       0.0.0.0:80->80/tcp, :::80->80/tcp   ecs-cli-local-nginx-1
e4bb51990b0a   mysql:8.0.29                                  "docker-entrypoint.s…"   5 minutes ago       Up 5 minutes       3306/tcp, 33060/tcp                 ecs-cli-local-mysql-1
fc9f664c8ea0   amazon/amazon-ecs-local-container-endpoints   "/local-container-en…"   About an hour ago   Up About an hour   80/tcp                              amazon-ecs-local-container-endpoints

コンテナを停止する時は、ecs-cli local down。

ecs-cli local down - Amazon Elastic Container Service

$ ecs-cli local down

とりあえずはこんなところでしょうか。

だいぶ限定的ではありますが、タスク定義をローカルで試してみる手段のひとつとして把握していてもよいのかな、と思いました。

Spring BatchでItemProcessorを複数適用してみる

これは、なにをしたくて書いたもの?

Spring Batchで、複数のItemProcessorを使うパターンをちょっと試しておこうかなと。

CompositeItemProcessor

結論を言うと、CompositeItemProcessorを使うことになります。

ドキュメントとしてはこちらに載っています。

Item processing / Chaining ItemProcessors

ItemProcessorではItemを変換したり検証したり、なにか処理をできるわけですが。複数のItemProcessorをつなげる場合に
CompositeItemProcessorを使います。

ドキュメントを見たらだいたい雰囲気はわかるのですが、実際に自分でも書いてみます。

お題

今回のお題は、以下とします。

  • 書籍データをCSVなStringとしてItemReaderが提供
  • ItemProcessorで…
    • split
    • 自分で用意したクラスに変換
  • ItemWriterでログ出力

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.3 2022-04-19
OpenJDK Runtime Environment (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1)
OpenJDK 64-Bit Server VM (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.3, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-113-generic", arch: "amd64", family: "unix"

Spring Batchが使用するデータベースには、MySQLを使用します。MySQLは、172.17.0.2で動作しているものとします。

$ mysql --version
mysql  Ver 8.0.29 for Linux on x86_64 (MySQL Community Server - GPL)

プロジェクトを作成する

まずはSpring Bootプロジェクトを作成します。依存関係にはbatchとmysqlを追加。

$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=2.7.0 \
  -d javaVersion=17 \
  -d name=batch-chaining-itemprocessors \
  -d groupId=org.littlewings \
  -d artifactId=batch-chaining-itemprocessors \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=org.littlewings.spring.batch \
  -d dependencies=batch,mysql \
  -d baseDir=batch-chaining-itemprocessors | tar zxvf -

プロジェクト内に移動。

$ cd batch-chaining-itemprocessors

生成されたソースコードは削除しておきます。

$ rm src/main/java/org/littlewings/spring/batch/BatchChainingItemprocessorsApplication.java src/test/java/org/littlewings/spring/batch/BatchChainingItemprocessorsApplicationTests.java

Maven依存関係やプラグインの設定は、こちら。

        <properties>
                <java.version>17</java.version>
        </properties>
        <dependencies>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-batch</artifactId>
                </dependency>

                <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <scope>runtime</scope>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>org.springframework.batch</groupId>
                        <artifactId>spring-batch-test</artifactId>
                        <scope>test</scope>
                </dependency>
        </dependencies>

        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                </plugins>
        </build>

最終的に変換先となるクラスは、こちらとします。

src/main/java/org/littlewings/spring/batch/Book.java

package org.littlewings.spring.batch;

public class Book {
    String isbn;
    String title;
    Integer price;

    public static Book create(String isbn, String title, Integer price) {
        Book book = new Book();
        book.setIsbn(isbn);
        book.setTitle(title);
        book.setPrice(price);

        return book;
    }

    // getter/setterは省略
}

mainクラスはこちら。

src/main/java/org/littlewings/spring/batch/App.java

package org.littlewings.spring.batch;

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableBatchProcessing
public class App {
    public static void main(String... args) {
        SpringApplication.run(App.class, args);
    }
}

アプリケーションの設定は、このようにします。

src/main/resources/application.properties

spring.datasource.url=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8
spring.datasource.username=kazuhira
spring.datasource.password=password

spring.batch.jdbc.initialize-schema=always

ここから、ItemReader、ItemProcessor、ItemWriter、Jobの定義の順に書いていきます。

ItemReader/ItemProcessor/ItemWriter/Jobを定義する

変換元のデータを提供するItemReaderはこちら。

src/main/java/org/littlewings/spring/batch/StringItemReader.java

package org.littlewings.spring.batch;

import java.util.Iterator;
import java.util.List;

import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.item.support.AbstractItemStreamItemReader;

public class StringItemReader extends AbstractItemStreamItemReader<String> {
    Iterator<String> iterator;

    public StringItemReader() {
        List<String> bookAsStrings = List.of(
                "978-4798142470,Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発,4400",
                "978-4774182179,[改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ,4180",
                "978-1492076988,Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications,6265",
                "978-1484237236,The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud,7361",
                "978-4798161488,MySQL徹底入門 第4版 MySQL 8.0対応,4180",
                "978-4797393118,基礎からのMySQL 第3版 (基礎からシリーズ),6038",
                "978-4873116389,実践ハイパフォーマンスMySQL 第3版,5280",
                "978-4295000198,やさしく学べるMySQL運用・管理入門【5.7対応】,2860",
                "978-4798147406,詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE),3960",
                "978-4774170206,MariaDB&MySQL全機能バイブル,3860"
        );

        iterator = bookAsStrings.iterator();
    }

    @Override
    public String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
        if (iterator.hasNext()) {
            return iterator.next();
        }

        return null;
    }
}

こんな感じですね。

"978-4798142470,Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発,4400",

こちらをsplitするItemProcessor。StringからList<String>に変換します。

src/main/java/org/littlewings/spring/batch/SplitItemProcessor.java

package org.littlewings.spring.batch;

import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemProcessor;

public class SplitItemProcessor implements ItemProcessor<String, List<String>> {
    Logger logger = LoggerFactory.getLogger(SplitItemProcessor.class);

    @Override
    public List<String> process(String item) throws Exception {
        logger.info("split: {}", item);
        return Arrays.asList(item.split(","));
    }
}

そして、List<String>からBookに変換するItemProcessor。

src/main/java/org/littlewings/spring/batch/ToBookItemProcessor.java

package org.littlewings.spring.batch;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemProcessor;

public class ToBookItemProcessor implements ItemProcessor<List<String>, Book> {
    Logger logger = LoggerFactory.getLogger(ToBookItemProcessor.class);

    @Override
    public Book process(List<String> item) throws Exception {
        logger.info("to book: {}", String.join(", ", item));

        return Book.create(
                item.get(0),
                item.get(1),
                Integer.parseInt(item.get(2))
        );
    }
}

ログ出力を行うItemWriter。ここでは、ItemをBookとして扱うように作成しています。

src/main/java/org/littlewings/spring/batch/LoggingItemWriter.java

package org.littlewings.spring.batch;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemWriter;

public class LoggingItemWriter implements ItemWriter<Book> {
    Logger logger = LoggerFactory.getLogger(LoggingItemWriter.class);

    @Override
    public void write(List<? extends Book> books) throws Exception {
        books.forEach(book ->
                logger.info("write: isbn = {}, title = {}, price = {}", book.getIsbn(), book.getTitle(), book.getPrice())
        );
    }
}

ItemProcessorにもログ出力は含めているのですが。

そしてJob定義。

src/main/java/org/littlewings/spring/batch/JobConfig.java

package org.littlewings.spring.batch;

import java.util.List;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.support.CompositeItemProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JobConfig {
    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory) {
        return jobBuilderFactory
                .get("job")
                .incrementer(new RunIdIncrementer())
                .start(step(null))
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory) {
        return stepBuilderFactory
                .get("step")
                .<String, Book>chunk(3)
                .reader(itemReader())
                .processor(compositeItemProcessor())
                .writer(itemWriter())
                .build();
    }

    @Bean
    @StepScope
    public StringItemReader itemReader() {
        return new StringItemReader();
    }

    @Bean
    @StepScope
    public SplitItemProcessor splitItemProcessor() {
        return new SplitItemProcessor();
    }

    @Bean
    @StepScope
    public ToBookItemProcessor toBookItemProcessor() {
        return new ToBookItemProcessor();
    }

    @Bean
    @StepScope
    public CompositeItemProcessor<String, Book> compositeItemProcessor() {
        List<ItemProcessor<?, ?>> delegates = List.of(
                splitItemProcessor(),  // String -> List<String>
                toBookItemProcessor()  // List<String> -> Book
        );

        CompositeItemProcessor<String, Book> itemProcessor = new CompositeItemProcessor<>();
        itemProcessor.setDelegates(delegates);

        return itemProcessor;
    }

    @Bean
    @StepScope
    public LoggingItemWriter itemWriter() {
        return new LoggingItemWriter();
    }
}

ItemProcessorにフォーカスして取り上げます。

まずは、ここまで作成したItemProcessorをBean定義。

    @Bean
    @StepScope
    public SplitItemProcessor splitItemProcessor() {
        return new SplitItemProcessor();
    }

    @Bean
    @StepScope
    public ToBookItemProcessor toBookItemProcessor() {
        return new ToBookItemProcessor();
    }

そして、CompositeItemProcessor。

    @Bean
    @StepScope
    public CompositeItemProcessor<String, Book> compositeItemProcessor() {
        List<ItemProcessor<?, ?>> delegates = List.of(
                splitItemProcessor(),  // String -> List<String>
                toBookItemProcessor()  // List<String> -> Book
        );

        CompositeItemProcessor<String, Book> itemProcessor = new CompositeItemProcessor<>();
        itemProcessor.setDelegates(delegates);

        return itemProcessor;
    }

使用するItemProcessorをListにまとめ、CompositeItemProcessor#setDelegatesで設定します。
Listには、ItemProcessorを適用する順番で登録します。

これで、String → List<String> → Bookという変換が行われることになります。

あとは、CompositeItemProcessorをItemProcessorとしてStepに登録すればOKです。

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory) {
        return stepBuilderFactory
                .get("step")
                .<String, Book>chunk(3)
                .reader(itemReader())
                .processor(compositeItemProcessor())
                .writer(itemWriter())
                .build();
    }

動作確認する

準備はできたので、パッケージングして

$ mvn package

実行。

$ java -jar target/batch-chaining-itemprocessors-0.0.1-SNAPSHOT.jar

ログ。

2022-05-29 00:02:09.061  INFO 114305 --- [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2022-05-29 00:02:09.225  INFO 114305 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=job]] launched with the following parameters: [{run.id=1}]
2022-05-29 00:02:09.335  INFO 114305 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step]
2022-05-29 00:02:09.417  INFO 114305 --- [           main] o.l.spring.batch.SplitItemProcessor      : split: 978-4798142470,Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発,4400
2022-05-29 00:02:09.423  INFO 114305 --- [           main] o.l.spring.batch.ToBookItemProcessor     : to book: 978-4798142470, Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発, 4400
2022-05-29 00:02:09.424  INFO 114305 --- [           main] o.l.spring.batch.SplitItemProcessor      : split: 978-4774182179,[改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ,4180
2022-05-29 00:02:09.424  INFO 114305 --- [           main] o.l.spring.batch.ToBookItemProcessor     : to book: 978-4774182179, [改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ, 4180
2022-05-29 00:02:09.424  INFO 114305 --- [           main] o.l.spring.batch.SplitItemProcessor      : split: 978-1492076988,Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications,6265
2022-05-29 00:02:09.424  INFO 114305 --- [           main] o.l.spring.batch.ToBookItemProcessor     : to book: 978-1492076988, Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications, 6265
2022-05-29 00:02:09.430  INFO 114305 --- [           main] o.l.spring.batch.LoggingItemWriter       : write: isbn = 978-4798142470, title = Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発, price = 4400
2022-05-29 00:02:09.430  INFO 114305 --- [           main] o.l.spring.batch.LoggingItemWriter       : write: isbn = 978-4774182179, title = [ 改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ, price = 4180
2022-05-29 00:02:09.431  INFO 114305 --- [           main] o.l.spring.batch.LoggingItemWriter       : write: isbn = 978-1492076988, title = Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications, price = 6265
2022-05-29 00:02:09.465  INFO 114305 --- [           main] o.l.spring.batch.SplitItemProcessor      : split: 978-1484237236,The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud,7361
2022-05-29 00:02:09.465  INFO 114305 --- [           main] o.l.spring.batch.ToBookItemProcessor     : to book: 978-1484237236, The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud, 7361
2022-05-29 00:02:09.466  INFO 114305 --- [           main] o.l.spring.batch.SplitItemProcessor      : split: 978-4798161488,MySQL徹底入門 第4 版 MySQL 8.0対応,4180
2022-05-29 00:02:09.466  INFO 114305 --- [           main] o.l.spring.batch.ToBookItemProcessor     : to book: 978-4798161488, MySQL徹底入門  第4版 MySQL 8.0対応, 4180
2022-05-29 00:02:09.466  INFO 114305 --- [           main] o.l.spring.batch.SplitItemProcessor      : split: 978-4797393118,基礎からのMySQL 第3版 (基礎からシリーズ),6038
2022-05-29 00:02:09.467  INFO 114305 --- [           main] o.l.spring.batch.ToBookItemProcessor     : to book: 978-4797393118, 基礎からのMySQL 第3版 (基礎からシリーズ), 6038
2022-05-29 00:02:09.467  INFO 114305 --- [           main] o.l.spring.batch.LoggingItemWriter       : write: isbn = 978-1484237236, title = The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud, price = 7361
2022-05-29 00:02:09.467  INFO 114305 --- [           main] o.l.spring.batch.LoggingItemWriter       : write: isbn = 978-4798161488, title = MySQL徹底入門 第4版 MySQL 8.0対応, price = 4180
2022-05-29 00:02:09.467  INFO 114305 --- [           main] o.l.spring.batch.LoggingItemWriter       : write: isbn = 978-4797393118, title = 基礎からのMySQL 第3版 (基礎からシリーズ), price = 6038
2022-05-29 00:02:09.492  INFO 114305 --- [           main] o.l.spring.batch.SplitItemProcessor      : split: 978-4873116389,実践ハイパフォーマンスMySQL 第3版,5280
2022-05-29 00:02:09.493  INFO 114305 --- [           main] o.l.spring.batch.ToBookItemProcessor     : to book: 978-4873116389, 実践ハイパフォ ーマンスMySQL 第3版, 5280
2022-05-29 00:02:09.494  INFO 114305 --- [           main] o.l.spring.batch.SplitItemProcessor      : split: 978-4295000198,やさしく学べるMySQL運用・管理入門【5.7対応】,2860
2022-05-29 00:02:09.494  INFO 114305 --- [           main] o.l.spring.batch.ToBookItemProcessor     : to book: 978-4295000198, やさしく学べるMySQL運用・管理入門【5.7対応】, 2860
2022-05-29 00:02:09.495  INFO 114305 --- [           main] o.l.spring.batch.SplitItemProcessor      : split: 978-4798147406,詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE),3960
2022-05-29 00:02:09.495  INFO 114305 --- [           main] o.l.spring.batch.ToBookItemProcessor     : to book: 978-4798147406, 詳解MySQL 5.7  止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE), 3960
2022-05-29 00:02:09.496  INFO 114305 --- [           main] o.l.spring.batch.LoggingItemWriter       : write: isbn = 978-4873116389, title = 実践ハイパフォーマンスMySQL 第3版, price = 5280
2022-05-29 00:02:09.496  INFO 114305 --- [           main] o.l.spring.batch.LoggingItemWriter       : write: isbn = 978-4295000198, title = やさしく学べるMySQL運用・管理入門【5.7対応】, price = 2860
2022-05-29 00:02:09.496  INFO 114305 --- [           main] o.l.spring.batch.LoggingItemWriter       : write: isbn = 978-4798147406, title = 詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE), price = 3960
2022-05-29 00:02:09.539  INFO 114305 --- [           main] o.l.spring.batch.SplitItemProcessor      : split: 978-4774170206,MariaDB&MySQL全機 能バイブル,3860
2022-05-29 00:02:09.539  INFO 114305 --- [           main] o.l.spring.batch.ToBookItemProcessor     : to book: 978-4774170206, MariaDB&MySQL全機能バイブル, 3860
2022-05-29 00:02:09.539  INFO 114305 --- [           main] o.l.spring.batch.LoggingItemWriter       : write: isbn = 978-4774170206, title = MariaDB&MySQL全機能バイブル, price = 3860
2022-05-29 00:02:09.568  INFO 114305 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [step] executed in 233ms
2022-05-29 00:02:09.624  INFO 114305 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=job]] completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED] in 350ms

OKですね。

まとめ

CompositeItemProcessorを使って、ItemProcessorを複数適用してみました。

特にハマりどころもなくあっさりと動かせました。よく使いたくなるものだと思うので、1度確認しておきたかったんですよね。

覚えておきましょう。