Implementing a custom Stork service registrar

preview

This guide explains how to implement a custom service registrar for SmallRye Stork and use it to programmatically register service instances at startup.

If you are new to Stork, please read the Stork Getting Started Guide.

这项技术被认为是preview。

在 preview(预览) 中,不保证向后兼容和在生态系统中的存在。具体的改进可能需要改变配置或API,并且正在计划变得 稳定 。欢迎在我们的 邮件列表 中提供反馈,或在我们的 GitHub问题列表 中提出问题。

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

先决条件

要完成这个指南,你需要:

  • 大概15分钟

  • 编辑器

  • JDK 17+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.9.16

  • 如果你愿意的话,还可以选择使用Quarkus CLI

  • 如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)

应用结构

In this guide, we will build an application that:

  • Implements a custom service registrar by extending Stork’s SPI

  • Programmatically registers a service instance at startup

  • Configures the registrar via application.properties

Beyond service discovery and load balancing, it also supports service registration. Service registration is the process of announcing a service instance to a registry so that other services can discover it. While Stork provides built-in registrars for Consul, Eureka, and others, you can implement your own by using the Stork SPI.

解决方案

我们建议您按照下一节的说明逐步创建应用程序。然而,您可以直接转到已完成的示例。

克隆 Git 仓库: git clone https://github.com/quarkusio/quarkus-quickstarts.git ,或下载一个 存档 。

The solution is located in the stork-programmatic-custom-registration-quickstart directory.

创建项目

Create a Quarkus project importing the quarkus-rest, quarkus-smallrye-stork, and quarkus-rest-client-jackson extensions using your favorite approach:

CLI
quarkus create app org.acme:stork-programmatic-custom-registration-quickstart \
    --extension='quarkus-rest,quarkus-smallrye-stork,quarkus-rest-client-jackson' \
    --no-code
cd stork-programmatic-custom-registration-quickstart

创建Grade项目,请添加 --gradle 或者 --gradle-kotlin-dsl 参数。

For more information about how to install and use the Quarkus CLI, see the Quarkus CLI guide.

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.39.5:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=stork-programmatic-custom-registration-quickstart \
    -Dextensions='quarkus-rest,quarkus-smallrye-stork,quarkus-rest-client-jackson' \
    -DnoCode
cd stork-programmatic-custom-registration-quickstart

创建Grade项目,请添加 -DbuildTool=gradle 或者 -DbuildTool=gradle-kotlin-dsl 参数。

For Windows users:

  • If using cmd, (don’t use backward slash \ and put everything on the same line)

  • If using Powershell, wrap -D parameters in double quotes e.g. "-DprojectArtifactId=stork-programmatic-custom-registration-quickstart"

In the generated project, also add the following dependencies:

pom.xml
<dependency>
    <groupId>io.smallrye.stork</groupId>
    <artifactId>stork-core</artifactId>
</dependency>
<dependency>
    <groupId>io.smallrye.stork</groupId>
    <artifactId>stork-configuration-generator</artifactId>
</dependency>
build.gradle
implementation("io.smallrye.stork:stork-core")
implementation("io.smallrye.stork:stork-configuration-generator")

stork-core provides the Stork API and SPI needed to implement a custom registrar. stork-configuration-generator generates configuration classes at build time based on annotations on your registrar provider.

The Custom Service Registrar Provider

Stork uses a provider model. To create a custom registrar, you need two classes:

  1. A provider implementing ServiceRegistrarProvider — the factory that creates registrar instances.

  2. A registrar implementing ServiceRegistrar — the actual registration logic.

Let’s start with the provider. Create the src/main/java/org/acme/services/CustomServiceRegistrarProvider.java file with the following content:

package org.acme.services;

import io.smallrye.stork.api.Metadata;
import io.smallrye.stork.api.ServiceRegistrar;
import io.smallrye.stork.api.config.ServiceRegistrarAttribute;
import io.smallrye.stork.api.config.ServiceRegistrarType;
import io.smallrye.stork.spi.ServiceRegistrarProvider;
import io.smallrye.stork.spi.StorkInfrastructure;
import jakarta.enterprise.context.ApplicationScoped;

@ServiceRegistrarType(value = "custom", metadataKey = Metadata.DefaultMetadataKey.class)
@ServiceRegistrarAttribute(name = "host",
        description = "Host name of the service registration server.", required = true)
@ServiceRegistrarAttribute(name = "port",
        description = "Port of the service registration server.", required = false)
@ApplicationScoped
public class CustomServiceRegistrarProvider
        implements ServiceRegistrarProvider<CustomRegistrarConfiguration, Metadata.DefaultMetadataKey> {

    @Override
    public ServiceRegistrar createServiceRegistrar(
            CustomRegistrarConfiguration config,
            String serviceName,
            StorkInfrastructure storkInfrastructure) {
        return new CustomServiceRegistrar(config);
    }

}

There are a few important things to note:

  • The @ServiceRegistrarType annotation declares the registrar type. This is the value used in the quarkus.stork.<service-name>.service-registrar.type property. Here, it is custom.

  • The @ServiceRegistrarAttribute annotations declare the configuration attributes for this registrar. Stork’s annotation processor (provided by stork-configuration-generator) uses these annotations to generate the CustomRegistrarConfiguration class at compile time. You do not need to write this class yourself.

  • The @ApplicationScoped annotation makes this provider a CDI bean, so Stork can discover it automatically.

  • The createServiceRegistrar method is the factory that creates a CustomServiceRegistrar from the generated configuration.

The Custom Service Registrar

Now let’s implement the registrar itself. Create the src/main/java/org/acme/services/CustomServiceRegistrar.java file with the following content:

package org.acme.services;

import io.smallrye.mutiny.Uni;
import io.smallrye.stork.api.Metadata;
import io.smallrye.stork.api.ServiceRegistrar;

import org.jboss.logging.Logger;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class CustomServiceRegistrar implements ServiceRegistrar {

    private static final Logger LOGGER = Logger.getLogger(CustomServiceRegistrar.class.getName());

    private final String backendHost;
    private final int backendPort;
    private final Map<String, String> registeredInstances = new ConcurrentHashMap<>();

    public CustomServiceRegistrar(CustomRegistrarConfiguration configuration) {
        this.backendHost = configuration.getHost();
        this.backendPort = Integer.parseInt(configuration.getPort()!=null?configuration.getPort():"8080");
    }


    @Override
    public Uni<Void> registerServiceInstance(String serviceName, Metadata metadata, String ipAddress, int defaultPort) {
        String address = ipAddress + ":" + defaultPort;
        LOGGER.info("Registering service: " + serviceName + " with ipAddress: " + ipAddress + " and port: " + defaultPort);
        registeredInstances.put(serviceName, address);
        return Uni.createFrom().voidItem();
    }

    @Override
    public Uni<Void> deregisterServiceInstance(String serviceName) {
        LOGGER.infof("Deregistering service '%s' from backend %s:%d", serviceName, backendHost, backendPort);
        registeredInstances.remove(serviceName);
        return Uni.createFrom().voidItem();
    }
}

This registrar:

  • Receives its configuration (host and port) from the generated CustomRegistrarConfiguration class.

  • Stores registered instances in a ConcurrentHashMap.

  • Implements registerServiceInstance to add a service instance to the map.

  • Implements deregisterServiceInstance to remove a service instance on shutdown.

  • Both methods return Uni<Void>, since Stork uses Mutiny for reactive support.

Programmatic Service Registration at Startup

With the registrar in place, we can now register service instances programmatically. Create the src/main/java/org/acme/services/Registration.java file with the following content:

package org.acme.services;

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

import io.quarkus.runtime.StartupEvent;
import io.smallrye.stork.Stork;
import io.vertx.mutiny.core.Vertx;

@ApplicationScoped
public class Registration {

    /**
     * Register our two services using custom registrar.
     *
     * Note: this method is called on a worker thread, and so it is allowed to block.
     */
    public void init(@Observes StartupEvent ev, Vertx vertx) {
        Stork.getInstance().getService("my-service").registerInstance("my-service", "localhost",
                9000);
    }
}

When the application starts, this CDI bean observes the StartupEvent and uses the Stork API to register a service instance. The Stork.getInstance().getService("my-service") call retrieves the Stork service named my-service, and registerInstance delegates to the custom registrar we created earlier.

Stork configuration

Now we need to configure Stork to use our custom registrar.

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

quarkus.stork.my-service.service-registrar.type=custom
quarkus.stork.my-service.service-registrar.host=localhost

The quarkus.stork.my-service.service-registrar.type property tells Stork to use the custom registrar type. This matches the value declared in the @ServiceRegistrarType annotation on CustomServiceRegistrarProvider.

The quarkus.stork.my-service.service-registrar.host property maps to the host attribute declared with @ServiceRegistrarAttribute on the provider. This is a required attribute.

You can optionally set quarkus.stork.my-service.service-registrar.port as well, since the provider declares an optional port attribute. If omitted, the registrar defaults to port 8080.

运行应用程序

Package the application:

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

And run it:

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

You should see the following log message in the output, confirming that the custom registrar is invoked:

INFO  [org.acm.ser.CustomServiceRegistrar] Registering service: my-service with ipAddress: localhost and port: 9000

You can compile this application into a native executable:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

Summary

In this guide, we have:

  1. Implemented a custom ServiceRegistrarProvider using the @ServiceRegistrarType and @ServiceRegistrarAttribute annotations.

  2. Implemented a custom ServiceRegistrar with registerServiceInstance and deregisterServiceInstance methods.

  3. Used the Stork API to programmatically register a service instance at startup.

  4. Configured the custom registrar in application.properties.

This pattern is useful when you need to integrate Stork with a service registry that is not supported out of the box.

进一步探索

This guide has shown how to extend SmallRye Stork with a custom service registrar. You can find more about Stork in: