Vert.x 3入門 〜 複数Verticleのデプロイ

[Vert.x](http://vert-x3.github.io/)とは、公式ページより

> JVM上にリアクティブアプリケーションを構築するためのツールキットです


[@timfox](https://twitter.com/timfox)氏が中心となって、[現在2015年6月22日を目標](https://github.com/vert-x3/wiki/wiki/Vert.x-Roadmap#vertx-300-final-release-22-june-2015)にversion 3が開発されています。この投稿含め何回かに分けてVert.x 3の動くサンプルを実装していこうと思います。

# 概要

Vert.xにおいて、`event loop thread`上で動かすVerticleでブロッキングするような処理を記述すべきではありません。データベースに対する処理等ブロッキングする処理は、それを専用にこなす別のVerticleを用意して、`worker thread`でこのVerticleを動かすことで、`event loop thread`上で動くVerticleの処理のブロッキングを避けることができます。この際二つのVerticle間の通信には`EventBus`を用います。

今回はHTTPリクエストを受け付けるVerticleと、データベースに関連する処理を担うVerticleの二つを用意して、シンプルなRESTサーバを動かしてみます。

# 前提

Vert.x 3はJava 8が必須となります、あらかじめJava 8をインストールしておいてください。Java 8がインストールできているかは以下コマンドで確認できます:

```bash
$ java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)
```

また、IntelliJを前提に解説しています、Eclipse等他のIDEの場合適宜読み替えてください。

データベースにはMySQLを用いています、PostgreSQL等他のデータベースを利用する場合適宜読み替えてください。
データベースのユーザはroot(パスワードもroot)を用いています、別のものを用いる場合適宜読み替えてください。

またJDBCへの接続には説明を簡便にするために[JDBI](http://jdbi.org/)を用いていますが、こちらは好みのものに置き換えて読んでください。

# 手順

## データベースの準備

予めサンプルデータを格納するデータベースを作成しておきます、MySQLに接続して以下SQL文を実行します。

```bash
$ msql -u root -p
> create database vertx_example;

> use vertx_example;

> create table products (
  id int not null auto_increment primary key,
  name varchar(20),
  price float,
  weight int
) engine=InnoDB;

> insert into products (name, price, weight) values
("Egg Whisk", 3.88, 150), ("Tea Cosy", 5.99, 100), ("Spatula", 1.00, 80);
```

## ひな形のクローン

ひな形となるプロジェクトをクローンします。

```bash
$ git clone https://github.com/p-baleine/vertx-gradle-template.git multi-verticle
$ cd multi-verticle
$ git remote rm origin # origin削除
```

## IntelliJでインポート

IntelliJを起動してWelcomeダイアログで[Import Project]を選択し、クローンした際にできたディレクトリを選択します。

Import Projectのダイアログで、[Import project from external model]にチェックを入れて、[Gradle]を選択し[Next]をクリックし、次に表示される画面で[Finish]をクリックします。


## ServerVerticle

まずHTTPリクエストを受け付けるServerVerticleを実装します。

IntelliJでProjectヒエラルキーにて`com.example.helloworld`パッケージを右クリックして[Refactor]→[Rename]を選択、`com.example.multi`にリネームします。

IntelliJでProjectヒエラルキーにて`com.example.multi`パッケージ下の`HelloWorldVerticle`を右クリックして[Refactor]→[Rename]を選択、`ServerVerticle`にリネームします。

必要となるアーティファクトをbuild.gradleに追記します。

```gradle
...
dependencies {
    compile 'io.vertx:vertx-core:3.0.0-milestone4'
    compile 'io.vertx:vertx-apex:3.0.0-milestone4' // 追加
}
...
```

IntelliJで[View]→[Tool Windows]→[Gradle]を選択してGradle Projectsビューを表示したら、同期アイコンをクリックして依存関係を読み込みます。

`com.example.multi`下の`ServerVerticle`を開いて内容を以下の通り編集します。

```java
package com.example.multi;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.AsyncResult;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonArray;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.impl.LoggerFactory;
import io.vertx.ext.apex.Router;
import io.vertx.ext.apex.RoutingContext;
import io.vertx.ext.apex.handler.BodyHandler;

public class ServerVerticle extends AbstractVerticle {

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

  @Override
  public void start() {
    DeploymentOptions options = new DeploymentOptions().setWorker(true);
    vertx.deployVerticle("com.example.multi.ProductProcessorVerticle", options, res -> {
      if (res.succeeded()) {
        Router router = Router.router(vertx);

        router.route().handler(BodyHandler.create());
        router.route().handler(routingContext -> {
          routingContext.response().putHeader("Content-Type", "application/json");
          routingContext.next();
        });

        router.get("/products").handler(this::handleListProduct);

        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
      } else {
        throw new RuntimeException(res.cause());
      }
    });
  }

  private void handleListProduct(RoutingContext routingContext) {
    EventBus eventBus = vertx.eventBus();

    eventBus.send("products.list", null, (AsyncResult<Message<JsonArray>> res) -> {
      if (res.failed()) {
        routingContext.fail(res.cause());
      } else {
        routingContext.response().end(res.result().body().encode());
      }
    });
  }
}
```

## ProductProcessorVerticle

次にデータベースに関連する処理を担うProductProcessorVerticleを実装します。

IntelliJでProjectヒエラルキーにて`com.example.multi`パッケージを右クリックして[New]→[Java Class]を選択します。Create New ClassダイアログでNameに`ProductProcessorVerticle`を入力して[OK]をクリックします。

追加で必要となるアーティファクトをbuild.gradleに追記します。

```gradle
...
dependencies {
    compile 'io.vertx:vertx-core:3.0.0-milestone4'
    compile 'io.vertx:vertx-apex:3.0.0-milestone4'
    compile 'com.mchange:c3p0:0.9.5'                 // 追記
    compile 'com.mchange:mchange-commons-java:0.2.9' // 追記
    compile 'mysql:mysql-connector-java:5.1.35'      // 追記
    compile 'org.jdbi:jdbi:2.62'                     // 追記
}
...
```
IntelliJで[View]→[Tool Windows]→[Gradle]を選択してGradle Projectsビューを表示したら、同期アイコンをクリックして依存関係を読み込みます。

`com.example.multi`下の`ProductProcessorVerticle`を開いて内容を以下の通り編集します。

```java
package com.example.multi;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonArray;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.impl.LoggerFactory;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.DefaultMapper;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.ResultIterator;

import java.util.Map;

public class ProductProcessorVerticle extends AbstractVerticle {

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

  private ComboPooledDataSource dataSource = null;
  private DBI dbi = null;

  @Override
  public void start() throws Exception {
    dataSource = new ComboPooledDataSource();
    dataSource.setDriverClass("com.mysql.jdbc.Driver");
    dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/vertx_example?user=root&password=root");
    dbi = new DBI(dataSource);
    EventBus eventBus = vertx.eventBus();

    eventBus.consumer("products.list", this::listProduct);
  }

  @Override
  public void stop() throws Exception {
    dataSource.close();
  }

  private void listProduct(Message<Object> message) {
    Handle h = dbi.open();
    ResultIterator<Map<String, Object>> results = h.createQuery("select * from products")
        .map(new DefaultMapper())
        .iterator();
    JsonArray arr = new JsonArray();
    while (results.hasNext()) {
      arr.add(results.next());
    }
    message.reply(arr);
    h.close();
  }
}
```

## アプリの起動

IntelliJの[Run]→[Edit Configurations...]を選択します。

Run/Debug Configurationsダイアログで[+]→[Application]を選択します。

以下の通り入力して、[OK]をクリックします:

* Name: Multi
* Main classs: io.vertx.core.Starter
* Program arguments: run com.example.multi.ServerVerticle

IntelliJの[Run]→[Run 'Database']を選択します、IntelliJ上のコンソールで、エラーなく起動することを確認します

## 確認

以下コマンドを実行して、プロダクト一覧が返却されることを確認します:

```bash
$ curl -D - localhost:8080/products
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 123

[{"id":"1","name":"Egg Whisk","price":150},{"id":"2","name":"Tea Cosy","price":100},{"id":"3","name":"Spatula","price":30}]
```

# 解説

前述のとおり今回のサンプルではHTTPリクエストを受け付けるVerticle(`ServerVerticle`)と、データベースに関連する処理を担うVerticle(`ProductProcessorVerticle`)の2つを用いています。

Vert.xのVerticleは[3種類](http://vert-x3.github.io/docs/vertx-core/java/#_verticle_types)あります:

* `Standard Verticles`: `event loop thread`で実行される

* `Worker Verticles`: `worker pool`のthreadで実行される、インスタンスは一つ以上のスレッドで同時にじっこうされることは_ない_

* `Multi-threaded worker verticles`: `worker pool`のthreadで実行される、インスタンスは一つ以上のスレッドで同時に実行することができる

今回の`ServerVerticle`はStandard Verticleです。また`ProductProcessorVerticle`は以下のとおりデプロイ時のオプションでWorker Verticleとして起動するよう指定してデプロイしています:

```java
    DeploymentOptions options = new DeploymentOptions().setWorker(true);
    vertx.deployVerticle("com.example.multi.ProductProcessorVerticle", options, res -> {
      // ...
    });
```

このように、ブロッキングする処理は`Worker verticle`で実行することで、`event loop thread`のブロッキングを避ける事ができます。


Verticle間の通信は[EventBus](http://vert-x3.github.io/docs/vertx-core/java/#event_bus)を介して行います。
予め`EventBus`の任意のアドレスに対しハンドラを登録しておき、別のVerticleからこのアドレスに対しメッセージを送信することで、異なるVerticle間での通信が可能となります。
今回は、`ProductProcessorVerticle`が`start`メソッド内で`products.list`アドレスに対し`listProducts`メソッドをハンドラとして登録しています。`ServerVerticle`側で一覧取得のHTTPリクエストが合った際に、`EventBus`にアドレス`products.list`を指定してメッセージを送信しています。メッセージを受け取った`ProductProcessorVerticle`では`listProducts`メソッドの中で引数に渡された`Message`インスタンスの`reply`メソッドで結果を応答しています。


Vert.x 3から導入されたサービスを実装することで、EventBusに関連する冗長な処理を省略することができます、サービスについては引き続く投稿でより詳細に扱います。

一覧表示以外を実装した完全なコードは[こちら](https://github.com/p-baleine/step-by-step-vert.x/tree/master/examples/multi)に置いてあります。


# こちらもどうぞ

* [Vert.x 3入門 〜 HelloWorldVerticle](http://qiita.com/p-baleine@github/items/304912bea4a041ae653b)
* [Vert.x 3入門 〜 Webアプリケーションフレームワーク Apex](http://qiita.com/p-baleine@github/items/d9975dad4e44987b48c8)
* [Vert.x 3入門 〜 JDBC Service](http://qiita.com/p-baleine@github/items/f5ecfcd8387d07418aac)