Dynamic configuration management with Spring Cloud Config

Original post is http://blog.ik.am/#/entries/290 (Japanese)

Please correct my poor English ;)

## What is Spring Cloud Config?

[Spring Cloud Config](http://cloud.spring.io/spring-cloud-config/) project provides a mechanism of configuration in a distributed system.
My most interested topic in [SpringOne 2gx 2014](http://springone2gx.com/).

Spring Cloud Config consists of Client and Server.

Sever manages the external configuration such as Git and properties file, provides the centralized configuration to all of Client.
Client holds the configuration from Server using the abstraction mechanism Spring Framework originally has such as `Environment`and`PropertySource`.
It also enables reloading the configuration.

The minimum system using them is as follows:

<a href="http://blog.ik.am/api/v1/files/593b022c-594e-4502-9144-2c4789118ada/config-architecture.png"><img src="http://blog.ik.am/api/v1/files/593b022c-594e-4502-9144-2c4789118ada/config-architecture.png" width="80%"></a>

"Normal application" requires the configuration is a Client. Even though there are many Clients, Server can mange them centrally.

## How to use
**Note that the following contents are for 1.0.0.M1 version, these can be changed drastically in the future.**

### Setup Config Server
Building Config Server is very simple. Add dependency on `org.springframework.cloud:spring-cloud-config-server` and just put `EnableConfigServer` to the entry point class.

Setting example of pom.xml is described later.

Entry point  class is like as follows:

``` java
package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.context.annotation.ComponentScan;

@EnableAutoConfiguration
@EnableConfigServer // important!!
@ComponentScan
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}
```
Describe the configuration where to fetch the configuration in ` bootstrap.yml` just under the classpath (Note that it's not `application.yml`). 

As a default, [spring-cloud-samples/config-repo](https://github.com/spring-cloud-samples/config-repo) is configured to fetch. In this case, because I want to change properties,  I'm using my [making/config-repo](https://github.com/making/config-repo) forked from it and set the url of  Git share repository url in `spring.platform.config.server.uri` property like:

```
spring.platform.config.server.uri: https://github.com/making/config-repo
```

Finally, the setting of the pom.xml. Since the official version has not been released yet, it has a little redundant configuration, it would be more simple after 1.0.0.RELEASE. The important part is the dependency on `org.springframework.cloud:spring-cloud-config-server`.

``` xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>demo</groupId>
    <artifactId>configserver</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.1.5.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starters</artifactId>
                <version>1.0.0.M1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.App</start-class>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>http://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>http://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework</groupId>
                        <artifactId>springloaded</artifactId>
                        <version>${spring-loaded.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>
```

The project structure is as follows:

<a href="http://blog.ik.am/api/v1/files/a1949d15-b358-4b2c-9f44-09e29b5e8a7a/configserver-structure.png"><img src="http://blog.ik.am/api/v1/files/a1949d15-b358-4b2c-9f44-09e29b5e8a7a/configserver-structure.png" width="40%"></a>

Server runs on 8888 port when you run `App` class. Defaut configured `application.yml` is included in spring-cloud-config-server-1.0.0.M1.jar.

By accessing `http://localhost:8888/{name}/{env}/{label}`, you can get the configuration for each environment(profile) of each application.

Probably you can regard

* `name` as application name
* `env` as profile name (default is `default`)
* `label` as branch name (default is `master`)

`label` can be omitted.

In the [making/config-repo](https://github.com/making/config-repo) example, these are the following pic:

<a href="http://blog.ik.am/api/v1/files/e9ec6c76-a971-431b-9496-408321285975/env-on-github.png"><img src="http://blog.ik.am/api/v1/files/e9ec6c76-a971-431b-9496-408321285975/env-on-github.png" width="80%"></a>

* By accessing http://localhost:8888/foo/default ,you can get the configuration of [foo.properties](https://github.com/making/config-repo/blob/master/foo.properties).
* By accessing http://localhost:8888/foo/development ,you can get the configuration [foo.properties](https://github.com/making/config-repo/blob/master/foo.properties) overwritten by [foo-development.properties](https://github.com/making/config-repo/blob/master/foo-development.properties).

``` bash
$ curl -X GET http://localhost:8888/foo/default | jq .
{
  "propertySources": [
    {
      "source": {
        "foo": "b",
        "test": "This is a test",
        "bar": "123456"
      },
      "name": "https://github.com/making/config-repo/foo.properties"
    },
    {
      "source": {
        "info.url": "https://github.com/spring-cloud-samples",
        "info.description": "Spring Cloud Samples"
      },
      "name": "https://github.com/making/config-repo/application.yml"
    }
  ],
  "label": "master",
  "name": "default"
}
```
Then set `development` to `env` and send a request:

``` bash
$ curl -X GET http://localhost:8888/foo/development | jq .
{
  "propertySources": [
    {
      "source": {
        "bar": "spam"
      },
      "name": "https://github.com/making/config-repo/foo-development.properties"
    },
    {
      "source": {
        "foo": "b",
        "test": "This is a test",
        "bar": "123456"
      },
      "name": "https://github.com/making/config-repo/foo.properties"
    },
    {
      "source": {
        "info.url": "https://github.com/spring-cloud-samples",
        "info.description": "Spring Cloud Samples"
      },
      "name": "https://github.com/making/config-repo/application.yml"
    }
  ],
  "label": "master",
  "name": "development"
}
```
Returned both values `default` and`development`. Which to use is determined at Client side. (this case,`development`is prioritized)

Let's change `foo-development.properties` on Github as follows:

```
bar: Updated!
foo: Added!
```

Then send a request to`http://localhost:8888/foo/development` again

``` bash
$ curl -X GET http://localhost:8888/foo/development | jq .
{
  "propertySources": [
    {
      "source": {
        "foo": "Added!",
        "bar": "Updated!"
      },
      "name": "https://github.com/making/config-repo/foo-development.properties"
    },
    {
      "source": {
        "foo": "b",
        "test": "This is a test",
        "bar": "123456"
      },
      "name": "https://github.com/making/config-repo/foo.properties"
    },
    {
      "source": {
        "info.url": "https://github.com/spring-cloud-samples",
        "info.description": "Spring Cloud Samples"
      },
      "name": "https://github.com/making/config-repo/application.yml"
    }
  ],
  "label": "master",
  "name": "development"
}
```

Returned latest value though pulling Git(In the following description, the original contents are reverted with `git push -f origin HEAD^:master`)

How to configure authentication/authorization and encrypt/decrypt are described in the [official document](http://cloud.spring.io/spring-cloud-config/spring-cloud-config.html#_spring_cloud_config_server).

### Setup Client
Let's move on to the Client side. Client is a Spring Boot application with the dependency on `org.springframework.cloud:spring-cloud-config-client`, which enables to connect Config Server automatically and use properties via Config Server.
Also add the dependency on `org.springframework.boot:spring-boot-starter-actuator`.

The application name of Client can be defined with `spring.application.name` key in `bootstrap.yml`.

```
spring:
  application:
    name: foo
```

By default, Config Server connects to `http://localhost:8888` with `env=default` and `label=master`. To override, configure as bellow:

```
spring:
  application:
    name: foo
  cloud:
    config:
      env: default # optional
      label: master # optional
      uri: http://localhost:8888 # optional
```

In `ClientApp`class which is the entry point for Client, implement a simple controller uses `bar` property.

``` java
package demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@EnableAutoConfiguration
@ComponentScan
@RestController
public class ClientApp {
    @Value("${bar:World!}")
    String bar;

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

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

pom.xml is as follows

``` xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>demo</groupId>
    <artifactId>configclient</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.1.5.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starters</artifactId>
                <version>1.0.0.M1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.ClientApp</start-class>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>http://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>http://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework</groupId>
                        <artifactId>springloaded</artifactId>
                        <version>${spring-loaded.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>
```


Project structure is as follows. (`application.yml` is described later.)

<a href="http://blog.ik.am/api/v1/files/4153a2ac-8aae-41b1-b3cb-1b5a4d58eefd/configclient-structure.png"><img src="http://blog.ik.am/api/v1/files/4153a2ac-8aae-41b1-b3cb-1b5a4d58eefd/configclient-structure.png" width="40%"></a>

You can find properties are obtained from Config Server by listing `PropertiesSource` using the feature of Spring Boot Actuator ([http://localhost:8080/env](http://localhost:8080/env)).


<a href="http://blog.ik.am/api/v1/files/90ea7ff7-e99c-4dfd-915f-aa923c955faa/client-env.png"><img src="http://blog.ik.am/api/v1/files/90ea7ff7-e99c-4dfd-915f-aa923c955faa/client-env.png" width="80%"></a>

You can also see a single property.

``` bash
$ curl http://localhost:8080/env/bar
123456
```

When you access to the controller,

``` bash
$ curl http://localhost:8080
Hello 123456!
```
you can find the property on Config Server is injected.

#### Change configuration dynamically

Then [rewrite](https://github.com/making/config-repo/commit/28a401b163cc77daba5478946b845362e28cb306) a property value on the Config Server as follows:

```
bar: Spring Boot
```

When you access a single property,

``` bash
$ curl http://localhost:8080/env/bar
123456
```

the change on the Config Server is not reflected at this stage. In order to reflect it, accessing `refresh` endpoint with POST method is required.

``` bash
$ curl -X POST http://localhost:8080/refresh
["bar"]
$ curl http://localhost:8080/env/bar
Spring Boot
```

You can find `PropertySource`is reflected in the client. 

Again,

``` bash
$ curl http://localhost:8080
Hello 123456!
```
It is not possible to refresh the properties which are already "DI"ed (because this Controller is a singleton scope).

In order to re"DI", restart DI container is required by accessing `restart`endpoint with POST method.

``` bash
$ curl -X POST http://localhost:8080/restart
{"message":"Restarting"}
```

You can find DI container restarted from log (but it takes a few seconds).

``` bash
$ curl http://localhost:8080
Hello Spring Boot!
```
At last, I could refresh the configuration without restarting the application!

By the way, `restart`endpoint is disabled by default. To enable it `application.yml` is needed to set as follows:

```
endpoints:
  restart:
    enabled: true
```

#### Ad-hoc configuration change

Without rewriting the Config Server, it is possible to change the configuration in the Client side ad hoc.


POST properties to `env` endpoint as follows:

``` bash
$ curl -X POST http://localhost:8080/env -d bar="Spring Cloud"
{"bar":"Spring Cloud"}
```

At this point, `PropertySource` of this application is re-written, updated value can be obtained by GETting the property.

``` bash
$ curl http://localhost:8080/env/bar
Spring Cloud
```
However, the result of the controller is unchanged since re-DI is not performed.

``` bash
$ curl http://localhost:8080
Hello Spring Boot!
```

Also `refesh` cannot change the result.

``` bash
$ curl -X POST http://localhost:8080/refresh
[]
$ curl http://localhost:8080
Hello Spring Boot!
```
Again `restart` by restarting the DI container, it is possible to rewrite the result of the controller.

``` bash
$ curl -X POST http://localhost:8080/restart
{"message":"Restarting"}
$ curl http://localhost:8080
Hello Spring Cloud!
```

#### Introduction of Refresh scope
So far, you may feel unhappy to restart DI container because it takes a long time.

So refresh scope is introduced.  A bean annotated with`@RefreshScope`are re-instantiated without restarting the DI container by POSTing `refresh` endpoint.


Modify `ClientApp`

``` java
package demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

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

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

When the application is restarted, it can be seen that ad hoc changes has been lost.

```
$ curl http://localhost:8080
Hello Spring Boot!
```

Again, change the value

```
$ curl -X POST http://localhost:8080/env -d bar="Spring Cloud Config"
{"bar":"Spring Cloud Config"}
```

The send a POST request to `refresh` endpoint.
```
$ curl -X POST http://localhost:8080/refresh
[]
```
Although a little while ago I did not changed this controller anything, this time `@RefreshScope` is anontated.


```
$ curl http://localhost:8080
Hello Spring Cloud Config!
```

It was possible to reflect the properties!

By the way,

* The beans annotated `@ConfigurationProperties`
* The properties for the log level logging.level. *

has a refresh scope from the beginning.


----
Sample code is [here](https://github.com/making/config)。

I thought again Spring's DI container was excellent! 
This feature should be prepared in Spring core.

Introduction of `@Conditional` (rather than Spring Boot) was revolutionary for Spring which provides like this functionality without almost any settings.