Helidonを使ってマイクロにWebAPサーバを立ち上げてみる


概要

このエントリでは、先週1.0がリリースされたJavaのマイクロサービスタイプの開発用のツールキット"Helidon"を使って、マイクロにWebサービスを立ち上げてみます。
一言で言うと、マイクロサービスに必要になる、Web APIのIFをコンパクトに立ち上げるところまでを主眼に置いて、フルスタックではなく最小限の構成だけを狙ったもの、というところです。

想定読者

  • Javaがかけるエンジニア
  • できれば、Spring BootとかMicronautとかのフレームワークの知識があること

Helidonとは

概要

Helidonは、Oracle製で、Apache2ライセンスでオープンソースのソフトウェアとしてGitHubで公開されています。
サイトのトップの説明をザクッと意訳すると、下記になります。

  • シンプルで速い〜Nettyをベースとしてライブラリを追加てまとめているだけなのでコンパクトで速い
  • MicroProfileをサポートしている〜MicroProfileの小ぶりのクラスライブラリを中核に、JSONサポート(JAX-RS, JSON-P/B)とか、依存性注入(CDI)のあたりは押さえている
  • ReactiveなWebサーバーを持つ〜WebサーバとしてNettyを組み込んであるので、とりあえずすぐに立ち上がり、Nettyの特徴であるReactive方面の特徴も使える
  • 観測可能ポイントをいろいろ持っている〜メトリクスとか、トレーシングとかの、マイクロサービスで必要になるような観測用のポイントを持っており、PrometheusとかZipkin、Kubernetesといったものとの組み合わせがしやすい

わかる人には、下記の位置付けと思えば良いです。
- Javaでいうと、Springっていうよりは、Micronaut
- Rubyで言うと、RailsっていうよりはSinatra
- Scalaで言うと、PlayっていうよりはScalatra
- Nodeで言うと、ExpressっていうよりはKoa

全体構造

サイトのIntroductionの1ページに書いてあることをまとめると、下表のようになります。
これで全部です。

レイヤー コンポーネント 説明
Helidon SE RxServer Webサーバとして動作
Security セキュリティ面をサポート
Config 設定の読み込みをサポート
Helidon MP JAX-RS(Jersey) Webリクエストでの入出力サポート
JSON Processing JSONの処理
CDI 依存性注入

動かしてみる

前提

Java8が動かせればよいので、このエントリではCorrettoを使います。

$ java -version
openjdk version "1.8.0_202"
OpenJDK Runtime Environment Corretto-8.202.08.2 (build 1.8.0_202-b08)
OpenJDK 64-Bit Server VM Corretto-8.202.08.2 (build 25.202-b08, mixed mode)

ソースを書く

Introductionにあるソースを動かして見ます。

メインとなるクラスは、下記だけでOKです。(簡単のため、mainの例外も処理せずthrowしています)


package io.hrkt.helidontest;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import io.helidon.webserver.Routing;
import io.helidon.webserver.WebServer;

public class HelidontestApplication {

    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        WebServer webServer = WebServer
                .create(Routing.builder()
                        .any((req, res) -> res.send("It works!")).build())
                .start().toCompletableFuture().get(10, TimeUnit.SECONDS);

        System.out.println(
                "Server started at: http://localhost:" + webServer.port());
    }

}

Gradleで動かすためのbuild.gradleファイルは、下記のような形です。

plugins {
    id 'application'
    id 'java'
    id 'idea'
    id 'eclipse'
}

group = 'io.hrkt'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
mainClassName = "io.hrkt.helidontest.HelidontestApplication"

repositories {
    mavenCentral()
}

dependencies {
    implementation group: 'io.helidon.webserver', name: 'helidon-webserver', version: '1.0.0'
}

起動

gradleの"application"プラグインを使って"run"タスクで動かして見ます。

$ ./gradlew run

> Task :run
2 21, 2019 6:21:17 午前 io.helidon.webserver.NettyWebServer lambda$start$7
情報: Channel '@default' started: [id: 0x229c8f03, L:/0:0:0:0:0:0:0:0:53721]
Server started at: http://localhost:53721
<=========----> 75% EXECUTING [6s]
> :run

アクセスしてみます。

$ curl localhost:53721
It works!

動きましたね。この時点でのソースは0.0.1としてGitHubにあげました。

設定を加える

上記だけだと実際には使えないので、ポート番号を指定してみるなど、設定を加えるところをみてみます。

前述のソースを少し改変して、ServerConfigurationクラスを使い、下記のようにします。

        int port = 8080;

        ServerConfiguration configuration = ServerConfiguration.builder().port(port).build();
        Routing routing = Routing.builder()
                .any((req, res) -> res.send("It works!")).build();
        WebServer webServer = WebServer
                .create(configuration, routing)
                .start().toCompletableFuture().get(10, TimeUnit.SECONDS);

        System.out.println(
                "Server started at: http://localhost:" + webServer.port());

$ ./gradlew run
> Task :run
2 21, 2019 7:30:45 午前 io.helidon.webserver.NettyWebServer lambda$start$7
情報: Channel '@default' started: [id: 0x4a177e3e, L:/0:0:0:0:0:0:0:0:8080]
Server started at: http://localhost:8080

このような形で、WebServerの起動時に、各種動作設定を渡したり、Routingを渡したりすることで、サービスを構築します。

この時点でのソースは0.0.2としてGitHubにあげました。

Jersey(JAX-RS)のリソースを足してみる

下のようなリソースクラスを足します。

package io.hrkt.helidontest.resource;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

@Path("/")
public class HelloWorld {

    @GET
    @Path("hello")
    public Response hello() {
        return Response.ok("Hello World!").build();
    }
}

mainクラスのRoutingに、設定を追加します。前述のソースに対し、下記のようになります。

        Routing routing = Routing.builder().register("/jersey", 
                JerseySupport.builder()
                .register(HelloWorld.class) 
                .build())
                .any((req, res) -> res.send("It works!")).build();

/jerseyとして追加したパス以下の/helloにアクセスしてみます。

$ curl localhost:8080/jersey/hello
Hello World!

期待したメッセージが出ました。これ以外のパスでは、前述の通り"It Works"が出力されます。

設定ファイルを読み込んでみる

Configまわりの機能を試してみます。設定ファイルをsrc/main/resources/application.propertiesとして用意します。

web.port=9801

前述のソースコードで、読み込み部分はこうします。

        Config config = Config.create();
        int port = config.get("web.port").asInt().orElse(8080);

起動してみます。

$ ./gradlew run

> Task :run
[DEBUG] (main) Using Console logging
2 21, 2019 8:08:21 午前 io.helidon.webserver.NettyWebServer lambda$start$7
情報: Channel '@default' started: [id: 0x4ea36bbe, L:/0:0:0:0:0:0:0:0:9801]
Server started at: http://localhost:9801

上記で、設定ファイルで指定した値(9801)が読み込まれ、フォールバック用の値(8080)は使われていないことがわかります。

設定ファイルは、ドキュメントによれば、上記で使った「properties」以外にも、YAML, HOCON, JSONが使えます。それぞれ、対応するjarファイルを依存関係に加えて使います。

Configは、読み込み時間を記録しています。設定ファイルの更新を検知して、対応するハンドラを起動する機能も備えています。アプリをデプロイした後に動的に構成を変えたい場合などに便利ですね。

        Instant loadTime = config.timestamp();
        System.out.println("Configfile was loaded at : " + loadTime);

まとめ

Helidonについて説明し、Webサーバを動作させてみました。

エントリ執筆時点では、1.0が出て日が浅いせいか、チュートリアルのコードがそのままでは動かなかったりする部分(リリースノートにあるような、設定ファイルの読み込み系あたりとか)もありましたが、テストコードやexampleのあたりを眺めると様子がだいたいわかる、コンパクトなフレームワークです。

このエントリのソースはGitHubにあげてあります。

セキュリティを追加するところは、別エントリでまた書きたいと思っています。