CLOVER🍀

That was when it all began.

QuarkusのOpenTracing Extensionを試す

これは、なにをしたくて書いたもの?

Quarkusに、OpenTracing向けのExtensionがあるので、こちらを試してみようかなと。

Opentracing Guide

ガイドを見ているとJaegerを使っているのですが、こちらは使ったことがありません。また、MicroProfile OpenTracingも初めて
使います。

MicroProfile OpenTracingとは

文字通り、MicroProfileにおけるOpenTracingへの仕様です。

microprofile-opentracing/microprofile-opentracing.asciidoc at 1.3.1 · eclipse/microprofile-opentracing · GitHub

OpenTracingがなにかというと、アプリケーション、特にMicroservicesでアプリケーションやミドルウェアの間の依存関係を可視化したり、
プロファイルなどに使う技術であるDistributed Tracingの標準化に向けてAPIや実装の作成を行っているプロジェクトです、と。

The OpenTracing project

What is Distributed Tracing?

Tracerとしては、Jaegerなどがあります。

Supported tracers

Quarkusのガイドでも、Jaegerを使うことになっているので、こちらを使用することにします。

Jaeger: open source, end-to-end distributed tracing

また、Quarkusで使うMicroProfile OpenTracingの実装としては、SmallRye OpenTracingとなります。

GitHub - smallrye/smallrye-opentracing: An MicroProfile-OpenTracing implementation

今回、このあたりを使ってQuarkusでのOpenTracing Extensionを試してみましょう。

お題

Quarkusのガイドでは、単一のJAX-RSリソースクラスに対して適用している例になっていますが、これではちょっと面白くない気がします。

今回は、もう少し広げてJAX-RSアプリケーションを2つ用意し、さらにその背後にRedisを置いてDistributed Tracingを行ってみましょう。

こういう構成にすることにします。

JAX-RS Clientについては、同じくQuarkusのExtensionを使用します。

Rest Client Guide

Redisに対しては、JedisやLettuce向けの実装を使うことにします。

GitHub - opentracing-contrib/java-redis-client: OpenTracing Instrumentation for Redis Client

環境

今回の環境は、こちらです。

$ java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-8u212-b03-0ubuntu1.18.04.1-b03)
OpenJDK 64-Bit Server VM (build 25.212-b03, mixed mode)


$ mvn -version
Apache Maven 3.6.1 (d66c9c0b3152b2e69ee9bac180bb8fcc8e6af555; 2019-04-05T04:00:29+09:00)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 1.8.0_212, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "4.15.0-51-generic", arch: "amd64", family: "unix"

GraalVM。

$ $GRAALVM_HOME/bin/native-image --version
GraalVM Version 1.0.0-rc16 CE

Redisは5.0.5とし、IPアドレス172.17.0.2で、パスワードが「redispass」で動作しているものとします。

Jaegerは、ドキュメントに習ってDockerで起動しておきます。ローカルポートへのバインドは削ったりシていますが。
IPアドレスは、172.17.0.3です。

$ docker container run -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 jaegertracing/all-in-one:1.12.0

RedisへアクセスするAPIを作成する

では、奥のAPIから作成していきましょう。

api-b」とします。

$ mkdir api-b
$ cd api-b

プロジェクトの作成。Extensionとして、「smallrye-opentracing」を含めてOpenTracing Extensionを有効にし、またJAX-RSリソースクラスが
JSONを扱えるように「resteasy-jsonb」を追加しておきます。

$ mvn io.quarkus:quarkus-maven-plugin:0.15.0:create \
    -DprojectGroupId=org.littlewings \
    -DprojectArtifactId=api-b \
    -Dextensions="smallrye-opentracing, resteasy-jsonb"

できあがったMaven依存関係は、こちら。

  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-opentracing</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jsonb</artifactId>
    </dependency>
  </dependencies>

ここにさらに、RedisへのDistributed Tracingを適用するためにJedis向けのOpenTracingの実装を追加し、GraalVM(Substrate VM)も
追加しておきます(こちらは後述)。

    <dependency>
      <groupId>io.opentracing.contrib</groupId>
      <artifactId>opentracing-redis-jedis</artifactId>
      <version>0.0.9</version>
    </dependency>
    <dependency>
      <groupId>com.oracle.substratevm</groupId>
      <artifactId>svm</artifactId>
      <version>1.0.0-rc16</version>
      <scope>provided</scope>
    </dependency>

opentracing-redis-jedisのバージョンがちょっと古いのですが、これはQuarkusが使っているSmallRye OpenTracingのOpenTracing API
バージョンが0.31.0で、これに合ったopentracing-redis-jedisを選んでこないと実行時にエラーになるからです…。

では、ソースコードに移ります。

先に、JAX-RSリソースクラスから。Redisへのアクセスは、JedisServiceというクラス内で行います。 src/main/java/org/littlewings/quarkus/tracing/RedisResource.java

package org.littlewings.quarkus.tracing;

import java.util.Map;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("redis")
public class RedisResource {
    @Inject
    JedisService jedisService;

    @GET
    @Path("{key}")
    @Produces(MediaType.TEXT_PLAIN)
    public String get(@PathParam("key") String key) {
        return jedisService.with(jedis -> jedis.get(key));
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    public String put(Map<String, String> request) {
        jedisService.with(jedis -> jedis.set(request.get("key"), request.get("value")));

        return "OK!!";
    }
}

このクラスでOpenTracingが有効になるように、Quarkusの設定ファイルに設定を追加します。
src/main/resources/application.properties

quarkus.http.port=9080

quarkus.jaeger.service-name=api-b
quarkus.jaeger.sampler-type=const
quarkus.jaeger.sampler-param=1
quarkus.jaeger.endpoint=http://172.17.0.3:14268/api/traces

ひとつのホストにQuarkusアプリケーションを2つ起動するので、こちらはポートを9080にしました。設定自体は、以下を参考にして
行っています。

Opentracing Guide

続いて、Redisへアクセスするためのクラス。
src/main/java/org/littlewings/quarkus/tracing/JedisService.java

package org.littlewings.quarkus.tracing;

import java.util.function.Function;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;

import io.jaegertracing.Configuration;
import io.opentracing.Tracer;
import io.opentracing.contrib.redis.jedis.TracingJedisPool;
import io.opentracing.util.GlobalTracer;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol;

@ApplicationScoped
public class JedisService {
    @ConfigProperty(name = "jedis.host", defaultValue = "localhost")
    String jedisHost;

    @ConfigProperty(name = "jedis.port", defaultValue = "6379")
    int jedisPort;

    @ConfigProperty(name = "jedis.password")
    String jedisPassword;

    @ConfigProperty(name = "jedis.jaeger.service-name")
    String serviceName;

    @ConfigProperty(name = "quarkus.jaeger.sampler-type")
    String jaegerSamplerType;

    @ConfigProperty(name = "quarkus.jaeger.sampler-param")
    int jaegerSamplerParam;

    @ConfigProperty(name = "quarkus.jaeger.endpoint")
    String jaegerEndpoint;

    JedisPool jedisPool;

    @PostConstruct
    public void init() {
        Tracer tracer =
                new Configuration(serviceName)
                        .withSampler(new Configuration.SamplerConfiguration()
                                .withType(jaegerSamplerType)
                                .withParam(jaegerSamplerParam))
                        .withReporter(new Configuration.ReporterConfiguration()
                                .withSender(new Configuration.SenderConfiguration().withEndpoint(jaegerEndpoint)))
                        .getTracerBuilder()
                        .withScopeManager(GlobalTracer.get().scopeManager())
                        .build();

        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setJmxEnabled(false);

        jedisPool = new TracingJedisPool(
                config,
                jedisHost,
                jedisPort,
                Protocol.DEFAULT_TIMEOUT,
                jedisPassword,
                tracer,
                false
        );
    }

    public <T> T with(Function<Jedis, T> func) {
        try (Jedis jedis = jedisPool.getResource()) {
            return func.apply(jedis);
        }
    }
}

Jaegerで見た時のサービス名を買えるために、今回はTracerを個別に作成しました。

        Tracer tracer =
                new Configuration(serviceName)
                        .withSampler(new Configuration.SamplerConfiguration()
                                .withType(jaegerSamplerType)
                                .withParam(jaegerSamplerParam))
                        .withReporter(new Configuration.ReporterConfiguration()
                                .withSender(new Configuration.SenderConfiguration().withEndpoint(jaegerEndpoint)))
                        .getTracerBuilder()
                        .withScopeManager(GlobalTracer.get().scopeManager())
                        .build();

また、JMXを無効にしているのは、GraalVM(Substrate VM)でネイティブイメージを作る時に例外になるからです。

        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setJmxEnabled(false);

こんな感じで。

Caused by: java.lang.NullPointerException
    at org.apache.commons.pool2.impl.BaseGenericObjectPool.jmxRegister(BaseGenericObjectPool.java:957)
    at org.apache.commons.pool2.impl.BaseGenericObjectPool.<init>(BaseGenericObjectPool.java:133)
    at org.apache.commons.pool2.impl.GenericObjectPool.<init>(GenericObjectPool.java:107)
    at redis.clients.util.Pool.initPool(Pool.java:44)
    at redis.clients.util.Pool.<init>(Pool.java:23)
    at redis.clients.jedis.JedisPool.<init>(JedisPool.java:185)
    at redis.clients.jedis.JedisPool.<init>(JedisPool.java:162)
    at redis.clients.jedis.JedisPool.<init>(JedisPool.java:92)
    at io.opentracing.contrib.redis.jedis.TracingJedisPool.<init>(TracingJedisPool.java:200)

JedisServiceで、OpenTracing(Jaeger)に関する設定はサービス名以外はJAX-RSリソースクラスと同じものを使いますが、それ以外の
Redisへの接続情報も設定ファイルに記載しました。

最終的に、こんな感じで。
src/main/resources/application.properties

# Configuration file
# key = value

quarkus.http.port=9080

quarkus.jaeger.service-name=api-b
quarkus.jaeger.sampler-type=const
quarkus.jaeger.sampler-param=1
quarkus.jaeger.endpoint=http://172.17.0.3:14268/api/traces

jedis.jaeger.service-name=redis
jedis.host=172.17.0.2
jedis.port=6379
jedis.password=redispass

あと、Jedis向けのOpenTracingの実装を使った場合はリフレクションの情報を登録しておく必要があります。
src/main/java/org/littlewings/quarkus/tracing/OpenTracingSubstitutions.java

package org.littlewings.quarkus.tracing;

import com.oracle.svm.core.annotate.AutomaticFeature;
import org.apache.commons.pool2.impl.DefaultEvictionPolicy;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeReflection;

public class OpenTracingSubstitutions {
}

@AutomaticFeature
class RuntimeReflectionRegistrationFeature implements Feature {
    @Override
    public void beforeAnalysis(Feature.BeforeAnalysisAccess access) {
        try {
            RuntimeReflection.register(DefaultEvictionPolicy.class);
            RuntimeReflection.register(DefaultEvictionPolicy.class.getDeclaredConstructor());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
}

これをしないと、こうなります。

Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.DefaultEvictionPolicy
    at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:51)
    at java.lang.Class.forName(DynamicHub.java:1131)
    at org.apache.commons.pool2.impl.BaseGenericObjectPool.setEvictionPolicyClassName(BaseGenericObjectPool.java:606)
確認

では、パッケージングして確認してみましょう。このエントリ上は、ネイティブイメージでの確認のみとします。

$ mvn -Pnative package

起動。

$ ./target/api-b-1.0-SNAPSHOT-runner

確認

$ curl -XPUT -H 'Content-Type: application/json' localhost:9080/redis -d '{"key": "key1", "value": "value1"}'
OK!!

$ curl localhost:9080/redis/key1
value1



$ curl -XPUT -H 'Content-Type: application/json' localhost:9080/redis -d '{"key": "key2", "value": "value2"}'
OK!!

$ curl localhost:9080/redis/key2
value2

とりあえず、アプリケーションとしては動作しました。

では、Jaegerでトレーシングの情報を見てみましょう。「http://[Jaegerが動作しているホスト]:16686/」へアクセスしてみます。

f:id:Kazuhira:20190609113852p:plain

JaegerのWeb UIが表示されるので、左の「Search」の「Service」で「api-b」を選んで「Find Traces」を実行すると、先ほどcurl
確認した時のトレースの様子を見ることができます。

f:id:Kazuhira:20190609114118p:plain

どれかひとつ選んで詳細を見ると、「api-b」と「redis」のトレースの様子を見ることができます。うまくいっているようです。

f:id:Kazuhira:20190609114152p:plain

この「redis」を表示するために、こんな感じで自分でRedis用のTracerを作ったわけですが

    @PostConstruct
    public void init() {
        Tracer tracer =
                new Configuration(serviceName)
                        .withSampler(new Configuration.SamplerConfiguration()
                                .withType(jaegerSamplerType)
                                .withParam(jaegerSamplerParam))
                        .withReporter(new Configuration.ReporterConfiguration()
                                .withSender(new Configuration.SenderConfiguration().withEndpoint(jaegerEndpoint)))
                        .getTracerBuilder()
                        .withScopeManager(GlobalTracer.get().scopeManager())
                        .build();

        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setJmxEnabled(false);

        jedisPool = new TracingJedisPool(
                config,
                jedisHost,
                jedisPort,
                Protocol.DEFAULT_TIMEOUT,
                jedisPassword,
                tracer,
                false
        );
    }

Tracerを独自に作らない(サービス名を変更しない)場合は、グローバルなTracerを使うことになるので(CDIからTracerを取得してもOKです)

        jedisPool = new TracingJedisPool(
                config,
                jedisHost,
                jedisPort,
                Protocol.DEFAULT_TIMEOUT,
                jedisPassword,
                GlobalTracer.get(),
                false
        );

こんな感じになります。

f:id:Kazuhira:20190609114725p:plain

Redisへのアクセスも、「api-b」になります。これは、ちょっと違う気がします…。これを回避するために、ScopeManagerをTracer間で
共有したのですが、これで対処として合ってるんでしょうかね…。

        Tracer tracer =
                new Configuration(serviceName)
                        .withSampler(new Configuration.SamplerConfiguration()
                                .withType(jaegerSamplerType)
                                .withParam(jaegerSamplerParam))
                        .withReporter(new Configuration.ReporterConfiguration()
                                .withSender(new Configuration.SenderConfiguration().withEndpoint(jaegerEndpoint)))
                        .getTracerBuilder()
                        .withScopeManager(GlobalTracer.get().scopeManager())
                        .build();

Scopes

ここまでで、RedisへアクセスするAPIは完成です。

前段のAPIを作る

続いて、先ほど作成したAPIにアクセスするAPI、この表現上では「api-a」と呼んでいたものを作成していきましょう。

ディレクトリを作って

$ mkdir api-a
$ cd api-a

SmallRye OpenTracing Extension、RESTEasy JSON-B Extensionに加えて、SmallRye REST Client Extensionも有効にします。

$ mvn io.quarkus:quarkus-maven-plugin:0.15.0:create \
    -DprojectGroupId=org.littlewings \
    -DprojectArtifactId=api-a \
    -Dextensions="smallrye-opentracing, resteasy-jsonb, smallrye-rest-client"

Maven依存関係は、こんな感じ。

  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-opentracing</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jsonb</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-rest-client</artifactId>
    </dependency>
  </dependencies>

REST APIにアクセスするクライアントとして、SmallRye REST Clientというものを使うみたいなのですが

Rest Client Guide

GitHub - smallrye/smallrye-rest-client: Implementation has been moved to https://github.com/resteasy/Resteasy/tree/master/resteasy-client-microprofile

こちらは、呼び出すREST APIに対応するJAX-RSリソース関係のアノテーションを使ったインターフェースを作成し、このインターフェースに
対してAPI呼び出しを書いていく使い方になります。

Rest Client for MicroProfileの実装のようです。

GitHub - eclipse/microprofile-rest-client: MicroProfile Rest Client

ちなみに、SmallRye REST Clientの実装はRESTEasyに移ったみたいですけどね…。

Resteasy/resteasy-client-microprofile at master · resteasy/Resteasy · GitHub

というわけで、先ほど作った「api-b」に対するインターフェースを作成します。
src/main/java/org/littlewings/quarkus/tracing/RedisService.java

package org.littlewings.quarkus.tracing;

import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

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

@Path("redis")
@RegisterRestClient
public interface RedisService {
    @GET
    @Path("{key}")
    @Produces(MediaType.TEXT_PLAIN)
    String get(@PathParam("key") String key);

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    String put(Map<String, String> request);
}

api-b」で作成したJAX-RSリソースクラスの定義を、そのままインターフェースにした感じですね。

このインターフェースを使った、JAX-RSリソースクラスを作成します。 src/main/java/org/littlewings/quarkus/tracing/FrontResource.java

package org.littlewings.quarkus.tracing;

import java.util.Map;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

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

@Path("")
public class FrontResource {
    @Inject
    @RestClient
    RedisService redisService;

    @GET
    @Path("{key}")
    @Produces(MediaType.TEXT_PLAIN)
    public String get(@PathParam("key") String key) {
        return redisService.get(key);
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    public String put(Map<String, String> request) {
        return redisService.put(request);
    }
}

CDIで@Injectする時に、@RestClientアノテーションがついているところがポイントです。

    @Inject
    @RestClient
    RedisService redisService;

このインターフェースを使って、API呼び出しを書いていきます。

    @GET
    @Path("{key}")
    @Produces(MediaType.TEXT_PLAIN)
    public String get(@PathParam("key") String key) {
        return redisService.get(key);
    }

src/main/resources/application.properties

# Configuration file
# key = value

org.littlewings.quarkus.tracing.RedisService/mp-rest/url=http://localhost:9080

quarkus.jaeger.service-name=api-a
quarkus.jaeger.sampler-type=const
quarkus.jaeger.sampler-param=1
quarkus.jaeger.endpoint=http://172.17.0.3:14268/api/traces

Jaegerの設定は「api-b」の時と同じとして、呼び出すAPIへのベースのURLは以下のように設定します。

org.littlewings.quarkus.tracing.RedisService/mp-rest/url=http://localhost:9080

「作成したインターフェース名/mp-rest/url」ですね。これは、Rest Client for MicroProfileでConfiguration for MicroProfileを使って
設定する場合の仕様のようです。

https://github.com/eclipse/microprofile-rest-client/blob/1.2.1/spec/src/main/asciidoc/cdi.asciidoc#support-for-microprofile-config

確認

では、ネイティブイメージにビルドして

$ mvn -Pnative package

確認してみましょう。

$ ./target/api-a-1.0-SNAPSHOT-runner

あ、1度JaegerとRedisは再起動して、まっさらにしました。

api-aにアクセスして、確認。

$ curl -XPUT -H 'Content-Type: application/json' localhost:8080 -d '{"key": "key1", "value": "value1"}'
OK!!

$ curl localhost:8080/key1
value1


$ curl -XPUT -H 'Content-Type: application/json' localhost:8080 -d '{"key": "key2", "value": "value2"}'
OK!!

$ curl localhost:8080/key2
value2

Jaegerで確認してみます。

f:id:Kazuhira:20190609121228p:plain

Rest Client側に特にTracerの仕込みをしていないので(できそうでしたが、だいぶ面倒そうだったので…)、「api-a」が2回登場しています…。
片方は、REST Client分ですね。

あと、同じアプリケーション内で開始される各Spanの開始時間があまりにも揃っていたので、おかしなことになっていないか気になって
調べてみましたが、一応Child Spanの扱いになっているようです。

デバッガーで止めて見ていたら、Spanの開始時間も合わせて遅れるので、それに合わせてずれていきました…。

ネイティブイメージではなくて、Javaで実行するともうちょっとハッキリしたりしました。

f:id:Kazuhira:20190609123907p:plain

f:id:Kazuhira:20190609124112p:plain

一応、やりたいことはできたようです。

まとめ

QuarkusのOpenTracing Extensionを使いつつ、RedisへアクセスしたりREST Clientを組み込んだりと、ちょっとガイドの幅を広げて
試してみました。

Jaegerを使うのが初めてだったり、Spanの扱いでだいぶてこずったのですが、とりあえずはなんとかなった感じが…。

Quarkusに限らず、Distributed Tracingまわりはもうちょっと勉強したいですね。

オマケ

ところで、Jedisを使ったアクセスは今回は妙な形態になっていて、Service内に閉じ込め@PostConstructでJedisPoolを構築するように
しています。

これ、最初はCDIのProducerで作って
src/main/java/org/littlewings/quarkus/tracing/JedisProducer.java

package org.littlewings.quarkus.tracing;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;

import io.opentracing.Tracer;
import io.opentracing.contrib.redis.jedis.TracingJedisPool;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import redis.clients.jedis.JedisPool;

@Dependent
public class JedisProducer {
    @ConfigProperty(name = "jedis.host", defaultValue = "localhost")
    String jedisHost;

    @ConfigProperty(name = "jedis.port", defaultValue = "6379")
    int jedisPort;

    @ApplicationScoped
    @Produces
    public JedisPool create(Tracer tracer) {
        return new TracingJedisPool(jedisHost, jedisPort, tracer, false);
    }

    public void close(@Disposes JedisPool jedisPool) {
        jedisPool.close();
    }
}

JedisPool自体をインジェクションしようとしていたのですが

@Path("redis")
public class RedisResource {
    @ConfigProperty(name = "jedis.password", defaultValue = "redispass")
    String jedisPassword;

    @Inject
    JedisPool jedisPool;

    @GET
    @Path("{key}")
    @Produces(MediaType.TEXT_PLAIN)
    public String get(@PathParam("key") String key) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.auth(jedisPassword);
            return jedis.get(key);
        }
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    public String put(Map<String, String> request) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.auth(jedisPassword);
            jedis.set(request.get("key"), request.get("value"));
        }

        return "OK!!";
    }
}

うまくいかず…。

Caused by: java.lang.NullPointerException
    at org.littlewings.quarkus.tracing.JedisProducer_ProducerMethod_create_2d0be3d75acf800db488a1594f00787b8e8bd27e_ClientProxy.delegate(Unknown Source)
    at org.littlewings.quarkus.tracing.JedisProducer_ProducerMethod_create_2d0be3d75acf800db488a1594f00787b8e8bd27e_ClientProxy.initPool(Unknown Source)
    at redis.clients.util.Pool.<init>(Pool.java:23)
    at redis.clients.jedis.JedisPool.<init>(JedisPool.java:185)
    at redis.clients.jedis.JedisPool.<init>(JedisPool.java:162)
    at redis.clients.jedis.JedisPool.<init>(JedisPool.java:28)
    at redis.clients.jedis.JedisPool.<init>(JedisPool.java:19)
    at org.littlewings.quarkus.tracing.JedisProducer_ProducerMethod_create_2d0be3d75acf800db488a1594f00787b8e8bd27e_ClientProxy.<init>(Unknown Source)
    at org.littlewings.quarkus.tracing.JedisProducer_ProducerMethod_create_2d0be3d75acf800db488a1594f00787b8e8bd27e_Bean$$function$$3.get(Unknown Source)
    at io.quarkus.arc.LazyValue.get(LazyValue.java:42)
    at org.littlewings.quarkus.tracing.JedisProducer_ProducerMethod_create_2d0be3d75acf800db488a1594f00787b8e8bd27e_Bean.get(Unknown Source)
    at org.littlewings.quarkus.tracing.JedisProducer_ProducerMethod_create_2d0be3d75acf800db488a1594f00787b8e8bd27e_Bean.get(Unknown Source)
    at org.littlewings.quarkus.tracing.RedisResource_Bean.create(Unknown Source)
    at org.littlewings.quarkus.tracing.RedisResource_Bean.create(Unknown Source)
    at io.quarkus.arc.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:86)
    at io.quarkus.arc.AbstractSharedContext.lambda$new$0(AbstractSharedContext.java:33)
    at io.quarkus.arc.ComputingCache$CacheFunction.lambda$apply$0(ComputingCache.java:115)
    at io.quarkus.arc.LazyValue.get(LazyValue.java:42)
    at io.quarkus.arc.ComputingCache.getValue(ComputingCache.java:57)
    at io.quarkus.arc.AbstractSharedContext.get(AbstractSharedContext.java:39)
    at org.littlewings.quarkus.tracing.RedisResource_Bean.get(Unknown Source)
    at org.littlewings.quarkus.tracing.RedisResource_Bean.get(Unknown Source)
    at io.quarkus.arc.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:310)
    at io.quarkus.arc.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:322)
    at io.quarkus.arc.ArcContainerImpl$1.get(ArcContainerImpl.java:201)
    at io.quarkus.arc.ArcContainerImpl$1.get(ArcContainerImpl.java:198)
    at io.quarkus.arc.runtime.ArcDeploymentTemplate$2$1.create(ArcDeploymentTemplate.java:86)
    at io.quarkus.resteasy.server.common.runtime.QuarkusConstructorInjector.construct(QuarkusConstructorInjector.java:72)
    at org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory.createResource(POJOResourceFactory.java:69)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:340)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:477)
    ... 56 more

JedisPoolを作る時の、コンストラクタ引数の扱いが…?なことに…?

今回は、ここは深追いしませんでしたが…。