Why this matters
- Hardcoding
http://inventory-service:8081in every client breaks when services move, scale, or get replaced during a deploy. - A central config server lets you change payment gateway URLs or feature flags across all services without rebuilding images.
- An API gateway gives BookStore customers one entry point while routing to catalog, order, and payment services behind the scenes.
StreamHub production architecture (AWS)
Spring Cloud components for BookStore
- Spring Cloud Gateway — reactive edge proxy; routes, rate-limits, and authenticates incoming traffic.
- Eureka / Consul — service registry; instances register on startup and deregister on shutdown.
- Spring Cloud Config — centralised
application.ymlserved per service and profile. @LoadBalanced RestClient— resolves service names through the registry instead of hardcoded hosts.- Spring Cloud Kubernetes — alternative to Eureka when you deploy on K8s and use native service discovery.
Service registry with Eureka
<!-- Eureka server (separate deployable) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServerApplication.class, args);
}
}
# bookstore-catalog-service application.yml
eureka:
client:
service-url:
defaultZone: http://discovery-server:8761/eureka/
instance:
prefer-ip-address: true
spring:
application:
name: bookstore-catalog
API Gateway routing
@Configuration
public class GatewayRoutesConfig {
@Bean
public RouteLocator bookstoreRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("catalog", r -> r
.path("/api/books/**")
.filters(f -> f.stripPrefix(0)
.addRequestHeader("X-Gateway", "bookstore"))
.uri("lb://bookstore-catalog"))
.route("orders", r -> r
.path("/api/orders/**")
.uri("lb://bookstore-orders"))
.route("payments", r -> r
.path("/api/payments/**")
.filters(f -> f.circuitBreaker(config -> config
.setName("paymentCircuitBreaker")
.setFallbackUri("forward:/fallback/payments")))
.uri("lb://bookstore-payments"))
.build();
}
}
Centralised configuration
# config-server application.yml
spring:
cloud:
config:
server:
git:
uri: https://github.com/bookstore/platform-config
search-paths: "{application}"
# bookstore-orders bootstrap.yml
spring:
application:
name: bookstore-orders
config:
import: "configserver:http://config-server:8888"
profiles:
active: ${DEPLOY_ENV:local}
@Configuration
@EnableConfigurationProperties(OrderServiceProperties.class)
public class OrderConfig {}
@ConfigurationProperties(prefix = "bookstore.orders")
public record OrderServiceProperties(
Duration reservationTimeout,
int maxItemsPerOrder,
String paymentServiceUrl
) {}
Load-balanced client calls
@Configuration
public class ServiceClientConfig {
@Bean
@LoadBalanced
public RestClient.Builder loadBalancedRestClientBuilder() {
return RestClient.builder();
}
}
@Service
public class InventoryClient {
private final RestClient restClient;
public InventoryClient(@LoadBalanced RestClient.Builder builder) {
this.restClient = builder.build();
}
public StockLevel checkStock(String isbn) {
return restClient.get()
.uri("http://bookstore-inventory/api/stock/{isbn}", isbn)
.retrieve()
.body(StockLevel.class);
}
}
Quick recall
Everything you need if you only revisit this box.
- Spring Cloud Gateway is the single entry point; route by path to backend services via
lb://service-name. - Eureka (or K8s DNS) lets clients resolve service names without hardcoded URLs.
- Spring Cloud Config serves environment-specific properties from a Git-backed config server.
@LoadBalanced RestClient.Builderpicks a healthy instance from the registry on each call.- Do not adopt microservices until independent deployment and scaling justify the operational complexity.
Test yourself
Answer these before moving on — recall is what makes it stick.