Spring Cloud: How To Deal With Microservice Configuration (Part 1)
In this article, we cover how to use a Spring Cloud Configuration module to implement a minimal microservice scenario based on a remote configuration.
Join the DZone community and get the full member experience.
Join For FreeConfiguring a software system in a monolithic approach does not pose particular problems. To make the configuration properties available to the system, we can store them in a file inside an application folder, in some place in the filesystem, or as OS environment variables. Microservice configuration is a more complex subject. We have to deal with a number, which can be huge, of totally independent services, each with its own configuration. We could even face a scenario in which several instances of the same service need different configuration values.
In such a situation, a way to centralize and simplify configuration management would be of great importance. Spring Cloud has its own module to solve these problems, named Spring Cloud Config. This module provides an implementation of a server that exposes an API to retrieve the configuration information, usually stored in some remote repository like Git, and, at the same time, it gives us the means to implement the client side meant to consume the services of that API.
In the first part of this article, we will discuss the basic features of this Spring Cloud module and storing the configuration in the configuration server classpath. In part two, we will show how to use other, more effective, repository options, like Git, and how to refresh the configuration without restarting the services. Then, in later posts, we will show how the centralized configuration can be coupled with service discovery features to set a solid base for the whole microservice system.
Microservice Configuration—Spring Cloud Config Server Side
The first component we need in a distributed configuration scenario is a server meant to provide the configuration information for the services. To implement such a server component by Spring Cloud Config, we have to use the right Spring Boot “starter” dependency, like in the following configuration fragment:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
Then, we have to annotate the Spring Boot main class with the @EnableConfigServer
annotation:
@SpringBootApplication
@EnableConfigServer
public class AppMain {
public static void main(String[] args) {
new SpringApplicationBuilder(Config.class).run(args);
}
}
The Spring Cloud Config server, according to the auto-configuration features of Spring Boot, would run on the default 8080 port as all Spring Boot applications. If we want to customize it, we can do it by the application.properties or application.yml
file:
server:
port: ${PORT:8888}
spring:
application:
name: config-server
If we run the application with the above configuration, it will use the 8888 port as default. We can override the default by launching the application with a different port, by the PORT
placeholder:
java -jar -DPORT=8889 sample-server-1.0-SNAPSHOT.jar
In any case, if we launch the application with the spring.config.name=configserver
argument instead, the default port will be 8888. This is due to a configserver.yml
default file embedded in the spring-cloud-config-server
library. As a matter of fact, it would be very convenient to launch the server config application on the 8888 port, either by explicitly configuring it by the server.port
parameter, like in the example above, or passing spring.config.name=configserver
in the startup Java command because 8888 happens to be the default port used by the client side.
Important Note: The spring.config.name=configserver
option only works if passed in the startup command and seems to be ignored, for some reason, if set in the configuration file. We can see below an example of how to start the config server with a Java command:
java -jar -Dspring.config.name=configserver spring-cloud-config-native-server-1.0-SNAPSHOT.jar
By default, the Spring Cloud Config server uses Git as a remote repository to store the configuration data. To simplify the discussion, we will focus on a more basic approach based on files stored on the application classpath. We will describe this option in the next section. It must be stressed that, in a real scenario, this would be far from ideal, and Git would be surely a better choice.
Enforcing Basic Authentication on the Server Side
We can provide the server with a basic security layer in the form of an authentication mechanism based on user and password. To do that, we must first add the following security starter to the POM
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
And then add the following piece of configuration in the application.yml
file:
security:
user:
name: myusername
password: mypassword
With the above, the client side should be configured accordingly to be able to connect to the server, as we will see in the section related to the client side. We will discuss more advanced securing mechanisms in later articles.
Spring Cloud Config Backend Storing Options
The Spring Cloud Config server can store the configuration data in several ways:
- By a remote Git system, which is the default.
- Other Version Control Systems (VCS) like SVN.
- VAULT: is a tool by HashiCorp specialized in storing passwords, certificates, or other entities as secrets.
- By storing it in some place in the file system or the classpath.
Below, we will describe the filesystem/classpath option. Spring Cloud Config has a profile named native that covers this scenario. In order to run the config server with a filesystem/classpath backend storage, we have to start it with the spring.profiles.active=native
option.
In the native scenario, the config server will search by default in the following places:
classpath:/
classpath:/config
file:./
file:./ config
So, we can simply store the configuration files inside the application jar file. If we want to use an external filesystem directory instead or customize the above classpath options, we can set the spring.cloud.config.server.native.searchLocations
property accordingly.
Config Server API
The Config Server can expose the configuration properties of a specific application by an HTTP API with the following endpoints:
/{ application}/{ profile}[/{ label}]
: this returns the configuration data as JSON with the specific application, profile, and an optional label parameter./{ label}/{ application}-{ profile}.yml
: this returns the configuration data in YAML format, with the specific application, profile, and an optional label parameter./{ label}/{ application}-{ profile}.properties
: this returns the configuration data as raw text, with the specific application, profile, and an optional label parameter.
The application part represents the name of the application configured by the spring.application.name
property and the profile part represents the active profile. A profile is a feature to segregate a set of configurations related to specific environments, such as development, test, and production. The label part is optional and is used when using Git as a backend repository to identify a specific branch.
Microservice Configuration—Spring Cloud Config Client Side
If we want our services to obtain their own configuration from the server, we must provide them with a dependency named spring-cloud-starter-config
:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
Clearly, the configuration must be obtained as the first step during its startup. To deal with this requirement, Spring Cloud introduces a bootstrap context. The bootstrap context can be seen as the parent of the application context. It serves the purpose of loading configuration retrieved data from some external source and making it available to the application context.
In earlier versions of Spring Cloud, we could provide the configuration properties for the bootstrap context by a bootstrap.yml
file. This is deprecated in the new versions. Now, we simply have to provide a config.import property=optional:configserver
: property in the standard application.yml
:
config:
import: "optional:configserver:"
With the optional:configserver
value, the config client service will use the default http://localhost:8888
address to contact the config server. If we exclude the optional part, an error will be raised during startup if the server is unreachable.
If we want to set a specific address and port, we can add the address part to the value like this:
config:
import: "optional:configserver:http://myhost:myport"
Configuring Security on the Client Side
If we have secured the server with basic authentication, we must provide the necessary configuration to the client. Adding the following piece of configuration to the application.yml
will be enough:
security:
user:
name: myusername
password: mypassword
Putting the Pieces Together in a Simple Demo
Using the notions described above, we can realize a simple demo with a configuration server and a single client service, as shown in the picture below:
Server Side Implementation
To implement the server side, we create a Spring Boot application with the required Spring Cloud release train and Spring Cloud Config starter:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
Then, we write the application.yml
file, setting the port to the conventional 8888 value, the active profile as native, and, finally, the application name:
server:
port: ${PORT:8888}
spring:
profiles:
active: native
application:
name: config-server
Since we have set the spring.profiles.active
equal to the native value, this config server application storage will be based on the filesystem/classpath
. In our example, we choose to store the configuration file of the client service in the classpath in a config subdirectory of the “/resources” folder. We name the client service application file name as client-service.yml
, and we fill it with the following content:
server:
port: ${PORT:8081}
myproperty: value
myproperties:
properties:
- value1
- value2
The myproperty
and myproperties
parts will be used to test this minimal demo: we will expose them by some REST service on the client, and if all works as expected, the above values will be returned.
Client Side Implementation
We configure the client application with the same release train of the server. As dependencies, we have a spring-cloud-starter-config
starter and also a spring-boot-starter-web
because we want our application to expose some HTTP REST services:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
The application.yml
properties will be consumed by a specific component class by the @Value
and @ConfigurationProperties
annotations:
@Component
@ConfigurationProperties(prefix = "myproperties")
public class DemoClient {
private List<String> properties = new ArrayList<String>();
public List<String> getProperties() {
return properties;
}
@Value("${myproperty}")
private String myproperty;
public String getMyproperty() {
return myproperty;
}
}
Then, a controller class will implement two REST services, /getProperties
and /getProperty
, returning the above class properties:
@RestController
public class ConfigClientController {
private static final Logger LOG = LoggerFactory.getLogger(ConfigClientController.class);
@Autowired
private DemoClient demoClient;
@GetMapping("/getProperties")
public List<String> getProperties() {
LOG.info("Properties: " + demoClient.getProperties().toString());
return demoClient.getProperties();
}
@GetMapping("/getProperty")
public String getProperty() {
LOG.info("Property: " + demoClient.getMyproperty().toString());
return demoClient.getMyproperty();
}
}
Compiling and Running the Config Server and Client Service
After compiling the two applications by Maven, we can take the resulting jars and run first the server part and then the client from the command line:
java -jar spring-cloud-config-native-server-1.0-SNAPSHOT.jar
...
java -jar spring-cloud-config-native-client-1.0-SNAPSHOT.jar
We can test the correct behavior by executing a call with the following address in the browser:
http://localhost:8081/getProperties
If all works as expected, we will have the following values printed on the screen:
[ "value1", "value2" ]
The Maven projects related to the demo described above are available on GitHub at the following addresses:
Conclusion
In this article, we have covered the basic notions required to configure a microservice system based on remote configuration. We have used the native approach here, using the classpath as a storage repository. In part two, we will show how to use a remote Git repository and how to refresh the configuration at runtime without restarting the services.
Published at DZone with permission of Mario Casari. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments