diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index 36e576aa..b07587c4 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -254,6 +254,17 @@ To disable it, set `spring.cloud.discovery.client.health-indicator.enabled=false To disable the description field of the `DiscoveryClientHealthIndicator`, set `spring.cloud.discovery.client.health-indicator.include-description=false`. Otherwise, it can bubble up as the `description` of the rolled up `HealthIndicator`. +==== Ordering `DiscoveryClient` instances +`DiscoveryClient` interface extends `Ordered`. This is useful when using multiple discovery + clients, as it allows you to define the order of the returned discovery clients, similar to +how you can order the beans loaded by a Spring application. By default, the order of any `DiscoveryClient` is set to +`0`. If you want to set a different order for your custom `DiscoveryClient` implementations, you just need to override +the `getOrder()` method so that it returns the value that is suitable for your setup. Apart from this, you can use +properties to set the order of the `DiscoveryClient` +implementations provided by Spring Cloud, among others `ConsulDiscoveryClient`, `EurekaDiscoveryClient` and +`ZookeeperDiscoveryClient`. In order to do it, you just need to set the +`spring.cloud.{clientIdentifier}.discovery.order` (or `eureka.client.order` for Eureka) property to the desired value. + === ServiceRegistry Commons now provides a `ServiceRegistry` interface that provides methods such as `register(Registration)` and `deregister(Registration)`, which let you provide custom registered services. diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java index dcee8e6a..6c0f00ab 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,22 +19,29 @@ package org.springframework.cloud.client.discovery; import java.util.List; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.core.Ordered; /** * Represents read operations commonly available to discovery services such as Netflix * Eureka or consul.io. + * * @author Spencer Gibb + * @author Olga Maciaszek-Sharma */ -public interface DiscoveryClient { +public interface DiscoveryClient extends Ordered { + + int DEFAULT_ORDER = 0; /** * A human-readable description of the implementation, used in HealthIndicator. + * * @return The description. */ String description(); /** * Gets all ServiceInstances associated with a particular serviceId. + * * @param serviceId The serviceId to query. * @return A List of ServiceInstance. */ @@ -45,4 +52,13 @@ public interface DiscoveryClient { */ List getServices(); + /** + * Default implementation for getting order of discovery clients. + * + * @return order + */ + @Override + default int getOrder() { + return DEFAULT_ORDER; + } } 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 index 4c011f0e..2df075d8 100644 --- 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 @@ -1,3 +1,19 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.cloud.client.discovery.composite; import java.util.ArrayList; @@ -7,18 +23,21 @@ import java.util.List; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; /** * A {@link DiscoveryClient} that is composed of other discovery clients and delegates * calls to each of them in order. * * @author Biju Kunjummen + * @author Olga Maciaszek-Sharma */ public class CompositeDiscoveryClient implements DiscoveryClient { private final List discoveryClients; public CompositeDiscoveryClient(List discoveryClients) { + AnnotationAwareOrderComparator.sort(discoveryClients); this.discoveryClients = discoveryClients; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClient.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClient.java index 17e94433..a6bae3dc 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClient.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryClient.java @@ -1,3 +1,19 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.cloud.client.discovery.simple; import java.util.ArrayList; @@ -12,6 +28,7 @@ import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperti * properties file as a source of service instances. * * @author Biju Kunjummen + * @author Olga Maciaszek-Sharma */ public class SimpleDiscoveryClient implements DiscoveryClient { @@ -42,4 +59,9 @@ public class SimpleDiscoveryClient implements DiscoveryClient { public List getServices() { return new ArrayList<>(this.simpleDiscoveryProperties.getInstances().keySet()); } + + @Override + public int getOrder() { + return this.simpleDiscoveryProperties.getOrder(); + } } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryProperties.java index 0c7f9e87..84590862 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/simple/SimpleDiscoveryProperties.java @@ -1,3 +1,19 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.cloud.client.discovery.simple; import java.net.URI; @@ -10,13 +26,18 @@ import javax.annotation.PostConstruct; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; /** * Properties to hold the details of a * {@link org.springframework.cloud.client.discovery.DiscoveryClient} service instances * for a given service. + * It also holds the user-configurable order that will be used to establish the + * precedence of this client in the list of clients + * used by {@link org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient}. * * @author Biju Kunjummen + * @author Olga Maciaszek-Sharma */ @ConfigurationProperties(prefix = "spring.cloud.discovery.client.simple") @@ -30,6 +51,8 @@ public class SimpleDiscoveryProperties { */ private SimpleServiceInstance local = new SimpleServiceInstance(); + private int order = DiscoveryClient.DEFAULT_ORDER; + public Map> getInstances() { return this.instances; } @@ -42,6 +65,14 @@ public class SimpleDiscoveryProperties { return this.local; } + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + } + @PostConstruct public void init() { for (String key : this.instances.keySet()) { diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientOrderTest.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientOrderTest.java new file mode 100644 index 00000000..f9f925e3 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientOrderTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.client.discovery.composite; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientTestsConfig.CUSTOM_DISCOVERY_CLIENT; +import static org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientTestsConfig.CUSTOM_SERVICE_ID; +import static org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientTestsConfig.DEFAULT_ORDER_DISCOVERY_CLIENT; +import static org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientTestsConfig.FOURTH_DISCOVERY_CLIENT; + +/** + * Tests for the support of ordered {@link DiscoveryClient} instances in {@link CompositeDiscoveryClient} + * + * @author Olga Maciaszek-Sharma + */ +@RunWith(SpringRunner.class) +@SpringBootTest(properties = "spring.cloud.discovery.client.simple.order:2", classes = { + CompositeDiscoveryClientTestsConfig.class}) +public class CompositeDiscoveryClientOrderTest { + + @Autowired + DiscoveryClient discoveryClient; + + @Test + public void shouldGetOrderedDiscoveryClients() { + // when: + List discoveryClients = ((CompositeDiscoveryClient) this.discoveryClient) + .getDiscoveryClients(); + + // then: + assertThat(discoveryClients.get(0).description()) + .isEqualTo(CUSTOM_DISCOVERY_CLIENT); + assertThat(discoveryClients.get(1).description()) + .isEqualTo(DEFAULT_ORDER_DISCOVERY_CLIENT); + assertThat(discoveryClients.get(2).description()) + .isEqualTo("Simple Discovery Client"); + assertThat(discoveryClients.get(3).description()) + .isEqualTo(FOURTH_DISCOVERY_CLIENT); + } + + @Test + public void shouldOnlyReturnServiceInstancesForTheHighestPrecedenceDiscoveryClient() { + // when: + List serviceInstances = this.discoveryClient + .getInstances(CUSTOM_SERVICE_ID); + + // then: + assertThat(serviceInstances).hasSize(1); + assertThat(serviceInstances.get(0).getPort()).isEqualTo(123); + } +} 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 index 841ab3cd..f0eca3bf 100644 --- 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 @@ -1,28 +1,38 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.cloud.client.discovery.composite; import java.net.URI; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; 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 static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientTestsConfig.CUSTOM_SERVICE_ID; /** * Tests for behavior of Composite Discovery Client - * + * * @author Biju Kunjummen */ @@ -32,7 +42,8 @@ import static org.assertj.core.api.Assertions.assertThat; "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" }) + "spring.cloud.discovery.client.simple.instances.service2[1].uri=https://s2-2:443",}, classes = { + CompositeDiscoveryClientTestsConfig.class}) public class CompositeDiscoveryClientTests { @Autowired @@ -50,56 +61,25 @@ public class CompositeDiscoveryClientTests { 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); + assertThat(this.discoveryClient.getInstances(CUSTOM_SERVICE_ID)).hasSize(1); + assertThat(this.discoveryClient.getInstances(CUSTOM_SERVICE_ID)).hasSize(1); } @Test public void getInstancesByUnknownServiceIdShouldReturnAnEmptyList() { assertThat(this.discoveryClient.getInstances("unknown")).hasSize(0); } - - @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 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/composite/CompositeDiscoveryClientTestsConfig.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientTestsConfig.java new file mode 100644 index 00000000..2a846eaf --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientTestsConfig.java @@ -0,0 +1,88 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.client.discovery.composite; + +import java.util.Collections; +import java.util.List; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +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 static java.util.Collections.singletonList; + +/** + * Test configuration for {@link CompositeDiscoveryClient} tests. + * + * @author Olga Maciaszek-Sharma + */ +@Configuration +@EnableAutoConfiguration +public class CompositeDiscoveryClientTestsConfig { + + static final String DEFAULT_ORDER_DISCOVERY_CLIENT = "Default order discovery client"; + static final String CUSTOM_DISCOVERY_CLIENT = "A custom discovery client"; + static final String FOURTH_DISCOVERY_CLIENT = "Fourth discovery client"; + static final String CUSTOM_SERVICE_ID = "custom"; + + @Bean + public DiscoveryClient customDiscoveryClient() { + return aDiscoveryClient(-1, CUSTOM_DISCOVERY_CLIENT); + } + + @Bean + public DiscoveryClient thirdOrderCustomDiscoveryClient() { + return aDiscoveryClient(3, FOURTH_DISCOVERY_CLIENT); + } + + @Bean + public DiscoveryClient defaultOrderDiscoveryClient() { + return aDiscoveryClient(null, DEFAULT_ORDER_DISCOVERY_CLIENT); + } + + private DiscoveryClient aDiscoveryClient(Integer order, String description) { + return new DiscoveryClient() { + @Override + public String description() { + return description; + } + + @Override + public List getInstances(String serviceId) { + if (serviceId.equals(CUSTOM_SERVICE_ID)) { + ServiceInstance s1 = new DefaultServiceInstance(CUSTOM_SERVICE_ID, + "host", 123, false); + return singletonList(s1); + } + return Collections.emptyList(); + } + + @Override + public List getServices() { + return singletonList(CUSTOM_SERVICE_ID); + } + + @Override + public int getOrder() { + return order != null ? order : DiscoveryClient.super.getOrder(); + } + }; + } +}