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

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.

Prerequisites

完成这个指南,你需要:

  • 大概15分钟

  • 编辑器

  • JDK 17+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.9.15

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

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

Architecture

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.

Solution

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

Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git, or download an archive.

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

Bootstrapping the project

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.35.4: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.

Running the application

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.

Going further

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

Related content