突然の『さくっと作ってよ』に答える軽量フレームワークを探してみた

# はじめに

『**こんな感じのモックアップをさくっと作ってよ。**』このようなの突然の一言で、タスクをふられた経験は無いでしょうか。

障害調査やフィージビリティスタディなどを行う際に調査対象のアプリケーションと連携するモックアップアプリケーションを作ることがありますが、今回ふってきたタスクもこのモックアップアプリケーションの作成です。

モックアップアプリケーションは少ない手数で動くものができあがればよいので、今回はまだ私が使ったことのない**さくっと作れそうな**フレームワークを探すところから始めてみようと思います。


# 採用するフレームワークに設定した条件

* OSSで提供されているフレームワークであること
* Javaで実装できること
* 環境構築 ⇒ 実装/コンパイル ⇒ デプロイ/起動 までの作業が**さくっと**やれること（今回はここが最も大事）
* 可能な限りサーバーのリソースを消費しないこと

# モックアップアプリケーションに実装する機能の要件

 * 受信したHTTPリクエストの内容を後で確認できること
 * JSON形式でデータをやり取りできること
 * リクエストの特定のパラメーターによって応答結果（OK/NG）を切替えられること

# 実際に行った探し方

まず、OSSで提供されているフレームワークを**[GitHub](https://github.com/search)**で、`java web framework microservice`という検索キーワードで検索してみました。検索結果は以下のとおり。
★の数をみて**tipsy/javalin**が第1候補、**pippo-java/pippo**が第2候補とします。
![github_search.png](https://qiita-image-store.s3.amazonaws.com/0/221324/80db3e47-5888-e72c-8f29-6276a2699bab.png)

次に、公式サイトでフレームワークの特徴を確認し、今回の採用条件と実装する機能要件を満たせそうかサイトの記載を斜め読みます。
どうやら両方とも設定した条件を満たしているようなので、「`カラフルでポップなアイコン`と`サイズを意識した設計（組込みデバイスでの利用も想定しいる点）`」が気になったので、今回は第2候補の**Pippo**を採用してみます。

![pippo-logo.png](https://qiita-image-store.s3.amazonaws.com/0/221324/54629595-8cf2-a814-1c4d-dd15389aa56c.png)

* 気になったのは、[Pippo公式サイト](http://www.pippo.ro/)にあった以下の記述。

> The core is small (around 140 KB) and we intend to keep it as small/simple as possible and to push new functionalities in pippo modules and third-party repositories/modules.
You are not forced to use a specific template engine or an embedded web server. Furthermore you have multiple out of the box options (see Templates and Server).
Also, Pippo comes with a very small footprint that makes it excellent for embedded devices (Raspberry Pi for example).

# モックアップアプリケーション構築

早速、モックアップアプリケーションを開発環境を構築します。

## 環境構築

* JDK 1.8
* Maven 3

### ソースファイルの取得

GitHubの[レポジトリ](https://github.com/pippo-java/pippo)からローカル環境へレポジトリを取得します。


### モックアップアプリケーションの雛形を作成

[Quickstart](http://www.pippo.ro/dev/quickstart.html)の記載に従って、ローカル環境にCloneしたレポジトリのルートディレクトリ`pippo`へ移動して、以下のコマンドを実行します。
`-DgroupId`と`-DartifactId`の箇所のみ変更しています。

```
mvn archetype:generate \
  -DarchetypeGroupId=ro.pippo \
  -DarchetypeArtifactId=pippo-quickstart \
  -DarchetypeVersion=1.5.0 \
  -DgroupId=com.example.mock \
  -DartifactId=example-mock
```

ビルドしたモジュールのバージョンを設定するか確認されるので変更する場合はここで入力します。
今回はデフォルトの`1.0-SNAPSHOT`で良いので空のままEnterキーを押下します。

```
Define value for property 'version' 1.0-SNAPSHOT: :
```

続いて設定内容ついて確認されるので、問題がなければ`Y`を入力します。

```
[INFO] Using property: package = com.example.mock
Confirm properties configuration:
groupId: com.example.mock
artifactId: example-mock
version: 1.0-SNAPSHOT
package: com.example.mock
 Y: :
```


## 実装

ルートディレクトリ`pippo`以下に`-DartifactId`で指定した名称`example-mock`のディレクトリが作成されているので、以下のファイルを編集してモックアップアプリケーションを作成していきます。

```
pippo
└─ example-mock
　 ├─ pom.xml (利用するモジュールの追加はこのファイルを編集します)
　 └─ src
　　  └─ main
　　　   ├─ java
　　　   │  └─ com
　　　   │     └─ example
　　　   │        └─ mock
　　　   │           ├─ PippoApplication.java (今回はこのファイルに処理を実装します)
　　　   │           └─ PippoLauncher.java
   　　　├─ resources
　　　   │  ├─ conf
　　　   │　│　├─ application.properties
　　　   │  │　├─ messages.properties
　　　   │  │　└─ messages_xx.properties etc (ロケールに応じたmessages.propertiesがいくつかあります)
　　　   │  ├─ templates
　　　   │　│　 └─ hello.ftl
　　　   │  └─ simplelogger.properties (ロガーの設定はこのファイルを編集します)
　　　   └─ webapp
　　　　    └─ WEB-INF
　　　　　     └─ web.xml

```

まずは、リクエストとレスポンスをマッピングするPOJOを作成します。

* リクエストPOJO

```java:RequestParameter.java
package com.example.mock.common;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class RequestParameter implements Serializable {

	private static final long serialVersionUID = 1L;

	public String itemA;
	public Boolean itemB;

}
```

* レスポンスPOJO

```java:ResponseParameter.java
package com.example.mock.common;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class ResponseParameter implements Serializable {

	private static final long serialVersionUID = 1L;

	// レスポンス結果（1:OK、0:NG）
	public Integer result;
	public String message;
}
```

次に、JSON形式のリクエストをPOSTで受付けた際に実行するロジックを実装します。

```java:PippoApplication.java

package com.example.mock;

import java.security.InvalidParameterException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.example.mock.common.RequestParameter;
import com.example.mock.common.ResponseParameter;

import ro.pippo.core.Application;

/**
 * A simple Pippo application.
 *
 * @see com.example.mock.PippoLauncher#main(String[])
 */
public class PippoApplication extends Application {

    private final static Logger log = LoggerFactory.getLogger(PippoApplication.class);

    @Override
    protected void onInit() {
        getRouter().ignorePaths("/favicon.ico");

        // send 'Hello World' as response
        GET("/", routeContext -> routeContext.send("Hello World"));

       // 今回実装した箇所はここ
        POST("/reqJson", routeContext -> {

        	log.debug("Check Request Parameters:" + routeContext.getRequest().getParameters());
        	log.debug("Check Request Body:" + routeContext.getRequest().getBody());
           // リクエストのBody部をリクエスト用のPOJOへマッピング
        	RequestParameter reqParm = routeContext.createEntityFromBody(RequestParameter.class);

        	if (null == reqParm) {
        		throw new InvalidParameterException("Request Paramater is Invalid");
        	}

        	ResponseParameter resParm = new ResponseParameter();

           // リクエストのitemBの値を判定して応答を切替え
        	if (Boolean.FALSE == reqParm.itemB) {
        		// NG
            	resParm.result = Integer.valueOf(0);
            	resParm.message = "NG:" + reqParm.itemA;

            	log.debug("Check Result:NG");
        	} else {
        		// OK
            	resParm.result = Integer.valueOf(1);
            	resParm.message = "OK:" + reqParm.itemA;

            	log.debug("Check Result:OK");
        	}

            // JSON形式のレスポンスを返却
        	routeContext.json().send(resParm);
        });
    }

}
```

`simplelogger.properties`を編集し、今回埋め込んだログが出力されるように修正します。
また、ログ出力日時のフォーマットに年月日も出力されるように修正します。

```
org.slf4j.simpleLogger.log.com.example.mock=debug
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss
```


## コンパイル/起動

今回作成したモックアプリケーションは、Jacksonを利用するため、`example-mock`ディレクトリ以下の`pom.xml`へ以下の記述を追加します。

```xml:pom.xml
<!-- Jackson -->
<dependency>
  <groupId>ro.pippo</groupId>
  <artifactId>pippo-jackson</artifactId>
  <version>${pippo.version}</version>
</dependency>
```


`example-mock`ディレクトリ以下で、コマンドを実行します。

```
mvn compile exec:java
```

## 動作検証

1. ブラウザで`http://localhost:8338/`へアクセスし、**Hello World**が表示されることを確認します。

1. cURLコマンドでJSON形式のリクエストを送信し、応答結果を確認します。

* 応答結果OKのパターン

```
$ curl -H "Content-Type: application/json" -X POST "http://localhost:8338/reqJson" -d '{"itemA": "Test Request", "itemB": true}' -i
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    80  100    40  100    40    183    183 --:--:-- --:--:-- --:--:--   197HTTP/1.1 200 OK
Date: Mon, 17 Dec 2018 02:53:48 GMT
Content-Type: application/json;charset=utf-8
Content-Length: 40
Server: Jetty(9.4.6.v20170531)

{"result":1,"message":"OK:Test Request"}
```

ログ出力結果

```
2018-12-17 11:53:48 [qtp188599000-16] DEBUG ro.pippo.core.PippoFilter - Request POST '/reqJson'
2018-12-17 11:53:48 [qtp188599000-16] DEBUG ro.pippo.core.route.DefaultRouter - Found 1 route matches for POST '/reqJson'
2018-12-17 11:53:48 [qtp188599000-16] DEBUG ro.pippo.core.route.DefaultRouteContext - Executing handler for POST '/reqJson'
2018-12-17 11:53:48 [qtp188599000-16] DEBUG com.example.mock.PippoApplication - Check Request Parameters:{}
2018-12-17 11:53:48 [qtp188599000-16] DEBUG com.example.mock.PippoApplication - Check Request Body:{"itemA": "Test Request", "itemB": true}
2018-12-17 11:53:48 [qtp188599000-16] DEBUG com.example.mock.PippoApplication - Check Result:OK
2018-12-17 11:53:48 [qtp188599000-16] DEBUG ro.pippo.core.route.RouteDispatcher - Returned status code 200 for POST '/reqJson'
```



* 応答結果NGのパターン

```
$ curl -H "Content-Type: application/json" -X POST "http://localhost:8338/reqJson" -d '{"itemA": "Test Request", "itemB": false}' -i
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    81  100    40  100    41    182    187 --:--:-- --:--:-- --:--:--   187HTTP/1.1 200 OK
Date: Mon, 17 Dec 2018 02:53:55 GMT
Content-Type: application/json;charset=utf-8
Content-Length: 40
Server: Jetty(9.4.6.v20170531)

{"result":0,"message":"NG:Test Request"}
```

ログ出力結果

```
2018-12-17 11:53:55 [qtp188599000-19] DEBUG ro.pippo.core.PippoFilter - Request POST '/reqJson'
2018-12-17 11:53:55 [qtp188599000-19] DEBUG ro.pippo.core.route.DefaultRouter - Found 1 route matches for POST '/reqJson'
2018-12-17 11:53:55 [qtp188599000-19] DEBUG ro.pippo.core.route.DefaultRouteContext - Executing handler for POST '/reqJson'
2018-12-17 11:53:55 [qtp188599000-19] DEBUG com.example.mock.PippoApplication - Check Request Parameters:{}
2018-12-17 11:53:55 [qtp188599000-19] DEBUG com.example.mock.PippoApplication - Check Request Body:{"itemA": "Test Request", "itemB": false}
2018-12-17 11:53:55 [qtp188599000-19] DEBUG com.example.mock.PippoApplication - Check Result:NG
2018-12-17 11:53:55 [qtp188599000-19] DEBUG ro.pippo.core.route.RouteDispatcher - Returned status code 200 for POST '/reqJson'
```

## ビルドの作成

動作検証が完了したら、[Pippoサイト](http://www.pippo.ro/doc/deployment.html)の記載を参考に`example-mock/src/main/assembly/assembly.xml`を作成します。`assembly.xml`の内容は以下のとおり。

```xml:assembly.xml
<?xml version="1.0" encoding="UTF-8"?>
<assembly>
    <id>app</id>
    <formats>
        <format>zip</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <useProjectArtifact>false</useProjectArtifact>
            <scope>runtime</scope>
            <outputDirectory>lib</outputDirectory>
            <includes>
                <include>*:jar:*</include>
            </includes>
        </dependencySet>
    </dependencySets>
    <fileSets>
        <fileSet>
            <directory>${project.build.directory}</directory>
            <outputDirectory>.</outputDirectory>
            <includes>
                <include>*.jar</include>
            </includes>
            <excludes>
                <exclude>*-javadoc.jar</exclude>
                <exclude>*-sources.jar</exclude>
            </excludes>
        </fileSet>
    </fileSets>
</assembly>
```

今回は、QuickStartの手順に沿っているため`pom.xml`の編集は不要です。（モックアップアプリケーションの雛形を作成した際に既に記載されています）
アプリケーションサーバー`Jetty`を組込んだビルド`example-mock-1.0-SNAPSHOT.zip`が`target`ディレクトリに作成されます。実行するコマンドは以下のとおり。

```
mvn clean package
```

なお、Tomcat等の別途アプリケーションサーバーが用意されている場合には、以下のコマンドを実行してモックアップアプリケーションのWarファイルをビルドします。

```
mvn -Pwar package
```


## 起動

作成したZipファイル`example-mock-1.0-SNAPSHOT.zip`を任意のディレクトリへ解凍し、解凍したディレクトリへ移動後に以下のコマンドを実行します。

```
java -jar example-mock-1.0-SNAPSHOT.jar
```


# まとめ

駆け足で、**Pippo**を用いたモックアップアプリケーションを作ってみた感想ですが、**さくっと作る**という今回の条件を十分に満たしていることが確認できました。

| 設定した条件 | 評価 |
|:---|:---|
| OSSで提供されているフレームワークであること | ○　Apache 2.0ライセンスで提供されています |
| Javaで実装できること | ○ |
| 環境構築からデプロイ/起動までの作業がさくっとやれること| ○　ドキュメントの記載もわかり易くてとくに詰ることなくさくっとできました |
| 可能な限りサーバーのリソースを消費しないこと | ○ ディスク上のサイズ、使用メモリーサイズともに小さいです|


個人的に楽しみにしていたWebアプリケーションのディスク上のサイズは、**約5.9MB**でした。（アプリケーションサーバー込み）
比較のためTomcat(8.5.30 : ディストリビューションはCore)にWarファイルをデプロイした際のディスク上のサイズを確認したところ、**約14.5MB**でした。ディスク上のサイズで2倍以上の差があり、かなり小さい印象をうけました。

また、JVMのヒープサイズもデフォルト値で動作させることができて、タスクマネージャーで確認したメモリー使用量は**約61MB**であり、組込みデバイスでの利用も考慮されているだけあって使用メモリーサイズへの配慮がみえます。

比較対象としてTomcatとJettyをデフォルト設定で起動した際のメモリー使用量は以下のとおりでした。

  * Tomcat（8.5.30）が**約110MB**
  * Jetty（9.4.14.v20181114）が**約66MB**

モジュール化されている[他の機能](http://www.pippo.ro/doc/demo.html)についても機会があればご紹介しようと思います。


# 参考情報

[Pippo公式サイト](http://www.pippo.ro/)

[GitHubレポジトリ](https://github.com/pippo-java/pippo)