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

# 概要

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

![Helidonのサイト](https://qiita-image-store.s3.amazonaws.com/0/65002/61ba1333-0053-63d3-57e8-8f7a27871867.png)

## 想定読者

- 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](https://helidon.io/docs/latest/#/about/01_introduction)の1ページに書いてあることをまとめると、下表のようになります。
これで全部です。

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


# 動かしてみる

## 前提

Java8が動かせればよいので、このエントリでは[Corretto](https://aws.amazon.com/corretto/)を使います。

```shell-session
$ 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](https://helidon.io/docs/latest/#/webserver/01_introduction)にあるソースを動かして見ます。

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

```java

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ファイルは、下記のような形です。

```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"タスクで動かして見ます。

```shell-session
$ ./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

```

アクセスしてみます。

```shell-session
$ curl localhost:53721
It works!
```

動きましたね。この時点でのソースは[0.0.1としてGitHubに](https://github.com/hrkt/helidontest/releases/tag/0.0.1)あげました。

## 設定を加える

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

前述のソースを少し改変して、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());
```

```shell-session

$ ./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に](https://github.com/hrkt/helidontest/releases/tag/0.0.2)あげました。

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

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

```java
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に、設定を追加します。前述のソースに対し、下記のようになります。

```java
        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として用意します。

```properties
web.port=9801
```

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

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

起動してみます。

```shell-session
$ ./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)は使われていないことがわかります。

設定ファイルは、[ドキュメントによれば](https://helidon.io/docs/latest/#/config/01_introduction)、上記で使った「properties」以外にも、YAML, HOCON, JSONが使えます。それぞれ、対応するjarファイルを依存関係に加えて使います。

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

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

```

# まとめ

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

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

このエントリの[ソースはGitHubに](https://github.com/hrkt/helidontest)あげてあります。

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