Spark Web Framework の使い方 : Getting Start 編

今回から何回かに分けて、Java界隈で人気らしい「Spark Framework」の使い方について書いていきたいと思います。

Spark Frameworkの公式ページはこちら -> [Spark Framework: An expressive web framework for Kotlin and Java](http://sparkjava.com/)

SparkやマイクロWebサービスがどんなものかとかは、公式ページや[Qiitaの他の方の記事](https://qiita.com/tags/SparkFramework)におまかせして、ここでは私なりのSparkの使い方を解説していきます。

## 今回使用するライブラリ

* spark-core 2.7.1
* spark-debug-tools 0.5


## Hello World!

### 実装

では、さっそくSparkの使い方を見ていきましょう。
まずは[公式アナウンスのとおり](http://sparkjava.com/tutorials/maven-setup)POMファイルを記述します。

* pom.xml

```xml
<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
    <dependency>
        <groupId>com.sparkjava</groupId>
        <artifactId>spark-core</artifactId>
        <version>2.7.1</version>
    </dependency>
</dependencies>
```

続いて、アプリのエントリーポイントを作成します。

* Application.java

```java
import static spark.Spark.get;

public class Application {

    public static void main(String[] args) { 
        // getメソッドの第一引数にはリクエストをマッピングするパス、第二引数にはリクエストを処理するハンドラを記述します。
        get("/", (req, res) -> "hello world!");
    }

}
```

ハンドラは`Route`インターフェイスを実装して作成しますが、`@FunctionalInterface`なのでJava 8以降なら例のようにラムダ式で実装することができます。寄り道して`Route`インターフェイスのシグネチャを見てみましょう。

```java
@FunctionalInterface
public interface Route {

    Object handle(Request request, Response response) throws Exception;

}

```

`Route`インターフェイスはリクエストとレスポンスを引数にとり、レスポンスボディを返却するよう実装します。

ここでいうリクエストとレスポンスはSpark独自の型で、`HttpServletRequest`なんかとは異なります。

### 実行

さて、実装したアプリを起動してみましょう。
私はEclipseで実装しているので、「実行」->「Javaアプリケーション」->「作成したクラスのMainメソッド」と選択していきます。

> 実際の起動時に`java`コマンドでMainメソッドを指定するのはナンセンスですね。

```console
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
```

実行すると、コンソールに上記のようなログが出力されると思いますが、ちゃんと起動しています。

> SparkはロギングをSLF4Jで実施していますが、今回のアプリではロギング実装ライブラリを入れていないので、警告が出ています。

実行したアプリにCURLでアクセスしてみましょう。もちろんブラウザでURLを叩いてもOKです。

```console
$ curl -s http://localhost:4567
hello world!
```

なんとも味気ないですが、「Hello World!」が出力されました。

## Sparkアプリのデバッグ

Eclipseでデバッグするから問題ないぜ！という方もいると思いますが、
Sparkでは公式で[デバッグツール](https://github.com/perwendel/spark-debug-tools)が提供されています。

spark-debug-toolsは、以下のように簡単に適用できます。

* pom.xml

```xml
<dependency>
    <groupId>com.sparkjava</groupId>
    <artifactId>spark-debug-tools</artifactId>
    <version>0.5</version>
</dependency>
```

* Application.java

```java
import static spark.Spark.get;
import static spark.debug.DebugScreen.enableDebugScreen;

public class Application {

    public static void main(String[] args) { 
        // (1) `enableDebugScreen`メソッドでデバッグ画面が有効になります。
        enableDebugScreen();

        // (2) ハンドラで例外（`Exception`）がスローされると、自動的にデバッグ画面に遷移するようになります。
        get("/", (req, res) -> { throw new Exception("error"); });
    }

}
```

> デバッグ画面はFree Markerで実装されているようです。たまにデバッグ画面のレンダリングに失敗することもあるので、Free Markerの知識があると使いやすいと思います。

## Sparkプロジェクトテンプレートの作成

ここまでの動作確認を踏まえて、Sparkアプリのプロジェクトテンプレートを作成しておきます。

* pom.xml

```xml
<properties>
    <!-- (1) Settings -->
    <java.version>1.8</java.version>
    <encoding>UTF-8</encoding>
    <spark.mainClass>io.github.yoshikawaa.spark.sample.Application</spark.mainClass>
    <!-- (2) Maven Properties -->
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <project.build.sourceEncoding>${encoding}</project.build.sourceEncoding>
    <project.reporting.outputEncoding>${encoding}</project.reporting.outputEncoding>
    <exec.mainClass>${spark.mainClass}</exec.mainClass>
</properties>

<build>
    <!-- (3) Create Fat Jar -->
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifest>
                        <mainClass>${spark.mainClass}</mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

<dependencies>
    <!-- (4) Spark -->
    <dependency>
        <groupId>com.sparkjava</groupId>
        <artifactId>spark-core</artifactId>
        <version>2.7.1</version>
    </dependency>
    <dependency>
        <groupId>com.sparkjava</groupId>
        <artifactId>spark-debug-tools</artifactId>
        <version>0.5</version>
    </dependency>

    <!-- (5) Logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.25</version>
    </dependency>

    <!-- (6) Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.20</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
```

| no. | description |
|-----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| (1) | プロジェクトのセッティングを集約定義します。基本的に、ここと`dependencies`だけ変えればそれなりにアプリが作れることを意識します。 |
| (2) |  Mavenプラグインにセットするプロパティを定義します。<br> * `maven.compiler.source`, `maven.compiler.target`は最初に出てきた、Java 8コンパイルするための定義です。<br>  * `project.build.sourceEncoding`, `project.reporting.outputEncoding`はソースやJavaDocをエンコードするための定義で、Eclipseでの文字化け防止に役立ちます。<br> * `exec.mainClass`はExec Maven Pluginで実行するときのMainクラスを指定するための定義で、Mavenからアプリを実行するときに使います。 |
| (3) | パッケージングしたJarを実行するときに`java`コマンドでMainクラスを指定しなければならない！、実行してみたけど依存ライブラリが足りない！などの問題をクリアするため、Maven Assembly Pluginで依存関係も含めてまとめたJarを作成します。 |
| (4) | `spark-core`と`spark-debug-tools`への依存関係を定義します。 |
| (5) | デフォルトロギング実装として`slf4j-simple`への依存関係を定義します。ロギングについては、次回以降解説します。 |
| (6) | 今回触れませんでしたが、コーディングサポートのため`lombok`への依存関係を定義します。 |

* Application.java

```java
import static spark.Spark.get;
import static spark.debug.DebugScreen.enableDebugScreen;

public class Application {

    public static void main(String[] args) { 
        // configure application.
        enableDebugScreen();

        // configure routes.
        get("/", (req, res) -> "hello world!");
    }

}
```

## まとめ

Sparkはスタートアップまでが非常に早いので、簡単に動くアプリをさっと作りたいときには重宝しそうです。
とはいえ、それなりにまともなアプリを作ろうと思うと、自分で考えなきゃいけないところがあり、Spring Bootのように「あまり仕組みを知らなくてもフルセットのアプリが作れる」という簡単さはないところですね。

次回からは、テーマを決めて実装方法を解説していきたいと思います。