Project: A Discoverable Microservices Setup
Project: A Discoverable Microservices Setup
The previous nine lessons have introduced each layer of the Spring Cloud stack individually: Eureka for registry, Spring Cloud Config for centralised properties, and Spring Cloud Gateway for the single entry point. This final lesson ties them together into a working, deployable system. By the end you will have three runnable services — a Config Server, a Eureka registry, and an API Gateway — plus one business service that registers with Eureka, fetches its configuration from the Config Server, and is reachable through the Gateway. Everything is wired the way a production team would actually wire it.
System Topology
Before writing any code, picture the four processes and the order in which they start:
- Config Server — starts first; reads properties from a Git repository (or a local classpath folder for the project). All other services are its clients.
- Eureka Server — starts second; fetches its own configuration from the Config Server, then opens its registry for business. Other services register here.
- API Gateway — starts third; registers with Eureka and fetches routing rules from the Config Server. It resolves service addresses via Eureka at request time.
- Order Service (sample business service) — starts last; registers with Eureka and fetches its
application.propertiesfrom the Config Server. The Gateway routes inbound traffic to it by its Eureka service ID, never by hardcoded host/port.
readinessProbe) or with Spring Cloud's retry-on-connect bootstrap. For a local run, simply start the four services in the order above with a few seconds between each.
1. Config Server
Create a Spring Boot project with the spring-cloud-config-server dependency, then annotate the main class:
src/main/resources/application.yml — point it at a local classpath folder so the project works without a remote Git server:
Create the folder src/main/resources/config-repo/ and add one file per downstream service. The naming convention is {application-name}.yml.
eureka-server.yml
api-gateway.yml
order-service.yml
spring.application.name of the connecting client. You never hardcode which service gets which file — the client identifies itself and the server does the matching.
2. Eureka Server
Create a second project with spring-cloud-starter-netflix-eureka-server, annotate the main class with @EnableEurekaServer, and set its bootstrap to pull from the Config Server:
That single spring.config.import line is the entire bootstrap. Spring Cloud Config Client (on the classpath) intercepts startup, contacts http://localhost:8888/eureka-server/default, and merges the remote properties before any bean is created. The eureka-server.yml file you placed in the config repo now controls port 8761 and the standalone registry flags.
3. API Gateway
Create a project with spring-cloud-starter-gateway, spring-cloud-starter-netflix-eureka-client, and spring-cloud-starter-config. Main class:
Bootstrap properties:
With discovery.locator.enabled: true (set in the remote api-gateway.yml), the Gateway automatically creates a route for every service registered in Eureka. A request to http://localhost:8080/order-service/api/orders is resolved by stripping the service-ID prefix and forwarding to whichever instance of order-service Eureka knows about — no manually declared routes required.
ReactorLoadBalancerExchangeFilterFunction backed by Spring Cloud LoadBalancer. When Eureka returns multiple instances of order-service, the Gateway picks one using a round-robin strategy by default. No Ribbon, no extra code.
4. Order Service
A minimal REST service that reads a config value and registers with Eureka:
prefer-ip-address: true tells Eureka to register the service's IP instead of its hostname. This avoids DNS resolution issues inside containers and is the standard setting for containerised deployments.
End-to-End Smoke Test
Start all four processes in order, then verify each layer:
- Config Server health:
GET http://localhost:8888/order-service/default— should return the merged properties JSON. - Eureka dashboard: open
http://localhost:8761— you should seeAPI-GATEWAYandORDER-SERVICElisted as UP. - Direct call:
GET http://localhost:8081/api/orders— bypasses the Gateway, confirms the service works. - Gateway call:
GET http://localhost:8080/order-service/api/orders— routed via Eureka, confirms discovery and routing work together. - Config read-through:
GET http://localhost:8080/order-service/api/orders/config— should return{"maxPerCustomer": 10}, proving the value came from the Config Server.
Security Considerations
A production setup needs several hardening steps that this project skeleton intentionally omits for clarity:
- Secure the Config Server actuator and config endpoints. Add Spring Security to the Config Server and require HTTP Basic or OAuth2 credentials. Without this, anyone who can reach port 8888 can read every service's secrets.
- Encrypt sensitive values. The Config Server supports symmetric (AES) and asymmetric (RSA) encryption via
{cipher}prefixes. Store the cipher text in Git; the server decrypts on delivery. - Restrict the Eureka management port. The
/eureka/appsAPI is unauthenticated by default. In production, place Eureka behind a private network or add Spring Security with a service-account password. - Never expose the Gateway management endpoints publicly. The
/actuator/gateway/routesendpoint reveals your entire routing table. Bind the management port to a non-public interface.
discovery.locator.enabled: true, every service in Eureka becomes reachable through the Gateway. If you register an internal admin service with Eureka, it is now publicly accessible unless you add explicit route predicates or a security filter to block it. In production, prefer explicit route definitions over the auto-discovery locator, or whitelist the service IDs that should be exposed.
Distributed-Systems Trade-offs
This architecture solves real problems but introduces new ones. Know the trade-offs before committing:
- Config Server as a single point of failure. If it goes down at startup, dependent services cannot boot. Mitigate with a Config Server cluster (Spring Cloud Config Server supports multiple Git replicas) or by caching a local copy of properties with
spring.cloud.config.fail-fast: falseand a localapplication.ymlfallback. - Eureka eventual consistency. Eureka uses a heartbeat model, not a consensus protocol. A freshly deregistered service can remain in the registry for up to 90 seconds (3 missed heartbeats). Your clients must tolerate a small number of calls to dead instances — which is exactly why circuit breakers (Resilience4j, covered in the next tutorial) pair naturally with service discovery.
- Gateway as a bottleneck. All public traffic flows through one process. Run at least two Gateway instances behind a hardware or cloud load balancer, and size them for the peak fan-out of your most expensive routes.
Summary
You now have a complete, working skeleton: a Config Server that owns all properties, a Eureka registry that owns all addresses, and a Gateway that resolves both at request time. The Order Service never knows where its peers live — it asks Eureka. It never owns its own configuration — it asks the Config Server. Adding a new service to this system means writing the business logic and a spring.application.name; the infrastructure takes care of the rest. That is the payoff of the patterns you have studied in this tutorial.