diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClient.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClient.java new file mode 100644 index 0000000000..c3735f198e --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClient.java @@ -0,0 +1,73 @@ +package org.springframework.cloud.client.discovery.composite; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; + +/** + * A {@link DiscoveryClient} composed of other Discovery Client's and will delegate the + * calls to each of them in order + * + * @author Biju Kunjummen + */ +public class CompositeDiscoveryClient implements DiscoveryClient { + + private final List discoveryClients; + + public CompositeDiscoveryClient(List discoveryClients) { + this.discoveryClients = discoveryClients; + } + + @Override + public String description() { + return "Composite Discovery Client"; + } + + @Override + public ServiceInstance getLocalServiceInstance() { + if (this.discoveryClients != null) { + for (DiscoveryClient discoveryClient : discoveryClients) { + ServiceInstance serviceInstance = discoveryClient.getLocalServiceInstance(); + if (serviceInstance != null) { + return serviceInstance; + } + } + } + return null; + } + + @Override + public List getInstances(String serviceId) { + if (this.discoveryClients != null) { + for (DiscoveryClient discoveryClient : discoveryClients) { + List instances = discoveryClient.getInstances(serviceId); + if (instances != null && instances.size() > 0) { + return instances; + } + } + } + return Collections.emptyList(); + } + + @Override + public List getServices() { + LinkedHashSet services = new LinkedHashSet<>(); + if (this.discoveryClients != null) { + for (DiscoveryClient discoveryClient : discoveryClients) { + List serviceForClient = discoveryClient.getServices(); + if (serviceForClient != null) { + services.addAll(serviceForClient); + } + } + } + return new ArrayList<>(services); + } + + List getDiscoveryClients() { + return discoveryClients; + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientAutoConfiguration.java new file mode 100644 index 0000000000..b71d28da47 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientAutoConfiguration.java @@ -0,0 +1,28 @@ +package org.springframework.cloud.client.discovery.composite; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import java.util.List; + +/** + * Auto-configuration for Composite Discovery Client. + * + * @author Biju Kunjummen + */ + +@Configuration +@AutoConfigureBefore(SimpleDiscoveryClientAutoConfiguration.class) +public class CompositeDiscoveryClientAutoConfiguration { + + @Bean + @Primary + public CompositeDiscoveryClient compositeDiscoveryClient(List discoveryClients) { + return new CompositeDiscoveryClient(discoveryClients); + } + +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClientAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClientAutoConfiguration.java index 797f632ba3..ef7b1ad78a 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClientAutoConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClientAutoConfiguration.java @@ -1,23 +1,23 @@ package org.springframework.cloud.client.discovery.simple; -import java.net.URI; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.util.ClassUtils; +import java.net.URI; + /** * Spring Boot Auto-Configuration for Simple Properties based Discovery Client * @@ -25,8 +25,6 @@ */ @Configuration -@ConditionalOnMissingBean(DiscoveryClient.class) -@EnableConfigurationProperties @AutoConfigureBefore(NoopDiscoveryClientAutoConfiguration.class) public class SimpleDiscoveryClientAutoConfiguration { @@ -54,9 +52,9 @@ public SimpleDiscoveryProperties simpleDiscoveryProperties() { } @Bean - public DiscoveryClient simpleDiscoveryClient( - SimpleDiscoveryProperties simpleDiscoveryProperties) { - return new SimpleDiscoveryClient(simpleDiscoveryProperties); + @Order(Ordered.LOWEST_PRECEDENCE) + public DiscoveryClient simpleDiscoveryClient() { + return new SimpleDiscoveryClient(simpleDiscoveryProperties()); } private int findPort() { diff --git a/spring-cloud-commons/src/main/resources/META-INF/spring.factories b/spring-cloud-commons/src/main/resources/META-INF/spring.factories index 4e17ff9ac1..874eb1fc89 100644 --- a/spring-cloud-commons/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-commons/src/main/resources/META-INF/spring.factories @@ -7,6 +7,7 @@ org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\ org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\ org.springframework.cloud.commons.util.UtilAutoConfiguration,\ +org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,\ org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientAutoConfigurationTests.java new file mode 100644 index 0000000000..f7572297d7 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientAutoConfigurationTests.java @@ -0,0 +1,79 @@ +package org.springframework.cloud.client.discovery.composite; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Composite Discovery Client should be the one found by default. + * + * @author Biju Kunjummen + */ + +@RunWith(SpringRunner.class) +@SpringBootTest +public class CompositeDiscoveryClientAutoConfigurationTests { + + @Autowired + private DiscoveryClient discoveryClient; + + @Test + public void compositeDiscoveryClientShouldBeTheDefault() { + assertThat(discoveryClient).isInstanceOf(CompositeDiscoveryClient.class); + CompositeDiscoveryClient compositeDiscoveryClient = (CompositeDiscoveryClient) discoveryClient; + assertThat(compositeDiscoveryClient.getDiscoveryClients()).hasSize(2); + assertThat(compositeDiscoveryClient.getDiscoveryClients().get(0).description()) + .isEqualTo("A custom discovery client"); + } + + @Test + public void simpleDiscoveryClientShouldBeHaveTheLowestPrecedence() { + CompositeDiscoveryClient compositeDiscoveryClient = (CompositeDiscoveryClient) discoveryClient; + assertThat(compositeDiscoveryClient.getDiscoveryClients().get(0).description()) + .isEqualTo("A custom discovery client"); + assertThat(compositeDiscoveryClient.getDiscoveryClients().get(1)) + .isInstanceOf(SimpleDiscoveryClient.class); + } + + @EnableAutoConfiguration + @Configuration + public static class Config { + + @Bean + public DiscoveryClient customDiscoveryClient1() { + return new DiscoveryClient() { + @Override + public String description() { + return "A custom discovery client"; + } + + @Override + public ServiceInstance getLocalServiceInstance() { + return null; + } + + @Override + public List getInstances(String serviceId) { + return null; + } + + @Override + public List getServices() { + return null; + } + }; + } + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientTests.java new file mode 100644 index 0000000000..bb5a9f2481 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientTests.java @@ -0,0 +1,116 @@ +package org.springframework.cloud.client.discovery.composite; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.test.context.junit4.SpringRunner; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for behavior of Composite Discovery Client + * + * @author Biju Kunjummen + */ + +@RunWith(SpringRunner.class) +@SpringBootTest(properties = { + "spring.application.name=service0", + "spring.cloud.discovery.client.simple.instances.service1[0].uri=http://s1-1:8080", + "spring.cloud.discovery.client.simple.instances.service1[1].uri=https://s1-2:8443", + "spring.cloud.discovery.client.simple.instances.service2[0].uri=https://s2-1:8080", + "spring.cloud.discovery.client.simple.instances.service2[1].uri=https://s2-2:443" }) +public class CompositeDiscoveryClientTests { + + @Autowired + private DiscoveryClient discoveryClient; + + @Test + public void getInstancesByServiceIdShouldDelegateCall() { + assertThat(this.discoveryClient).isInstanceOf(CompositeDiscoveryClient.class); + + assertThat(this.discoveryClient.getInstances("service1")).hasSize(2); + + ServiceInstance s1 = this.discoveryClient.getInstances("service1").get(0); + assertThat(s1.getHost()).isEqualTo("s1-1"); + assertThat(s1.getPort()).isEqualTo(8080); + assertThat(s1.getUri()).isEqualTo(URI.create("http://s1-1:8080")); + assertThat(s1.isSecure()).isEqualTo(false); + } + + @Test + public void getServicesShouldAggregateAllServiceNames() { + assertThat(this.discoveryClient.getServices()).containsOnlyOnce("service1", "service2", "custom"); + } + + @Test + public void getDescriptionShouldBeComposite() { + assertThat(this.discoveryClient.description()).isEqualTo("Composite Discovery Client"); + } + + @Test + public void getInstancesShouldRespectOrder() { + assertThat(this.discoveryClient.getInstances("custom")).hasSize(1); + assertThat(this.discoveryClient.getInstances("custom")).hasSize(1); + } + + @Test + public void getInstancesByUnknownServiceIdShouldReturnAnEmptyList() { + assertThat(this.discoveryClient.getInstances("unknown")).hasSize(0); + } + + + @Test + public void localServiceInstanceShouldReturnTheFirstMatch() { + assertThat(this.discoveryClient.getLocalServiceInstance().getServiceId()).isEqualTo("service0"); + } + + @EnableAutoConfiguration + @Configuration + public static class Config { + + @Bean + @Order(1) + public DiscoveryClient customDiscoveryClient() { + return new DiscoveryClient() { + @Override + public String description() { + return "A custom discovery client"; + } + + @Override + public ServiceInstance getLocalServiceInstance() { + return null; + } + + @Override + public List getInstances(String serviceId) { + if (serviceId.equals("custom")) { + ServiceInstance s1 = new DefaultServiceInstance("custom", "host", + 123, false); + return Arrays.asList(s1); + } + return Collections.emptyList(); + } + + @Override + public List getServices() { + return Arrays.asList("custom"); + } + }; + } + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/DiscoveryClientAutoConfigurationDefaultTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/DiscoveryClientAutoConfigurationDefaultTests.java index b62e7aca04..bb6732ec9f 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/DiscoveryClientAutoConfigurationDefaultTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/DiscoveryClientAutoConfigurationDefaultTests.java @@ -3,36 +3,34 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; /** - * DiscoveryClient implementation defaults to {@link SimpleDiscoveryClient} + * DiscoveryClient implementation defaults to {@link CompositeDiscoveryClient} + * * @author Biju Kunjummen */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = DiscoveryClientAutoConfigurationDefaultTests.App.class) +@SpringBootTest(classes = DiscoveryClientAutoConfigurationDefaultTests.Config.class) public class DiscoveryClientAutoConfigurationDefaultTests { @Autowired - DiscoveryClient discoveryClient; + private DiscoveryClient discoveryClient; @Test public void simpleDiscoveryClientShouldBeTheDefault() { - assertThat(discoveryClient).isInstanceOf(SimpleDiscoveryClient.class); + assertThat(discoveryClient).isInstanceOf(CompositeDiscoveryClient.class); } @EnableAutoConfiguration @Configuration - public static class App { - public static void main(String[] args) { - SpringApplication.run(App.class, args); - } + public static class Config { } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClientPropertiesMappingTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClientPropertiesMappingTests.java index b1095976e8..56435d5aaf 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClientPropertiesMappingTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClientPropertiesMappingTests.java @@ -8,7 +8,6 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; @@ -32,7 +31,7 @@ public class SimpleDiscoveryClientPropertiesMappingTests { private SimpleDiscoveryProperties props; @Autowired - private DiscoveryClient discoveryClient; + private SimpleDiscoveryClient discoveryClient; @Test public void propsShouldGetCleanlyMapped() { diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/UserDefinedDiscoveryClientOverridesDefaultsTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/UserDefinedDiscoveryClientOverridesDefaultsTests.java deleted file mode 100644 index 04672f17ad..0000000000 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/simple/UserDefinedDiscoveryClientOverridesDefaultsTests.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.springframework.cloud.client.discovery.simple; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Biju Kunjummen - */ -@RunWith(SpringRunner.class) -@SpringBootTest(classes = UserDefinedDiscoveryClientOverridesDefaultsTests.App.class) -public class UserDefinedDiscoveryClientOverridesDefaultsTests { - - @Autowired - DiscoveryClient discoveryClient; - - @Test - public void testDiscoveryClientIsNotNoop() { - assertThat(discoveryClient).isNotInstanceOf(SimpleDiscoveryClient.class); - - assertThat(discoveryClient.description()) - .isEqualTo("user defined discovery client"); - } - - @EnableAutoConfiguration - @Configuration - public static class App { - - @Bean - public DiscoveryClient discoveryClient() { - return new DiscoveryClient() { - @Override - public String description() { - return "user defined discovery client"; - } - - @Override - public ServiceInstance getLocalServiceInstance() { - return null; - } - - @Override - public List getInstances(String serviceId) { - return null; - } - - @Override - public List getServices() { - return null; - } - }; - } - - public static void main(String[] args) { - SpringApplication.run(App.class, args); - } - } -}