Level: Advanced
Time: ~75 minutes
Learning Objectives
- Understand microservices in the context of Spring Boot
- Use Spring Cloud (optional) for service discovery and config
- Implement inter-service communication (HTTP client, resilience)
- Apply API gateway and observability concepts at a high level
12.1 Microservices in Short
- Monolith: One deployable application containing all features.
- Microservices: Many small services, each owning a bounded context, deployed and scaled independently.
Spring Boot is well-suited for implementing each service: stand-alone JARs, embedded server, and a rich ecosystem (Spring Cloud, Spring Cloud Netflix, etc.).
12.2 Building a Single Service (Recap)
Each microservice is typically:
- A Spring Boot application
- Owns its own data (database per service)
- Exposes REST (or gRPC) API
- Configured via environment and profiles
- Monitored via Actuator (health, metrics)
12.3 Inter-Service Communication
HTTP Client (RestClient / RestTemplate / WebClient)
RestClient (Spring 6.1+):
@Service
public class OrderService {
private final RestClient orderClient;
public OrderService(RestClient.Builder builder) {
this.orderClient = builder
.baseUrl("http://order-service:8080")
.build();
}
public Order getOrder(Long id) {
return orderClient.get()
.uri("/api/orders/{id}", id)
.retrieve()
.body(Order.class);
}
}
WebClient (reactive, non-blocking):
@Service
public class OrderService {
private final WebClient webClient;
public OrderService(WebClient.Builder builder) {
this.webClient = builder
.baseUrl("http://order-service:8080")
.build();
}
public Mono<Order> getOrder(Long id) {
return webClient.get()
.uri("/api/orders/{id}", id)
.retrieve()
.bodyToMono(Order.class);
}
}
Base URL should come from configuration (e.g. app.order-service.url) or service discovery.
12.4 Resilience: Retry and Circuit Breaker
Failures and latency in other services should not bring down your service.
- Retry: Retry failed HTTP calls with backoff (e.g. Spring Retry, or resilience4j).
- Circuit breaker: After N failures, stop calling the downstream service for a period, then try again (e.g. Resilience4j, Spring Cloud Circuit Breaker).
Resilience4j (conceptual):
@CircuitBreaker(name = "orderService", fallbackMethod = "getOrderFallback")
public Order getOrder(Long id) {
return orderClient.get()
.uri("/api/orders/{id}", id)
.retrieve()
.body(Order.class);
}
public Order getOrderFallback(Long id, Exception ex) {
return Order.unknown(id);
}
Implementation details (dependency, configuration, and exact annotations) depend on the version of Spring Boot and the resilience library you use.
12.5 Service Discovery (Conceptual)
In a dynamic environment, service instances come and go. Service discovery lets clients find instances by name (e.g. “order-service”) instead of hard-coding URLs.
- Client-side discovery: Client asks a registry (e.g. Consul, Eureka) for instances and then calls one.
- Server-side discovery: A load balancer or API gateway resolves the service name; the client only calls the gateway.
Spring Cloud provides integration with Eureka, Consul, and others. You register your app with the discovery server and optionally use a client (e.g. DiscoveryClient) or a load-balanced RestTemplate/WebClient to resolve service names.
12.6 Centralized Configuration (Conceptual)
Spring Cloud Config provides a central server that serves configuration (e.g. from Git) to applications. Each application can have a profile-specific config (e.g. myapp-prod.yml). This is optional; many teams use environment variables and Kubernetes ConfigMaps instead.
12.7 API Gateway (Conceptual)
An API Gateway sits in front of multiple services and provides:
- Single entry point (e.g.
https://api.example.com) - Routing (e.g.
/orders/*→ order-service) - Auth, rate limiting, logging
Spring Cloud Gateway is a reactive gateway built on Spring WebFlux. Each backend service remains a Spring Boot app; the gateway is another Spring Boot app that routes and optionally enforces security.
12.8 Observability
- Logging: Structured logs (e.g. JSON); correlate by trace id.
- Metrics: Actuator + Micrometer; export to Prometheus/Grafana.
- Tracing: Distributed tracing (e.g. Sleuth/Micrometer Tracing, Zipkin) to follow a request across services.
12.9 Summary
- Microservices = many small Spring Boot apps, each with its own API and data.
- Communication: HTTP via RestClient/WebClient; base URL from config or discovery.
- Resilience: Retry and circuit breaker for downstream calls.
- Discovery: Optional; use a registry (Eureka, Consul) or Kubernetes DNS.
- Config: Centralized (e.g. Spring Cloud Config) or env/Kubernetes.
- Gateway: Optional; Spring Cloud Gateway for routing and cross-cutting concerns.
- Observability: Logs, metrics, and tracing for production.
Exercises
- Create a second Spring Boot service “Product Service” that exposes GET /api/products. Call it from the first service via RestClient with base URL from application.properties.
- Add a simple retry (e.g. Spring Retry or manual loop) when the Product Service call fails.
- (Optional) Add Spring Cloud Config or use environment variables for the Product Service URL and verify the client uses it.