Merge pull request #412 from OlgaMaciaszek/gh-304-add-discovery-clients-order-support

Gh 304 add discovery clients order support
This commit is contained in:
Olga Maciaszek-Sharma
2018-09-10 17:58:53 +02:00
committed by GitHub
8 changed files with 291 additions and 48 deletions

View File

@@ -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.

View File

@@ -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<String> getServices();
/**
* Default implementation for getting order of discovery clients.
*
* @return order
*/
@Override
default int getOrder() {
return DEFAULT_ORDER;
}
}

View File

@@ -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<DiscoveryClient> discoveryClients;
public CompositeDiscoveryClient(List<DiscoveryClient> discoveryClients) {
AnnotationAwareOrderComparator.sort(discoveryClients);
this.discoveryClients = discoveryClients;
}

View File

@@ -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<String> getServices() {
return new ArrayList<>(this.simpleDiscoveryProperties.getInstances().keySet());
}
@Override
public int getOrder() {
return this.simpleDiscoveryProperties.getOrder();
}
}

View File

@@ -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<String, List<SimpleServiceInstance>> 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()) {

View File

@@ -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<DiscoveryClient> 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<ServiceInstance> serviceInstances = this.discoveryClient
.getInstances(CUSTOM_SERVICE_ID);
// then:
assertThat(serviceInstances).hasSize(1);
assertThat(serviceInstances.get(0).getPort()).isEqualTo(123);
}
}

View File

@@ -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<ServiceInstance> 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<String> getServices() {
return Arrays.asList("custom");
}
};
}
}
}

View File

@@ -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<ServiceInstance> 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<String> getServices() {
return singletonList(CUSTOM_SERVICE_ID);
}
@Override
public int getOrder() {
return order != null ? order : DiscoveryClient.super.getOrder();
}
};
}
}