At the end of said post I outlined how updating a Git-backed property and sending a POST request to the client application’s /refresh actuator endpoint caused the configuration property to be reloaded and the dependent Spring beans to be refreshed picking up the new value.
But what if it’s not just one or two microservices but maybe dozens or even hundreds that would need to reload those properties changes? Hitting /refresh for each one doesn’t seem like an elegant solution. What if the numbers of instances keep growing?
In this post I’ll extend the Spring Cloud Config Server and the client service implemented in part 1 with Spring Cloud Bus and RabbitMQ support and a Bitbucket webhook to automatically notify subscribed client services of changes in the Git-backed configuration files.
A Spring Cloud Config server instance for the Refreshable Demo Config client to read properties from.
A RabbitMQ host for the Config server to publish changes to and for the subscribed clients to get notifications with the updated properties.
3. SETTING UP RABBITMQ
To keep this tutorial simple, a RabbitMQ service instance from https://www.cloudamqp.com’s free plan is going to be used. All needed is to set an exchange up using these values:
name
springCloudBus
type
topic
durable
true
autoDelete
false
internal
false
4. UPDATING THE SPRING CLOUD CONFIG SERVER
pom.xml:
spring-cloud-config-monitor provides a /monitor endpoint for the Config server to receive notification events when properties backed by a Git repository change, only if spring.cloud.bus property is enabled. spring-cloud-starter-stream-rabbit is used to send event notifications from the Config server to a RabbitMQ exchange (again, only if spring.cloud.bus property is enabled) with the properties change for subscribers to get notified with.
Note:Apache Kafka or Redis could also be used instead of RabbitMQ.
ConfigServerApplication.java:
Now that RabbitMQ libraries are in the classpath, the main entry point of the Config server is going to exclude RabbitMQ autoconfiguration and activate it only when the config-monitorSpring profile is used so that this infrastructure microservice could be started as standalone service or in a registration-first approach without the need to process Git push event notifications.
ConfigMonitorConfiguration.java:
This Spring configuration class, which is scanned as part of the application startup process, will only be used when activating the config-monitorSpring profile. It’s at this point where RabbitMQ autoconfiguration takes place.
application.yml:
By default spring.cloud.bus.enabled is set to false, meaning the Spring Cloud Config server won’t use Spring Cloud Bus capabilities to process Git push events notifications.
Once the config-monitor profile is activated, through -Dspring.profiles.active=config-monitor for instance, Git push events notifications sent via a Bitbucket webhook are processed by the Config server and changes are sent to a RabbitMQ exchange for subscribed client services listeners to process.
spring-cloud-starter-eureka was left out to keep this demo simple. This service won’t register with a Discovery server and neither will retrieve the Config server metadata from it. If you are interested in find out more about this topic please refer to Centralized and versioned configuration using Spring Cloud Config Server and Git post. spring-cloud-starter-bus-amqp brings the dependencies required for this service to subscribe to the RabbitMQ exchange where Spring Cloud Config server will send messages with the properties that need to be refreshed.
bootstrap.yml:
The Config service is found at http://localhost:8100 and the developmentSpring profile is active by default.
application.yml:
Similarly to Config server’s application.yml, these are properties used to connect to the RabbitMQ host.
No Eureka-related setting is found in this file, because again, registering with the Discovery server was removed to keep this post simple.
ActorResource.java:
This is the resource that includes a property app.message coming from a remote Git-backed file, that it’s supposed to get updated once the file including it is pushed, thanks to the @RefreshScope annotation.
6. ADDING A BITBUCKET WEBHOOK
Let’s add a Bitbucket webhook so that the Config server gets notified of repository push events:
Bitbucket Config server webhook
7. RUNNING THE CONFIG SERVER
It could be run using either java <VM args> -jar target/config-server.jar or spring-boot:runMaven goal as shown next:
Notice the RabbitMQ message channel / handler logs.
8. RUNNING THE DEMO REFRESHABLE CONFIG CLIENT
Lets run this demo using Java instead:
It registers a listener to the springCloudBus topic, uses the developmentSpring profile and reads the app.message property from the remote file demo-refreshable-config-client-development.properties found in a Git repo.
8.1. Sending an initial request to the endpoint
Nothing out of the ordinary, App message from development profile - Update 6 value comes from the Git-backed file.
8.2. Simulating a push event
Lets send a POST request to the Config server /monitor endpoint to simulate a request Bitbucket would send when a Git repo file is pushed:
The response is not necessarily relevant at this point, just the is’s a 200 OK and it causes the Config server to process it:
Config server logs:
Notice the Refresh for: and RefreshListener:27 - Received remote refresh request. Keys refreshed [] logs, it refreshes its properties, nothing changed though, because this was just a simulation with an empty changeset payload.
Refreshable config demo client logs:
Again, RefreshListener:27 - Received remote refresh request. means a refresh event was successfully received.
But sending another request to the /actors/1 won’t result in a different response because the file in the Git repo hasn’t changed, lets now update app.message property in the Git repository:
Notice RefreshListener:27 - Received remote refresh request. Keys refreshed [app.message, config.client.version] log, this time the ActorResource’s msg attribute should have gotten updated with the new app.message value.
Lets now send a GET request to /actors/1 endpoint:
There you have it.
Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.
9. SOURCE CODE
Accompanying source code for this blog post can be found at:
Orlando L Otero is a Sr Software Engineer Consultant focusing on integration, modernization, cloud adoption and migration, microservices, API design and implementation, and agile delivery.