Asakusa Frameworkで Hello, World!

# はじめに
この記事はJavaバッチフレームワークの[Asakusa Framework](http://docs.asakusafw.com/latest/release/ja/html/index.html)で簡単なバッチアプリケーションを作成する方法を共有します。
公式には[Asakusa Framework チュートリアル](http://docs.asakusafw.com/basic-tutorial/0.10/release/ja/html/index.html)が存在しますが、本記事では手軽にAsakusaの開発や運用について概要を理解して雰囲気をつかんでもらえることを目的としています。

# 動作環境
以下の環境で動作確認をしています。

* Windows 10 (64bit)
* JDK 1.8.0_161
* Gradle 4.5

（以下の手順を試す前に、JDKとGradleのインストールを済ませておいてください。）

# やりたいこと
紹介するサンプルアプリケーションは、入力ファイル（内容は`Hello`だけ)を読み込んで、内容を`Hello, World!`に更新してファイルを出力する簡単なものです。
DFDの形式にすると以下のようなものになります。
![helloworld_dfd.png](https://qiita-image-store.s3.amazonaws.com/0/23102/2139772e-fdf7-a74d-49f8-3cb45b63714c.png)


# 開発環境の準備
以下のフォルダを作成して、サンプルアプリケーションのプロジェクト用フォルダとします。

```
C:¥asakusa-develop¥helloworld
```

作成したプロジェクト用フォルダの配下にGradleのビルドスクリプトファイル（`build.gradle`）を作成します。
今回のサンプルアプリケーションはWindowsのJVM上で実行させるため、[Asakusa Vanilla](http://docs.asakusafw.com/latest/release/ja/html/vanilla/index.html)を構成しています。
ここの設定を変えることでHadoopやM3BP用のアプリケーションを生成することも可能です。
作成したGradleスクリプトの詳細については、[Asakusa Gradle Plugin リファレンス](http://docs.asakusafw.com/latest/release/ja/html/application/gradle-plugin-reference.html)を参照してください。

```groovy:build.gradle
group 'com.example'

buildscript {
    repositories {
        maven { url 'http://asakusafw.s3.amazonaws.com/maven/releases' }
        maven { url 'http://asakusafw.s3.amazonaws.com/maven/snapshots' }
    }
    dependencies {
        classpath group: 'com.asakusafw.gradle', name: 'asakusa-distribution', version: '0.10.0'
    }
}

apply plugin: 'asakusafw-sdk'
apply plugin: 'asakusafw-organizer'
apply plugin: 'asakusafw-vanilla'
apply plugin: 'eclipse'

asakusafwOrganizer {
    vanilla.enabled true
    profiles.prod {
        hadoop.embed true
    }
}
```

以下のGradleコマンドを実行するとEclipseのプロジェクトとしてインポート可能な構成ファイルが生成されます。（プロジェクトフォルダの下で実行してください）

```
C:¥asakusa-develop¥helloworld>gradle eclipse
```

IntelliJ IDEAを利用したい場合は、公式の[IntelliJ IDEAの利用](http://docs.asakusafw.com/latest/release/ja/html/application/intellij-idea.html)を参照してください。
なお、Eclipseを使うのであれば、[Shafu](http://docs.asakusafw.com/jinrikisha/ja/html/shafu.html)（Asakusa開発用のEclipseプラグイン）の利用を検討してください。

# データモデルクラスの生成
データモデルを定義する[DMDL(Data Model Definition Language)](http://docs.asakusafw.com/latest/release/ja/html/dmdl/start-guide.html)スクリプトファイルを作成します。
以下のスクリプトでは、入力モデルと出力モデルはそれぞれ`input`と`output`という名前で、[Direct I/O CSV](http://docs.asakusafw.com/latest/release/ja/html/directio/csv-format.html)で入出力を行う設定になっています。
以下のフォルダを作成してその配下にスクリプトファイル（`models.dmdl`）を作成します。

```
C:¥asakusa-develop¥helloworld¥src¥main¥dmdl
```

```text:models.dmdl
@directio.csv
input = {
  value : TEXT;
};

@directio.csv
output = {
  value : TEXT;
};
```

以下のGradleコマンドを実行すると、作成したスクリプトファイルを元にAsakusa Frameworkで利用可能なデータモデルクラスが生成されます。（プロジェクトフォルダの下で実行してください）

```
C:¥asakusa-develop¥helloworld>gradle compileDMDL
```

# 演算子クラスの作成
以下のフォルダを作成してその配下に`com.example.operator.HelloWorldOperator`クラスを作成します。
実装する演算子の処理は入力ファイルのvalueに値（`, World!`）を追加する処理なので、[`@Update`](http://docs.asakusafw.com/latest/release/ja/html/dsl/operators.html#update-operator)演算子を実装します。
演算子に関する詳細については、[Asakusa DSL演算子リファレンス](http://docs.asakusafw.com/latest/release/ja/html/dsl/operators.html)を参照してください。
`com.example.modelgen.dmdl.model.Input`クラスは先ほどのDMDLから自動生成されたクラスです。

```
C:¥asakusa-develop¥helloworld¥src¥main¥java
```

```java:com.example.operator.HelloWorldOperator
package com.example.operator;

import com.asakusafw.vocabulary.operator.Update;
import com.example.modelgen.dmdl.model.Input;

public abstract class HelloWorldOperator {

    @Update
    public void appendMessage(Input input) {
        input.setValueAsString(input.getValueAsString() + ", World!");
    }
}
```

# 入出力定義クラスの作成
入力定義クラスと出力定義クラスを作成します。それぞれ、`AbstractInputCsvInputDescription`と`AbstractOutputCsvOutputDescription`を継承して実装しています。これらのクラスもDMDLから自動生成されています。
ファイル入出力クラスの詳細については、[Direct I/O ユーザーガイド - ファイルの入出力](http://docs.asakusafw.com/latest/release/ja/html/directio/user-guide.html#id41)を参照するとよいでしょう。

## 入力定義クラスの作成
```java:com.example.jobflow.InputFile
package com.example.jobflow;

import com.example.modelgen.dmdl.csv.AbstractInputCsvInputDescription;

public class InputFile extends AbstractInputCsvInputDescription {

    @Override
    public String getBasePath() {
        return "input";
    }

    @Override
    public String getResourcePattern() {
        return "hello.csv";
    }
}
```

## 出力定義クラスの作成
```java:com.example.jobflow.OutputFile
package com.example.jobflow;

import com.example.modelgen.dmdl.csv.AbstractOutputCsvOutputDescription;

public class OutputFile extends AbstractOutputCsvOutputDescription {

    @Override
    public String getBasePath() {
        return "output";
    }

    @Override
    public String getResourcePattern() {
        return "helloworld.csv";
    }

}
```

# ジョブフロークラスの作成
以下のジョブフロークラスでは、コンストラクタでデータフローの`Import`に入力定義クラス（`InputFile`）を設定し、`Export`に出力定義クラス（`OutputFile`）を設定しています。
describeメソッドにデータフローを記述します。サンプルでは、入力モデル（`input`）から更新演算子（`appendMessage`）へ結線してその結果を出力モデル（`output`）へ出力しています。
詳細については、[Asakusa DSLユーザーガイド - Flow DSL](http://docs.asakusafw.com/latest/release/ja/html/dsl/user-guide.html#flow-dsl) を参照してください。

```java:com.example.jobflow.HelloWorldJob
package com.example.jobflow;

import com.asakusafw.vocabulary.flow.Export;
import com.asakusafw.vocabulary.flow.FlowDescription;
import com.asakusafw.vocabulary.flow.Import;
import com.asakusafw.vocabulary.flow.In;
import com.asakusafw.vocabulary.flow.JobFlow;
import com.asakusafw.vocabulary.flow.Out;
import com.asakusafw.vocabulary.flow.util.CoreOperatorFactory;
import com.example.modelgen.dmdl.model.Input;
import com.example.modelgen.dmdl.model.Output;
import com.example.operator.HelloWorldOperatorFactory;
import com.example.operator.HelloWorldOperatorFactory.AppendMessage;

@JobFlow(name = "helloWorldJob")
public class HelloWorldJob extends FlowDescription {

    final In<Input> input;
    final Out<Output> output;
    
    public HelloWorldJob(
            @Import(name = "input", description = InputFile.class)
            In<Input> input,
            @Export(name = "output", description = OutputFile.class)
            Out<Output> output) {
        this.input = input;
        this.output = output;
    }
    
    @Override
    protected void describe() {
        CoreOperatorFactory core = new CoreOperatorFactory();
        HelloWorldOperatorFactory operator = new HelloWorldOperatorFactory();
        AppendMessage appendedMessage = operator.appendMessage(input);
        output.add(core.restructure(appendedMessage.out, Output.class));
    }
}
```

# バッチクラスの作成
以下のバッチクラスは、ジョブフロークラス（`HelloWorldJob`）を実行するだけです。
バッチクラスについては、[Asakusa DSLユーザーガイド - Batch DSL](http://docs.asakusafw.com/latest/release/ja/html/dsl/user-guide.html#batch-dsl)を参照してください。

```java:com.example.batch.HelloWorldBatch
package com.example.batch;

import com.asakusafw.vocabulary.batch.Batch;
import com.asakusafw.vocabulary.batch.BatchDescription;
import com.example.jobflow.HelloWorldJob;

@Batch(name = "example.helloWorld")
public class HelloWorldBatch extends BatchDescription {

    @Override
    protected void describe() {
        run(HelloWorldJob.class).soon();
    }
}
```

ここまでで、ソースフォルダの配下は次のような構成になっていると思います。
![source_folder.png](https://qiita-image-store.s3.amazonaws.com/0/23102/17f9b0bb-f665-2494-aad7-37fdf09e0d43.png)

# アプリケーションのビルド
次のGradleコマンドでサンプルアプリケーションのデプロイメントアーカイブファイルを生成します。（プロジェクトフォルダの下で実行してください）

```
C:¥asakusa-develop¥helloworld>gradle assemble
```

実行に成功すると`build`フォルダの配下にデプロイメントアーカイブファイルが作成されています。

```
C:¥asakusa-develop¥helloworld¥build¥asakusafw-helloworld.tar.gz
```

# アプリケーションのデプロイ
今回はWindows上にデプロイするので（商用などの場合はHadoopやM3BP環境へデプロイするのですが。。）Windows環境変数に`ASAKUSA_HOME`を追加します。

| 変数       | 値                       |
|:-----------|:-------------------------|
|ASAKUSA_HOME|C:¥asakusa-develop¥asakusa|

生成したデプロイメントアーカイブファイル（`asakusafw-helloworld.tar.gz`）をASAKUSA_HOMEの配下に展開します。
ASAKUSA_HOMEの下で以下のコマンドを実行します。

```
C:¥asakusa-develop¥asakusa>java -jar tools¥bin¥setup.jar
setup: C:¥asakusa-develop¥asakusa
framework version: 0.10.0
installation path is not a POSIX file system: C:¥asakusa-develop¥asakusa
```

# アプリケーションの実行
## 入力ファイルの配備
バッチの入力ファイルを以下のフォルダへ配備します。デフォルトではユーザーホームディレクトリ以下の`target¥testing¥directio`フォルダの配下に配備します。
入力定義クラスで設定した`basePath`（`getBasePath`メソッドの返り値）と`resourcePattern`（`getResourcePattern`メソッドの返り値）に一致するようにします。

```
C:¥...<ユーザーホームディレクトリ>...¥target¥testing¥directio¥input¥
```

```text:hello.csv
Hello
```

## Asakusa CLIで実行する
このサンプルでは、[Asakusa CLI](http://docs.asakusafw.com/latest/release/ja/html/cli/index.html)を利用するため（商用では、[YAESS](http://docs.asakusafw.com/latest/release/ja/html/yaess/index.html)で実行することになります）、WindowsのPATH環境変数に以下の値を追加します。

```
%ASAKUSA_HOME%¥bin
```

以下のコマンドでバッチアプリケーションを実行します。

```
asakusa run vanilla.example.helloWorld
```

実行結果として以下のファイルが出力されていることを確認します。

```
C:¥...<ユーザーホームディレクトリ>...¥target¥testing¥directio¥output¥
```
```text:helloworld.csv
"Hello, World!"
```

# 最後に
Asakusa Frameworkのversionも0.10.0になって開発や運用の敷居が下がってきているので、このタイミングでまとめてみようと思い立ちこの記事を書いてみましたが如何だったでしょうか？まだ書き足りないことも多いのですが、また次回の記事で紹介していければと思います。