Posted at

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

More than 3 years have passed since last update.

Vert.xとは、公式ページより


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


@timfox氏が中心となって、現在2015年6月22日を目標に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がインストールできているかは以下コマンドで確認できます:

$ 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を用いていますが、こちらは好みのものに置き換えて読んでください。


手順


データベースの準備

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

$ 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);


ひな形のクローン

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

$ 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に追記します。

...

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を開いて内容を以下の通り編集します。

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に追記します。

...

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を開いて内容を以下の通り編集します。

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上のコンソールで、エラーなく起動することを確認します


確認

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

$ 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種類あります:


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


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


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


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

    DeploymentOptions options = new DeploymentOptions().setWorker(true);

vertx.deployVerticle("com.example.multi.ProductProcessorVerticle", options, res -> {
// ...
});

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

Verticle間の通信はEventBusを介して行います。

予めEventBusの任意のアドレスに対しハンドラを登録しておき、別のVerticleからこのアドレスに対しメッセージを送信することで、異なるVerticle間での通信が可能となります。

今回は、ProductProcessorVerticlestartメソッド内でproducts.listアドレスに対しlistProductsメソッドをハンドラとして登録しています。ServerVerticle側で一覧取得のHTTPリクエストが合った際に、EventBusにアドレスproducts.listを指定してメッセージを送信しています。メッセージを受け取ったProductProcessorVerticleではlistProductsメソッドの中で引数に渡されたMessageインスタンスのreplyメソッドで結果を応答しています。

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

一覧表示以外を実装した完全なコードはこちらに置いてあります。


こちらもどうぞ