Yesterday I put a gateway in front of the system, so the client only ever talks to one URL. But look inside and there was still a mess I'd been ignoring: every service carried its own copy of the config. The Eureka registry URL was pasted into order-service, into inventory-service, into the gateway — the same line, three times. Change the registry and I'd be editing three files, rebuilding three jars, and redeploying three services, hoping none of them drifted in between. That's config sprawl, and today's job was to kill it: one source of truth every service fetches from.
The idea
Externalize configuration out of the jars into one place, and have each service pull it at startup. That's factor III of the twelve-factor app — separate config from code — taken one step further than profiles and env vars: the build artifact is identical everywhere, and the environment decides what it's configured with.
Spring Cloud Config Server is a tiny Spring Boot app that does exactly this. Add the starter, put @EnableConfigServer on the main class, and it serves each service's merged config over HTTP at /{application}/{profile}. Where the config actually lives is a pluggable backend chosen by profile: native reads plain files from the classpath or filesystem (simplest, and what I used so the whole thing is reproducible with no git remote); git is the production default and gives you versioning, review and rollback for free.
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] a){ SpringApplication.run(ConfigServerApplication.class, a); }
}
server.port: 8888
spring:
profiles.active: native
cloud.config.server.native.search-locations: classpath:/config
The config repo itself is just yaml files laid out in layers: application.yml is shared by every service, {service}.yml is that one service's own, and {service}-{profile}.yml is that service in a given environment. The server merges them most-specific-first, so a value defined in several layers resolves to the most specific one.
config/
application.yml # shared: eureka URL, org-wide props
order-service.yml # order-service only
order-service-dev.yml # order-service, dev profile (overrides)
inventory-service.yml
Making the services clients
A service becomes a config client with one dependency and one line. Since Spring Boot 2.4 the old bootstrap.yml dance is gone — config import is part of the normal config-data loading now.
spring:
application.name: order-service
config.import: "optional:configserver:http://localhost:8888"
That optional: prefix turned out to be the most important word in the file. Without it, every @SpringBootTest in the reactor would try to reach a config server that isn't running during the build and fail hard at context load. With it, when the server is unreachable — tests, offline, a cold start before the server is up — the import is skipped silently and the service boots on its local defaults. The config server never becomes a hard startup dependency.
The property I actually moved out
To prove it rather than just wire it, I deleted the duplicated eureka.client.service-url.defaultZone line from both services and put it once in the config server's shared application.yml. Now there's a single place to change the registry address.
The reason that's safe offline is a small, satisfying detail: Eureka's own built-in default is already http://localhost:8761/eureka/. So if the config server is down, the framework default kicks in and local boot is byte-for-byte unchanged — the centralized value and the fallback happen to agree. I kept each service's prod profile pointing its Eureka URL at ${EUREKA_SERVER_URL}, because some things genuinely belong to the deployment, not the shared repo.
One more thing worth naming even though I didn't fully wire it today: live reload. A @RefreshScope bean is re-created (re-reading its config) when you POST /actuator/refresh, so a central change lands without a restart; Spring Cloud Bus fans that one refresh out to a whole fleet. And secrets do NOT go in the repo — reference them as ${ENV} vars, encrypt them as {cipher} values, or use a Vault backend. Config in the repo, secrets in a vault.
The reactor is five modules now — config-server, eureka-server, api-gateway, order-service, inventory-service — and mvn clean install stays green.
Full 3-tier walkthrough (with a live layered-config sim you can edit and refresh): https://dev48v.infy.uk/orderhub.php
Code: https://github.com/dev48v/order-hub-from-zero
Top comments (0)