Skip to content

Implement your own service discovery mechanism

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

Dependencies

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

<dependency>
    <groupId>io.smallrye.stork</groupId>
    <artifactId>stork-core</artifactId>
    <version>1.1.1</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>1.1.1</version>
</dependency>

Implementing a service discovery provider

Service discovery implementation consists of three elements:

  • ServiceDiscovery which is responsible for locating service instances for a single Stork service.
  • ServiceDiscoveryProvider which creates instances of ServiceDiscovery for a given service discovery type.
  • $typeConfiguration which is a configuration for the discovery. This class is automatically generated.

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

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

A ServiceDiscoveryProvider implementation needs to be annotated with @ServiceDiscoveryType that defines the type. Any configuration properties that the provider expects should be defined with @ServiceDiscoveryAttribute annotations placed on the provider.

A service discovery provider class should look as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package examples;

import io.smallrye.stork.api.ServiceDiscovery;
import io.smallrye.stork.api.config.ServiceConfig;
import io.smallrye.stork.api.config.ServiceDiscoveryAttribute;
import io.smallrye.stork.api.config.ServiceDiscoveryType;
import io.smallrye.stork.spi.StorkInfrastructure;
import io.smallrye.stork.spi.ServiceDiscoveryProvider;

@ServiceDiscoveryType("acme")
@ServiceDiscoveryAttribute(name = "host",
        description = "Host name of the service discovery server.", required = true)
@ServiceDiscoveryAttribute(name = "port",
        description = "Hort of the service discovery server.", required = false)
public class AcmeServiceDiscoveryProvider
        implements ServiceDiscoveryProvider<AcmeConfiguration> {

    @Override
    public ServiceDiscovery createServiceDiscovery(
            AcmeConfiguration config,
            String serviceName,
            ServiceConfig serviceConfig,
            StorkInfrastructure storkInfrastructure) {
        return new AcmeServiceDiscovery(config);
    }
}

Note, that the ServiceDiscoveryProvider interface takes a configuration class as a parameter. This configuration class is generated automatically by the Configuration Generator. Its name is created by appending Configuration to the service discovery type, such as AcmeConfiguration.

The next step is to implement the ServiceDiscovery interface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package examples;

import java.util.Collections;
import java.util.List;

import io.smallrye.mutiny.Uni;
import io.smallrye.stork.api.ServiceDiscovery;
import io.smallrye.stork.api.ServiceInstance;
import io.smallrye.stork.impl.DefaultServiceInstance;
import io.smallrye.stork.utils.ServiceInstanceIds;

public class AcmeServiceDiscovery implements ServiceDiscovery {

    private final String host;
    private final int port;

    public AcmeServiceDiscovery(AcmeConfiguration configuration) {
        this.host = configuration.getHost();
        this.port = Integer.parseInt(configuration.getPort());
    }

    @Override
    public Uni<List<ServiceInstance>> getServiceInstances() {
        // Proceed to the lookup...
        // Here, we just return a DefaultServiceInstance with the configured host and port
        // The last parameter specifies whether the communication with the instance should
        // happen over a secure connection
        DefaultServiceInstance instance =
                new DefaultServiceInstance(ServiceInstanceIds.next(), host, port, false);
        return Uni.createFrom().item(() -> Collections.singletonList(instance));
    }
}

This implementation is simplistic. Typically, instead of creating a service instance with values from the configuration, you would connect to a service discovery 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.

Using your service discovery

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-discovery.type=acme
stork.my-service.service-discovery.host=localhost
stork.my-service.service-discovery.port=1234
quarkus.stork.my-service.service-discovery.type=acme
quarkus.stork.my-service.service-discovery.host=localhost
quarkus.stork.my-service.service-discovery.port=1234

Then, Stork will use your implementation to locate the my-service service.

Using your service discovery using the programmatic API

When building your service discovery project, the configuration generator creates a configuration class. This class can be used to configure your service discovery using the Stork programmatic API.

```java linenums="1"
package examples;

import io.smallrye.mutiny.Uni;
import io.smallrye.stork.api.ServiceDefinition;
import io.smallrye.stork.api.ServiceInstance;
import io.smallrye.stork.api.StorkServiceRegistry;

public class AcmeDiscoveryApiUsage {

    public void example(StorkServiceRegistry stork) {
        stork.defineIfAbsent("my-service", ServiceDefinition.of(
                new AcmeConfiguration().withHost("my-host"))
        );

        Uni<ServiceInstance> uni = stork.getService("my-service").selectInstance();
    }

}

Remember that attributes, like host, are declared using the @ServiceDiscoveryAttribute annotation on the ServiceDiscoveryProvider implementation.