Repository: smallrye/smallrye-stork
Pull request #962 (2024-10-21 09:57:35)

Fail service registration if address is not provided


Patch:
diff --git a/api/src/main/java/io/smallrye/stork/api/ServiceRegistrar.java b/api/src/main/java/io/smallrye/stork/api/ServiceRegistrar.java
index b8d95547..04d8889d 100644
--- a/api/src/main/java/io/smallrye/stork/api/ServiceRegistrar.java
+++ b/api/src/main/java/io/smallrye/stork/api/ServiceRegistrar.java
@@ -5,9 +5,16 @@ import io.smallrye.mutiny.Uni;
 public interface ServiceRegistrar<MetadataKeyType extends Enum<MetadataKeyType> & MetadataKey> {
 
     default Uni<Void> registerServiceInstance(String serviceName, String ipAddress, int defaultPort) {
+        checkAddressNotNull(ipAddress);
         return registerServiceInstance(serviceName, Metadata.empty(), ipAddress, defaultPort);
     }
 
+    default void checkAddressNotNull(String ipAddress) {
+        if (ipAddress == null || ipAddress.isEmpty() || ipAddress.isBlank()) {
+            throw new IllegalArgumentException("Parameter ipAddress should be provided.");
+        }
+    }
+
     Uni<Void> registerServiceInstance(String serviceName, Metadata<MetadataKeyType> metadata, String ipAddress,
             int defaultPort);
 
diff --git a/docs/docs/load-balancer/overview.md b/docs/docs/load-balancer/overview.md
new file mode 100644
index 00000000..10ec349f
--- /dev/null
+++ b/docs/docs/load-balancer/overview.md
@@ -0,0 +1,16 @@
+### Load Balancer / Service Selection in SmallRye Stork
+
+Once services are registered and discovered, the next critical step is selecting which service instance will handle a given request. 
+SmallRye Stork provides flexible load balancing strategies to efficiently distribute requests across multiple instances of a service. 
+This ensures optimal resource usage, improved performance, and high availability.
+
+#### Key Features:
+- **Multiple Load Balancing Strategies**: SmallRye Stork supports several built-in strategies for selecting service instances. 
+Check them out in the following dedicated sections.
+- **Customizable Strategies**: You can define custom service selection strategies based on your unique use case or performance requirements, ensuring that the load balancer can adapt to specific needs.
+
+#### How it Works:
+Once a service has been registered and discovered, the load balancer comes into play when a client makes a request to that service. 
+Stork applies the configured load balancing strategy to select an instance from the available pool of discovered services.
+
+This feature ensures that your services remain responsive, scalable, and resilient, providing a smooth experience for both users and developers.
diff --git a/docs/docs/service-discovery/overview.md b/docs/docs/service-discovery/overview.md
new file mode 100644
index 00000000..abeb7173
--- /dev/null
+++ b/docs/docs/service-discovery/overview.md
@@ -0,0 +1,12 @@
+### Service Discovery in SmallRye Stork
+
+As already introduced, service discovery is a crucial part of modern microservices architectures. 
+It allows services to dynamically discover the location of other services at runtime, which is particularly useful in distributed systems where services may scale up or down,
+or change their network addresses.
+
+SmallRye Stork provides a flexible and extensible mechanism for service discovery.
+It supports out of the box some service discovery such as Kubernetes or Consul but the main strength of it is customization so you can easily create your own implementation related on your business for example.
+Stork allows services to communicate with each other without requiring hardcoded addresses, making it an ideal solution for microservices deployments.
+SmallRye Stork brings this capability to clients for Quarkus applications but it's vendor agnostic so you easily use it with other solutions and even in standalone mode.
+
+You can explore the different implementations and learn how to create your own in the following sections.
\ No newline at end of file
diff --git a/docs/docs/service-registration/overview.md b/docs/docs/service-registration/overview.md
new file mode 100644
index 00000000..b4e3d3ac
--- /dev/null
+++ b/docs/docs/service-registration/overview.md
@@ -0,0 +1,19 @@
+### Service Registration in SmallRye Stork
+
+Service registration is the process by which services announce their availability to a central registry, allowing other services to discover and communicate with them. 
+In SmallRye Stork, service registration is automated and integrated with supported registries like **Consul**. 
+This ensures that services can dynamically join and leave the network.
+
+#### Key Features:
+- **Automatic Registration**: For Quarkus applications, SmallRye Stork automatically registers it with the configured service registry (e.g., Consul).
+
+**IMPORTANT** Public IP address needs to be provided. Smallrye Stork will fail if the service IP address is not provided during registration.
+
+#### Supported Registries:
+Currently, Smallrye Stork provides seamless integration with **Consul**, Eureka and a Static registry.
+This integration simplifies the management of dynamic environments where services are frequently added or removed.
+
+#### Custom Registration:
+In addition to the default mechanisms, SmallRye Stork allows you to implement custom service registration strategies, providing flexibility for different infrastructures or custom service discovery needs.
+
+In the following sections you can have more details about each specific implementation.
\ No newline at end of file
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 7151f6e0..d684dafd 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -14,6 +14,7 @@ nav:
   - Javadoc: 'https://javadoc.io/doc/io.smallrye.stork/smallrye-stork-api/latest/index.html'
   - Using Stork with Quarkus: './quarkus.md'
   - Service Discovery:
+    - Overview: 'service-discovery/overview.md'
     - Consul: 'service-discovery/consul.md'
     - DNS: 'service-discovery/dns.md'
     - Kubernetes: 'service-discovery/kubernetes.md'
@@ -23,6 +24,7 @@ nav:
     - Static List: 'service-discovery/static-list.md'
     - Custom Service Discovery: 'service-discovery/custom-service-discovery.md'
   - Load-Balancing:
+    - Overview: 'load-balancer/overview.md'
     - Round Robin: 'load-balancer/round-robin.md'
     - Random: 'load-balancer/random.md'
     - Least Requests: 'load-balancer/least-requests.md'
@@ -31,6 +33,7 @@ nav:
     - Sticky: 'load-balancer/sticky.md'
     - Custom Load Balancer: 'load-balancer/custom-load-balancer.md'
   - Service Registration:
+      - Overview: 'service-registration/overview.md'
       - Consul: 'service-registration/consul.md'
       - Eureka: 'service-registration/eureka.md'
       - Static List: 'service-registration/static-list.md'
diff --git a/service-registration/consul/src/main/java/io/smallrye/stork/serviceregistration/consul/ConsulServiceRegistrar.java b/service-registration/consul/src/main/java/io/smallrye/stork/serviceregistration/consul/ConsulServiceRegistrar.java
index 0d56009a..5a1e8e33 100644
--- a/service-registration/consul/src/main/java/io/smallrye/stork/serviceregistration/consul/ConsulServiceRegistrar.java
+++ b/service-registration/consul/src/main/java/io/smallrye/stork/serviceregistration/consul/ConsulServiceRegistrar.java
@@ -37,6 +37,7 @@ public class ConsulServiceRegistrar implements ServiceRegistrar<ConsulMetadataKe
     @Override
     public Uni<Void> registerServiceInstance(String serviceName, Metadata<ConsulMetadataKey> metadata, String ipAddress,
             int defaultPort) {
+        checkAddressNotNull(ipAddress);
 
         String consulId = metadata.getMetadata().get(ConsulMetadataKey.META_CONSUL_SERVICE_ID).toString();
 
diff --git a/service-registration/eureka/src/main/java/io/smallrye/stork/serviceregistration/eureka/EurekaServiceRegistrar.java b/service-registration/eureka/src/main/java/io/smallrye/stork/serviceregistration/eureka/EurekaServiceRegistrar.java
index eb487e2f..5338b4c4 100644
--- a/service-registration/eureka/src/main/java/io/smallrye/stork/serviceregistration/eureka/EurekaServiceRegistrar.java
+++ b/service-registration/eureka/src/main/java/io/smallrye/stork/serviceregistration/eureka/EurekaServiceRegistrar.java
@@ -41,6 +41,8 @@ public class EurekaServiceRegistrar implements ServiceRegistrar<EurekaMetadataKe
     public Uni<Void> registerServiceInstance(String serviceName, Metadata<EurekaMetadataKey> metadata, String ipAddress,
             int defaultPort) {
 
+        checkAddressNotNull(ipAddress);
+
         return registerApplicationInstance(client, serviceName,
                 metadata.getMetadata().get(EurekaMetadataKey.META_EUREKA_SERVICE_ID).toString(), ipAddress, null,
                 defaultPort, null, -1, "UP", "");
diff --git a/service-registration/static-list/src/main/java/io/smallrye/stork/serviceregistration/staticlist/StaticListServiceRegistrar.java b/service-registration/static-list/src/main/java/io/smallrye/stork/serviceregistration/staticlist/StaticListServiceRegistrar.java
index 03ea9cbd..53e5b942 100644
--- a/service-registration/static-list/src/main/java/io/smallrye/stork/serviceregistration/staticlist/StaticListServiceRegistrar.java
+++ b/service-registration/static-list/src/main/java/io/smallrye/stork/serviceregistration/staticlist/StaticListServiceRegistrar.java
@@ -24,6 +24,7 @@ public class StaticListServiceRegistrar implements ServiceRegistrar<Metadata.Def
     public Uni<Void> registerServiceInstance(String serviceName, Metadata<Metadata.DefaultMetadataKey> metadata,
             String ipAddress,
             int defaultPort) {
+        checkAddressNotNull(ipAddress);
         HostAndPort hostAndPortToAdd = StorkAddressUtils.parseToHostAndPort(ipAddress, defaultPort,
                 "service '" + serviceName + "'");
         String hostAndPortToAddString = StorkAddressUtils.parseToString(hostAndPortToAdd);


Test patch:
diff --git a/service-registration/consul/src/test/java/io/smallrye/stork/serviceregistration/consul/ConsulServiceRegistrationTest.java b/service-registration/consul/src/test/java/io/smallrye/stork/serviceregistration/consul/ConsulServiceRegistrationTest.java
index 4719c949..0ac686e9 100644
--- a/service-registration/consul/src/test/java/io/smallrye/stork/serviceregistration/consul/ConsulServiceRegistrationTest.java
+++ b/service-registration/consul/src/test/java/io/smallrye/stork/serviceregistration/consul/ConsulServiceRegistrationTest.java
@@ -3,6 +3,8 @@ package io.smallrye.stork.serviceregistration.consul;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.fail;
 import static org.awaitility.Awaitility.await;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import java.time.Duration;
 import java.util.Map;
@@ -86,4 +88,26 @@ public class ConsulServiceRegistrationTest {
         assertThat(subscriber.awaitItem().getItem()).isNotNull();
 
     }
+
+    @Test
+    void shouldFailIfNoIpAddressProvided() throws InterruptedException {
+        String serviceName = "my-service";
+        TestConfigProvider.addServiceConfig(serviceName, null, null, "consul",
+                null, Map.of("consul-host", "localhost", "consul-port", String.valueOf(consulPort), "refresh-period", "5"),
+                Map.of("consul-host", "localhost", "consul-port", String.valueOf(consulPort)));
+        Stork stork = StorkTestUtils.getNewStorkInstance();
+
+        ServiceRegistrar<ConsulMetadataKey> consulRegistrar = stork.getService(serviceName).getServiceRegistrar();
+
+        Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+            consulRegistrar.registerServiceInstance(serviceName, Metadata.of(ConsulMetadataKey.class)
+                    .with(ConsulMetadataKey.META_CONSUL_SERVICE_ID, serviceName), null, 8406);
+        });
+
+        String expectedMessage = "Parameter ipAddress should be provided.";
+        String actualMessage = exception.getMessage();
+
+        assertTrue(actualMessage.contains(expectedMessage));
+
+    }
 }
diff --git a/service-registration/eureka/src/test/java/io/smallrye/stork/serviceregistration/eureka/EurekaRegistrationTest.java b/service-registration/eureka/src/test/java/io/smallrye/stork/serviceregistration/eureka/EurekaRegistrationTest.java
index 43a86bb9..1e06425a 100644
--- a/service-registration/eureka/src/test/java/io/smallrye/stork/serviceregistration/eureka/EurekaRegistrationTest.java
+++ b/service-registration/eureka/src/test/java/io/smallrye/stork/serviceregistration/eureka/EurekaRegistrationTest.java
@@ -3,6 +3,8 @@ package io.smallrye.stork.serviceregistration.eureka;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.fail;
 import static org.awaitility.Awaitility.await;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import java.time.Duration;
 import java.util.Map;
@@ -104,4 +106,26 @@ public class EurekaRegistrationTest {
 
     }
 
+    @Test
+    void shouldFailIfNoIpAddressProvided() throws InterruptedException {
+        String serviceName = "my-service";
+        TestConfigProvider.addServiceConfig(serviceName, null, null, "eureka", null, null,
+                Map.of("eureka-host", eureka.getHost(), "eureka-port", String.valueOf(port)));
+
+        Stork stork = StorkTestUtils.getNewStorkInstance();
+
+        ServiceRegistrar<EurekaMetadataKey> eurekaServiceRegistrar = stork.getService(serviceName).getServiceRegistrar();
+
+        Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+            eurekaServiceRegistrar.registerServiceInstance(serviceName, Metadata.of(EurekaMetadataKey.class)
+                    .with(EurekaMetadataKey.META_EUREKA_SERVICE_ID, serviceName), null, 8406);
+        });
+
+        String expectedMessage = "Parameter ipAddress should be provided.";
+        String actualMessage = exception.getMessage();
+
+        assertTrue(actualMessage.contains(expectedMessage));
+
+    }
+
 }
diff --git a/service-registration/static-list/src/test/java/io/smallrye/stork/serviceregistration/staticlist/StaticServiceRegistrationTest.java b/service-registration/static-list/src/test/java/io/smallrye/stork/serviceregistration/staticlist/StaticServiceRegistrationTest.java
index 0788be8a..60d44d81 100644
--- a/service-registration/static-list/src/test/java/io/smallrye/stork/serviceregistration/staticlist/StaticServiceRegistrationTest.java
+++ b/service-registration/static-list/src/test/java/io/smallrye/stork/serviceregistration/staticlist/StaticServiceRegistrationTest.java
@@ -1,6 +1,8 @@
 package io.smallrye.stork.serviceregistration.staticlist;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.List;
 import java.util.Map;
@@ -89,4 +91,28 @@ public class StaticServiceRegistrationTest {
         assertThat(addresses).hasSize(1);
         assertThat(addresses.get(0)).isEqualTo("localhost:8081/hello");
     }
+
+    @Test
+    void shouldFailIfAddresseNull() {
+        TestConfigProvider.addServiceConfig("first-service", null, null, "static",
+                null, null, null);
+
+        stork = StorkTestUtils.getNewStorkInstance();
+
+        String serviceName = "first-service";
+
+        ServiceRegistrar<Metadata.DefaultMetadataKey> staticRegistrar = stork.getService(serviceName).getServiceRegistrar();
+
+        staticRegistrar.registerServiceInstance(serviceName, "http://localhost:8081/hello", 8080);
+
+        Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+            staticRegistrar.registerServiceInstance(serviceName, null, 8080);
+        });
+
+        String expectedMessage = "Parameter ipAddress should be provided.";
+        String actualMessage = exception.getMessage();
+
+        assertTrue(actualMessage.contains(expectedMessage));
+
+    }
 }