Spring Cloud Config Server and Good Practice of Refresh Scope Usage

Yunus Kılıç
Analytics Vidhya
Published in
3 min readDec 9, 2019

--

Photo by Oskar Yildiz on Unsplash

Twelve-factor app development methodology strongly suggests that “strict separation of config from code.”[1]

Spring Cloud offers a solution to that problem with Spring Cloud Config Server. Spring Cloud Config Server defines itself as below.

“Spring Cloud Config provides server-side and client-side support for externalized configuration in a distributed system. With the Config Server, you have a central place to manage external properties for applications across all environments.”[2]

Managing your configuration in a central place is a super easy and also very efficient feature. Lacking centralized configuration causes making mistakes very often.

Another good feature is after changing your configuration, you can easily get these from your application without a restart. There are lots of tutorials that demonstrate how to create a config server and refresh a property at runtime. I believe that WHY-ARTICLES is more important than HOW-ARTICLES. So I do not state how to create config server etc. I want to describe why do we use Configuration properties while dealing with runtime property refresh.

If you search on the net, there are tons of resources that just show refresh scope at controller like below.

@EnableAutoConfiguration
@ComponentScan
@RestController
@RefreshScope // important!
public class SpringApp {
@Value("${bar:World!}")
String bar;

@RequestMapping("/")
String hello() {
return "Hello " + bar + "!";
}

public static void main(String[] args) {
SpringApplication.run(SpringApp.class, args);
}
}

But choosing refresh scope at the right place is not easy as shown. Let me think below scenario

test:
value: MyTestValue

Controller:

@RestController("/test")
public class TestController {

private final TestService testService;

public TestController(TestService testService) {
this.testService = testService;
}

@GetMapping("/v1")
public String test() {
return testService.getValueWithDelay();
}

@GetMapping("/v2")
public String test2() {
return testService.getValue();
}
}

Service:

@RefreshScope
@Service
public class TestService {

@Value("${test.value}")
private String value;

public String getValueWithDelay() {
try {
Thread.sleep(30000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return value;
}

public String getValue() {
return value;
}
}

Call http://127.0.0.1:8080/test/v1. There is a 30-sec time delay. It returns after 30 seconds.

Call http://127.0.0.1:8080/test/v2. It returns after 6 milliseconds.

It seems everything is perfect. Let’s change the scenario.

V2 endpoint returns after 10-20 seconds or around. (You need to finish these operations before v1 execution finished)

We used refresh scope at the service class. If value which used inside service class, then other methods will wait until this value replaced by context. In our modern software development environment, even milliseconds are important. V1 and V2 are different operations, but they block each other because of refresh scope usage. Worse than this, tracking which value will affect to which service, becomes very problematic.

Configuration Properties concept becomes involved at that point. If you use refresh scope inside configuration properties rather than service, then blocking different services each other can be resolved. Because context only refreshes configuration properties bean. And the refreshing property is very fast. That’s why you should use configuration property and use refresh scope inside it.

@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "test")
public class TestConfiguration {

private String value;

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}

Service:

@Service
public class TestService {
private final TestConfiguration testConfiguration;

public TestService(TestConfiguration testConfiguration) {
this.testConfiguration = testConfiguration;
}

public String getValueWithDelay() {
try {
Thread.sleep(30000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return testConfiguration.getValue();
}

public String getValue() {
return testConfiguration.getValue();
}
}

V2 endpoint returns after 10 milliseconds or around. There are no blocks.

Problem solved.

PS: Special thanks to Ercan Sormaz.

References:

  1. https://12factor.net/config
  2. https://cloud.spring.io/spring-cloud-config/reference/html/

--

--

Yunus Kılıç
Analytics Vidhya

I have 10 years of experience in high-quality software application development, implementation, and integration.