The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

SmallRye Stork入門

分散システムの本質は、サービス間の相互作用にあります。現代のアーキテクチャでは、負荷を共有したり、冗長性によって回復力を高めたりするために、サービスの複数のインスタンスを持つことがよくあります。しかし、サービスの最適なインスタンスをどのように選択すればよいのでしょうか?そこで、 SmallRye Storkがお役に立ちます。Storkが最適なインスタンスを選択してくれます。次の機能を提供します:

  • 拡張可能なサービスディスカバリーメカニズム

  • ConsulとKubernetesの組込サポート

  • カスタマイズ可能なクライアントロードバランシング戦略

この技術は、previewと考えられています。

preview では、下位互換性やエコシステムでの存在は保証されていません。具体的な改善には設定や API の変更が必要になるかもしれませんが、 stable になるための計画は現在進行中です。フィードバックは メーリングリストGitHub の課題管理 で受け付けています。

For a full list of possible statuses, check our FAQ entry.

前提条件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.8.1+

  • 動作するコンテナランタイム(Docker, Podman)

  • 使用したい場合、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

アーキテクチャ

このガイドでは、次のような構成のアプリケーションを構築します。

  • 9000番ポートで公開されるシンプルなブルーサービス

  • 9001番ポートで公開されるシンプルなレッドサービス

  • ブルーまたはレッドのサービスを呼び出すRESTクライアント(選択はStorkに委ねられている)

  • RESTクライアントを使用し、サービスを呼び出すRESTエンドポイント

  • ブルーとレッドのサービスは Consulに登録されています。

アプリケーションのアーキテクチャ

For the sake of simplicity, everything (except Consul) will be running in the same Quarkus application. Of course, each component will run in its own process in the real world.

ソリューション

We recommend that you follow the instructions in the next sections and create the applications step by step. However, you can go right to the completed example.

Gitレポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.gitアーカイブ をダウンロードします。

The solution is located in the stork-quickstart directory.

ディスカバリーとセレクション

Before going further, we need to discuss discovery vs. selection.

  • Service discovery is the process of locating service instances. It produces a list of service instances that is potentially empty (if no service matches the request) or contains multiple service instances.

  • Service selection, also called load-balancing, chooses the best instance from the list returned by the discovery process. The result is a single service instance or an exception when no suitable instance can be found.

Stork handles both discovery and selection. However, it does not handle the communication with the service but only provides a service instance. The various integrations in Quarkus extract the location of the service from that service instance.

ディスカバリーと選択

プロジェクトのブートストラップ

quarkus-rest-client-reactiveおよびquarkus-resteasy-reactiveエクステンションをインポートして、好きなアプローチでQuarkusプロジェクトを作成します。

CLI
quarkus create app org.acme:stork-quickstart \
    --extension=quarkus-rest-client-reactive,quarkus-resteasy-reactive \
    --no-code
cd stork-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストール方法については、Quarkus CLIガイドをご参照ください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.11.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=stork-quickstart \
    -Dextensions="quarkus-rest-client-reactive,quarkus-resteasy-reactive" \
    -DnoCode
cd stork-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

生成されたプロジェクトに、以下の依存関係を追加します:

pom.xml
<dependency>
  <groupId>io.smallrye.stork</groupId>
  <artifactId>stork-service-discovery-consul</artifactId>
</dependency>
<dependency>
  <groupId>io.smallrye.reactive</groupId>
  <artifactId>smallrye-mutiny-vertx-consul-client</artifactId>
</dependency>
build.gradle
implementation("io.smallrye.stork:stork-service-discovery-consul")
implementation("io.smallrye.reactive:smallrye-mutiny-vertx-consul-client")

stork-service-discovery-consul provides an implementation of service discovery for Consul. smallrye-mutiny-vertx-consul-client is a Consul client which we will use to register our services in Consul.

The Blue and Red services

Let’s start with the very beginning: the service we will discover, select and call.

以下の内容で src/main/java/org/acme/services/BlueService.java を作成してください。

package org.acme.services;

import io.quarkus.runtime.StartupEvent;
import io.vertx.mutiny.core.Vertx;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;

@ApplicationScoped
public class BlueService {

    @ConfigProperty(name = "blue-service-port", defaultValue = "9000") int port;

    /**
     * Start an HTTP server for the blue service.
     *
     * Note: this method is called on a worker thread, and so it is allowed to block.
     */
    public void init(@Observes StartupEvent ev, Vertx vertx) {
        vertx.createHttpServer()
                .requestHandler(req -> req.response().endAndForget("Hello from Blue!"))
                .listenAndAwait(port);
    }
}

It creates a new HTTP server (using Vert.x) and implements our simple service when the application starts. For each HTTP request, it sends a response with "Hello from Blue!" as the body.

Following the same logic, create the src/main/java/org/acme/services/RedService.java with the following content:

package org.acme.services;

import io.quarkus.runtime.StartupEvent;
import io.vertx.mutiny.core.Vertx;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;

@ApplicationScoped
public class RedService {
    @ConfigProperty(name = "red-service-port", defaultValue = "9001") int port;

    /**
     * Start an HTTP server for the red service.
     *
     * Note: this method is called on a worker thread, and so it is allowed to block.
     */
    public void init(@Observes StartupEvent ev, Vertx vertx) {
        vertx.createHttpServer()
                .requestHandler(req -> req.response().endAndForget("Hello from Red!"))
                .listenAndAwait(port);
    }

}

This time, it writes "Hello from Red!".

Service registration in Consul

Now that we have implemented our services, we need to register them into Consul.

Stork is not limited to Consul and integrates with other service discovery mechanisms.

Create the src/main/java/org/acme/services/Registration.java file with the following content:

package org.acme.services;

import io.quarkus.runtime.StartupEvent;
import io.vertx.ext.consul.ServiceOptions;
import io.vertx.mutiny.ext.consul.ConsulClient;
import io.vertx.ext.consul.ConsulClientOptions;
import io.vertx.mutiny.core.Vertx;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;

@ApplicationScoped
public class Registration {

    @ConfigProperty(name = "consul.host") String host;
    @ConfigProperty(name = "consul.port") int port;

    @ConfigProperty(name = "blue-service-port", defaultValue = "9000") int red;
    @ConfigProperty(name = "red-service-port", defaultValue = "9001") int blue;

    /**
     * Register our two services in Consul.
     *
     * Note: this method is called on a worker thread, and so it is allowed to block.
     */
    public void init(@Observes StartupEvent ev, Vertx vertx) {
        ConsulClient client = ConsulClient.create(vertx, new ConsulClientOptions().setHost(host).setPort(port));

        client.registerServiceAndAwait(
                new ServiceOptions().setPort(blue).setAddress("localhost").setName("my-service").setId("blue"));
        client.registerServiceAndAwait(
                new ServiceOptions().setPort(red).setAddress("localhost").setName("my-service").setId("red"));

    }
}

When the application starts, it connects to Consul using the Vert.x Consul Client and registers our two instances. Both registration uses the same name (my-service), but different ids to indicate that it’s two instances of the same service.

The REST Client interface and the front end API

So far, we didn’t use Stork; we just scaffolded the services we will be discovering, selecting, and calling.

We will call the services using the Reactive REST Client. Create the src/main/java/org/acme/MyService.java file with the following content:

package org.acme;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * The REST Client interface.
 *
 * Notice the `baseUri`. It uses `stork://` as URL scheme indicating that the called service uses Stork to locate and
 * select the service instance. The `my-service` part is the service name. This is used to configure Stork discovery
 * and selection in the `application.properties` file.
 */
@RegisterRestClient(baseUri = "stork://my-service")
public interface MyService {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    String get();
}

It’s a straightforward REST client interface containing a single method. However, note the baseUri attribute. It starts with stork://. It instructs the REST client to delegate the discovery and selection of the service instances to Stork. Notice the my-service part in the URL. It is the service name we will be using in the application configuration.

It does not change how the REST client is used. Create the src/main/java/org/acme/FrontendApi.java file with the following content:

package org.acme;

import org.eclipse.microprofile.rest.client.inject.RestClient;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * A frontend API using our REST Client (which uses Stork to locate and select the service instance on each call).
 */
@Path("/api")
public class FrontendApi {

    @RestClient MyService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String invoke() {
        return service.get();
    }

}

It injects and uses the REST client as usual.

Stork設定

The system is almost complete. We only need to configure Stork and the Registration bean.

In the src/main/resources/application.properties, add:

consul.host=localhost
consul.port=8500

quarkus.stork.my-service.service-discovery.type=consul
quarkus.stork.my-service.service-discovery.consul-host=localhost
quarkus.stork.my-service.service-discovery.consul-port=8500
quarkus.stork.my-service.load-balancer.type=round-robin

The first two lines provide the Consul location used by the Registration bean.

The other properties are related to Stork. stork.my-service.service-discovery indicates which type of service discovery we will be using to locate the my-service service. In our case, it’s consul. quarkus.stork.my-service.service-discovery.consul-host and quarkus.stork.my-service.service-discovery.consul-port configures the access to Consul. Finally, quarkus.stork.my-service.load-balancer.type configures the service selection. In our case, we use a round-robin.

Running the application

We’re done! So, let’s see if it works.

まず、Consulを起動します:

docker run --rm --name consul -p 8500:8500 -p 8501:8501 consul:1.7 agent -dev -ui -client=0.0.0.0 -bind=0.0.0.0 --https-port=8501

Consulを別の方法で起動する場合は、アプリケーションの設定を編集することを忘れないでください。

次に、アプリケーションをパッケージ化します:

CLI
quarkus build
Maven
./mvnw clean package
Gradle
./gradlew build

そして実行します:

> java -jar target/quarkus-app/quarkus-run.jar

別の端末で、次を実行します。

> curl http://localhost:8080/api
...
> curl http://localhost:8080/api
...
> curl http://localhost:8080/api
...

The responses alternate between Hello from Red! and Hello from Blue!.

You can compile this application into a native executable:

CLI
quarkus build --native
Maven
./mvnw package -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

And start it with:

> ./target/stork-getting-started-1.0.0-SNAPSHOT-runner

さらに詳しく

このガイドでは、SmallRye Storkを使ってサービスを発見し、選択する方法を紹介しました。Storkについては、以下のページで詳しく紹介しています: