Spring Cloud Config Server — Work around for Auto Refresh
In the previous article we saw how to achieve auto refreshable configuration but what if you cannot afford to set up a Spring Cloud bus and a message broker due to infrastructure limitations or you may want control the refresh instead of a webhook.
Let’s discuss the use cases first.
- A more controlled environment that restricts auto refresh for many reasons and the team wants to manually control refresh but still can afford to set up cloud bus and message broker.
- Infrastructure doesn’t support message broker, spring cloud bus and git web hook.
Use case 1:
This is easy to solve without much code changes. Config server is ready to publish changes in case of change event and all the clients are listening to Kafka broker to pick up any changes. All you have to do is make a POST call thru postman or any other tool to hit the /actuator/bus-refresh endpoint on the config server. Config server will pick up changes from git and publish a message to kafka topic that clients are listening to.
Use Case 2:
In this case, the application does not have any of the auto refreshable components like cloud bus and message broker.
The work around is to introduce a new endpoint in config server that you can hit from browser or postman. The controller on the endpoint is responsible to perform a refresh on all clients of config server replacing what cloud bus and kafka could do you for you. It can be achieved with the following code.
The below code does the following step by step.
- It uses eurekaClient to get all registered applications in Eureka.
- Removes “config-server” from the list because config server doesn’t have to refresh itself.
- Collects all instances registered under the applications. You can have one or more instances for an application.
- iterate thru all instances and invoke POST http to invoke “/actuator/refresh” endpoint on all clients.
This solution could fit even large applications with more instances. You can also use parallelism to refresh more clients concurrently.
@RestController
@RequestMapping("/")
public class RefreshController {
@Autowired
private EurekaClient eurekaClient;
private Map<String, String> resultMap;
private RestTemplate restTemplate;
@GetMapping("/refresh")
public Map<String, String> refresh() throws Exception
{
resultMap = new HashMap<>();
restTemplate = new RestTemplate();
if(Objects.isNull(eurekaClient) || Iterate.isEmpty(eurekaClient.getApplications().getRegisteredApplications()))
{
resultMap.put("Failure", "No Eureka Clients available to refresh");
return resultMap;
}
Lists.adapt(eurekaClient.getApplications().getRegisteredApplications())
.reject(application -> application.getName().equalsIgnoreCase("config-server"))
.flatCollect(Application::getInstances) .forEach(instanceInfo -> this.invokeClient(instanceInfo));
return resultMap;
}
private void invokeClient(InstanceInfo instanceInfo)
{
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(null, headers);
ResponseEntity<String> response = restTemplate.postForEntity("http://"+instanceInfo.getHostName()+":"+instanceInfo.getPort() + "/actuator/refresh", entity, String.class);
resultMap.put(instanceInfo.getHomePageUrl(), response.getStatusCode().getReasonPhrase());
}
}