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間での通信が可能となります。
今回は、ProductProcessorVerticle
がstart
メソッド内でproducts.list
アドレスに対しlistProducts
メソッドをハンドラとして登録しています。ServerVerticle
側で一覧取得のHTTPリクエストが合った際に、EventBus
にアドレスproducts.list
を指定してメッセージを送信しています。メッセージを受け取ったProductProcessorVerticle
ではlistProducts
メソッドの中で引数に渡されたMessage
インスタンスのreply
メソッドで結果を応答しています。
Vert.x 3から導入されたサービスを実装することで、EventBusに関連する冗長な処理を省略することができます、サービスについては引き続く投稿でより詳細に扱います。
一覧表示以外を実装した完全なコードはこちらに置いてあります。