Skip to content

Implement your own service registration mechanism#

Stork is extensible, and you can implement your own service registrar mechanism.

Dependencies#

To implement your Service Registration Provider, make sure your project depends on Core and Configuration Generator. The former brings classes necessary to implement custom registrar, the latter contains an annotation processor that generates classes needed by Stork.

<dependency>
    <groupId>io.smallrye.stork</groupId>
    <artifactId>stork-core</artifactId>
    <version>2.7.4</version>
</dependency>
<dependency>
    <groupId>io.smallrye.stork</groupId>
    <artifactId>stork-configuration-generator</artifactId>
    <scope>provided</scope>
    <!-- provided scope is sufficient for the annotation processor -->
    <version>2.7.4</version>
</dependency>

Implementing a service registrar provider#

Service registration implementation consists of three elements:

  • ServiceRegistrar which is responsible for registering service instances for a single Stork service.
  • ServiceRegistrarProvider which creates instances of ServiceRegistrar for a given service registrar type.
  • $typeConfiguration which is a configuration for the registrar. This class is automatically generated during the compilation (using an annotation processor).

A type, for example, acme, identifies each provider. This type is used in the configuration to reference the provider:

stork.my-service.service-registrar.type=acme
quarkus.stork.my-service.service-registrar.type=acme

A ServiceRegistrarProvider implementation needs to be annotated with @ServiceRegistrarType that defines the type. Any configuration properties that the provider expects should be defined with @ServiceRegistrarAttribute annotations placed on the provider. Optionally, you can also add @ApplicationScoped annotation in order to provide the service registrar implementation as CDI bean.

A service registrar provider class should look as follows:

package examples;

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 = "acme", 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 AcmeServiceRegistrarProvider
        implements ServiceRegistrarProvider<AcmeRegistrarConfiguration, Metadata.DefaultMetadataKey> {

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

}

Note, that the ServiceRegistrarProvider interface takes a configuration class as a parameter. This configuration class is generated automatically by the Configuration Generator. Its name is created by appending RegistrarConfiguration to the service registrar type, such as AcmeRegistrarConfiguration.

The next step is to implement the ServiceRegistrar interface:

package examples;

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

public class AcmeServiceRegistrar implements ServiceRegistrar {

    private final String backendHost;
    private final int backendPort;

    public AcmeServiceRegistrar(AcmeRegistrarConfiguration configuration) {
        this.backendHost = configuration.getHost();
        this.backendPort = Integer.parseInt(configuration.getPort());
    }


    @Override
    public Uni<Void> registerServiceInstance(String serviceName, Metadata metadata, String ipAddress, int defaultPort) {
        //do whatever is needed for registering service instance
        return Uni.createFrom().voidItem();
    }

    @Override
    public Uni<Void> deregisterServiceInstance(String serviceName) {
        //do whatever is needed for deregistering service instance
        return Uni.createFrom().voidItem();
    }
}

This implementation is simplistic. Typically, instead of creating a service instance with values from the configuration, you would connect to a service registration backend, look for the service and build the list of service instance accordingly. That’s why the method returns a Uni. Most of the time, the lookup is a remote operation.

As you can see, the AcmeConfiguration class gives access to the configuration attribute.

Implement your own service deregistration mechanism#

Finally, you can implement your own service deregistration mechanism in deregisterServiceInstance method.

Using your service registrar using the programmatic API#

In the project using it, don’t forget to add the dependency on the module providing your implementation. Then, in the configuration, just add:

stork.my-service.service-registrar.type=acme
stork.my-service.service-registrar.host=localhost
stork.my-service.service-registrar.port=1234
quarkus.stork.my-service.service-registrar.type=acme
quarkus.stork.my-service.service-registrar.host=localhost
quarkus.stork.my-service.service-registrar.port=1234

When building your service registrar project project, the configuration generator creates a configuration class. Then, Stork will use your implementation to register/deregister the service instances using the acme backend.

This class can be used to configure your service registrar using the Stork programmatic API.

package examples;

import io.smallrye.stork.api.ServiceDefinition;
import io.smallrye.stork.api.StorkServiceRegistry;
import io.smallrye.stork.servicediscovery.staticlist.StaticConfiguration;

public class AcmeRegistrarApiUsage {

    public void example(StorkServiceRegistry stork) {
        String list = "localhost:8080, localhost:8081";

        stork.defineIfAbsent("my-service", ServiceDefinition.of(
                new StaticConfiguration().withAddressList(list),
                new AcmeLoadBalancerConfiguration().withMyAttribute("my-value"),new AcmeRegistrarConfiguration())
        );

        stork.getService("my-service").registerInstance("my-service", "localhost",
                9000);
    }

}