マイクロサービスフレームワークArmeriaを始める

Armeriaのチュートリアルを書いてみる
https://line.github.io/armeria/index.html

RESTサーバーとして使う

まずは、プロジェクトを用意します。
dependencyにcom.linecorp.armeria:armeria:0.62.0を追加します。
mavenの場合、次のようなpom.xmlを用意します。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=
           "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mycompany</groupId>
    <artifactId>armeria_tutorial</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>com.linecorp.armeria</groupId>
            <artifactId>armeria</artifactId>
            <version>0.62.0</version>
        </dependency>
    </dependencies>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>10</maven.compiler.source>
        <maven.compiler.target>10</maven.compiler.target>
    </properties>
</project>

sourceやtargetは環境に合わせて設定してください。1.8以降で動くはず。


まずは、簡単なHTTP RESTサーバーを書いてみます。

package tutorial;

import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.server.Server;
import com.linecorp.armeria.server.ServerBuilder;
import java.util.concurrent.CompletableFuture;

public class BasicServer {
    public static void main(String[] args) {
        ServerBuilder sb = new ServerBuilder();
        sb.http(8080);
        
        sb.service("/hello", (ctx, res) -> HttpResponse.of(
                HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "Hello!!"));
        
        Server server = sb.build();
        CompletableFuture<Void> future = server.start();
        future.join();
    }
}


http://localhost:8080/helloにアクセスしてみます。


詳しくはこちらを
https://line.github.io/armeria/server-basics.html

アノテーションでサービスを定義する

serviceメソッドでURLとサービスを結び付けていましたが、通常のクラスにアノテーションを付けたメソッドとしてサービスを定義することもできます。

package tutorial;

import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.server.annotation.Get;
import com.linecorp.armeria.server.annotation.Param;

public class MyService {
    @Get("/service")
    public HttpResponse service() {
        return HttpResponse.of(
                HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "My Service");
    }
    @Get("/greet/:name")
    public HttpResponse greet(@Param("name") String name) {
        return HttpResponse.of(
                HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "Hello " + name);
    }
}


BasicServerに追加します。

        sb.annotatedService(new MyService());



詳しくはこのあたり
https://line.github.io/armeria/server-annotated-service.html

Thriftサーバーとして使う

ThriftのIDLを書きます。
/src/main/thriftにhello.thriftファイルを置きます。

namespace java tutorial.thrift.hello

service HelloService {
  string hello(1:string name)
}

これをthriftコンパイラJavaコードを生成します。
ここからダウンロードします
https://thrift.apache.org/download

$ thrift -out src/main/java --gen java src/main/thrift/hello.thrift


こんな感じのソースが生成されます。

/**
 * Autogenerated by Thrift Compiler (0.11.0)
 *
 * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
 *  @generated
 */
package tutorial.thrift.hello;

@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-04-10")
public class HelloService {

  public interface Iface {

    public java.lang.String hello(java.lang.String name) throws org.apache.thrift.TException;

  }
...


実際はmavenプラグインを使ってmaven上でコンパイルできるようにしたほうがいいけど、このあたりを見てがんばる感じで。
https://stackoverflow.com/questions/18767986/how-can-i-compile-all-thrift-files-thrift-as-a-maven-phase


できたinterfaceを実装したコードを書きます。

package tutorial;

import org.apache.thrift.TException;
import tutorial.thrift.hello.HelloService;

public class HelloServiceImpl implements HelloService.Iface {

    @Override
    public String hello(String name) throws TException {
        return String.format("Hello %s!!!", name);
    }

}


ArmeriaでThriftを使うためにdependencyにarmeria-thriftを追加します。

        <dependency>
            <groupId>com.linecorp.armeria</groupId>
            <artifactId>armeria-thrift</artifactId>
            <version>0.62.0</version>
        </dependency>


そしたらserviceとして追加します。ここでは別サーバーとして追加してみましょう。
future.join()で処理がブロックしてしまうので、そこは消してまとめてjoinします。

        CompletableFuture<Void> tfuture = new ServerBuilder()
                .http(8081)
                .service("/hello", THttpService.of(new HelloServiceImpl()))
                .build()
                .start();
        
        CompletableFutures.allAsList(Arrays.asList(
                future, tfuture)).join();


詳しくはこちらを。
https://line.github.io/armeria/server-thrift.html

DocServiceを使う

しかしながら、クライアント作るまで動かせないのは不便ですね。
そこでArmeriaではDocServiceというのが用意されています。

        CompletableFuture<Void> tfuture = new ServerBuilder()
                .http(8081)
                .service("/hello", THttpService.of(new HelloServiceImpl()))
                .serviceUnder("/docs", new DocService())
                .build()
                .start();

serviceUnderにするのを忘れないように。


http://localhost:8081/docsにアクセスすると、呼び出しが行えます。


ただ、いきなりJSONを入力しろと言われても何を入れていいかわからないので、サンプルが欲しいところ。ということで、DocserviceBuilderを使います。

                .serviceUnder("/docs", new DocServiceBuilder()
                    .exampleRequest(new HelloService.hello_args("naoki"))
                    .build())


パラメータも用意されて、ボタンを押すと実行されました。


詳しくはこちらを。
https://line.github.io/armeria/server-docservice.html

Thriftクライアントとして使う

さて、ではThriftサーバーを呼びだしてみましょう。

        HelloService.Iface helloService = Clients.newClient(
                "tbinary+http://localhost:8081/hello",
                HelloService.Iface.class);
        sb.service("/thello/{name}", (ctx, res) -> HttpResponse.of(
                HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8,
                helloService.hello(ctx.pathParam("name"))));


呼び出せてる感じ。


詳しくはここを
https://line.github.io/armeria/client-thrift.html

HTTPクライアントとして使う

さて、最後にHTTPクライアントとしても使ってみます。
まずはHttpClientライブラリを。

        HttpClient httpClient = HttpClient.of("http://localhost:8080");
        
        CompletableFuture<Void> hfuture = new ServerBuilder()
                .http(8082)
                .service("/hello", (ctx, res) ->{
                    CompletableFuture<AggregatedHttpMessage> msg =
                            httpClient.get("/hello").aggregate();
                    return HttpResponse.of(
                            HttpStatus.OK,
                            MediaType.PLAIN_TEXT_UTF_8,
                            msg.get().content().toStringAscii());
                })
                .build()
                .start();
        
        CompletableFutures.allAsList(Arrays.asList(
                future, tfuture, hfuture)).join();
Retrofitを使う

実際は、HTTP呼び出しは結構多いのでRetrofitでメソッドに結び付けたい。
ということで、armeria-retrofit2をdependencyに加えます。また、Retrofitで使うconverterやadapterも追加しておきます。

        <dependency>
            <groupId>com.linecorp.armeria</groupId>
            <artifactId>armeria-retrofit2</artifactId>
            <version>0.62.0</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>converter-scalars</artifactId>
            <version>2.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>adapter-java8</artifactId>
            <version>2.4.0</version>
        </dependency>


HTTP呼び出しをメソッドとして定義します。

package tutorial;

import java.util.concurrent.CompletableFuture;
import retrofit2.http.GET;
import retrofit2.http.Path;

public interface MyHttp {
    @GET("/hello")
    CompletableFuture<String> hello();
    
    @GET("/thello/{name}")
    CompletableFuture<String> hello(@Path("name") String name);
}


このメソッドを使ってHTTP呼び出し。

        Retrofit retrofit = new ArmeriaRetrofitBuilder()
                .baseUrl("http://localhost:8080/")
                .addConverterFactory(ScalarsConverterFactory.create())
                .addCallAdapterFactory(Java8CallAdapterFactory.create())
                .build();
        MyHttp myHttp = retrofit.create(MyHttp.class);
        
        CompletableFuture<Void> hfuture = new ServerBuilder()
                .http(8082)
                .service("/hello", (ctx, res) ->HttpResponse.of(
                        HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8,
                        myHttp.hello().get()))
                .service("/hello/{name}", (ctx, res) -> HttpResponse.of(
                        HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8,
                        myHttp.hello(ctx.pathParam("name")).get()))
                .build()
                .start();