Spring Cloud Basics
Architecture Overview
┌─────────────────────────────────────────────────────────────────┐ │ API Gateway │ │ (Spring Cloud Gateway) │ └───────────────────────────┬─────────────────────────────────────┘ │ ┌───────────────────────────▼─────────────────────────────────────┐ │ Service Discovery │ │ (Eureka / Consul) │ └──────────┬─────────────────┬─────────────────┬──────────────────┘ │ │ │ ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ │ Service A │ │ Service B │ │ Service C │ │ (3 inst.) │ │ (2 inst.) │ │ (1 inst.) │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────┼─────────────────┘ ▼ ┌────────────────┐ │ Config Server │ └────────────────┘
Quick Start - Eureka
Server
@SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
server: port: 8761 eureka: client: register-with-eureka: false fetch-registry: false
Client
spring: application: name: product-service eureka: client: service-url: defaultZone: http://localhost:8761/eureka instance: prefer-ip-address: true
Full Reference: See service-discovery.md for Eureka HA and Config Server.
Quick Start - API Gateway
spring: cloud: gateway: routes: - id: product-service uri: lb://product-service predicates: - Path=/api/products/** filters: - StripPrefix=0
Full Reference: See gateway.md for custom filters and programmatic routes.
Quick Start - Circuit Breaker
@Service public class ProductClient {
@CircuitBreaker(name = "productService", fallbackMethod = "fallback")
@Retry(name = "productService")
public List<Product> getProducts() {
return restClient.get()
.uri("http://product-service/api/products")
.retrieve()
.body(new ParameterizedTypeReference<>() {});
}
private List<Product> fallback(Exception e) {
return List.of();
}
}
resilience4j: circuitbreaker: instances: productService: sliding-window-size: 10 failure-rate-threshold: 50 wait-duration-in-open-state: 10s
Full Reference: See resilience.md for Retry, Bulkhead, Rate Limiter, Feign.
Service Communication Pattern
@Service @RequiredArgsConstructor public class OrderService {
private final ProductClient productClient;
private final InventoryClient inventoryClient;
private final PaymentClient paymentClient;
@Transactional
public Order createOrder(CreateOrderRequest request) {
// 1. Verifica prodotti
List<Product> products = request.items().stream()
.map(item -> productClient.getProductById(item.productId()))
.toList();
// 2. Verifica inventario
boolean available = inventoryClient.checkAvailability(request.items());
if (!available) {
throw new InsufficientInventoryException("Items not available");
}
// 3. Crea ordine
Order order = Order.create(request.customerId(), products, request.items());
order = orderRepository.save(order);
// 4. Riserva inventario
inventoryClient.reserveItems(order.getId(), request.items());
// 5. Processa pagamento (con rollback)
try {
PaymentResult payment = paymentClient.processPayment(
new PaymentRequest(order.getId(), order.getTotal())
);
order.setPaymentId(payment.paymentId());
order.setStatus(OrderStatus.PAID);
} catch (PaymentFailedException e) {
inventoryClient.releaseReservation(order.getId());
order.setStatus(OrderStatus.PAYMENT_FAILED);
throw e;
}
return orderRepository.save(order);
}
}
Load Balancer
@Configuration public class LoadBalancerConfig {
@Bean
@LoadBalanced
public RestClient.Builder loadBalancedRestClientBuilder() {
return RestClient.builder();
}
}
// Usage: use service name instead of host restClient.get() .uri("http://product-service/api/products") .retrieve() .body(new ParameterizedTypeReference<>() {});
Distributed Tracing
management: tracing: sampling: probability: 1.0 zipkin: tracing: endpoint: http://localhost:9411/api/v2/spans
logging: pattern: level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
Best Practices
Do Don't
Use Service Discovery for all services Hardcode service URLs
Implement Circuit Breaker with fallback Ignore failures
Centralize config with Config Server Duplicate configuration
Add distributed tracing Miss observability
Use API Gateway as single entry point Expose services directly
When NOT to Use This Skill
-
Single service - Spring Cloud adds unnecessary complexity
-
Kubernetes native - Use K8s service discovery, ConfigMaps
-
Simple deployments - Overhead not justified
-
Specific components - Use dedicated skills for deep dives
Common Pitfalls
Error Cause Solution
No instances available
Service not registered Verify Eureka registration
Connection refused
Service down Implement Circuit Breaker
Timeout
Service slow Configure appropriate timeouts
Config not loading
Config server unreachable Use fail-fast: false or fallback
Load balancing not working Missing @LoadBalanced Annotate RestClient builder
Anti-Patterns
Anti-Pattern Problem Solution
Hardcoding service URLs No discovery benefit Use service names
No circuit breaker Cascading failures Add Resilience4j
Missing retry Transient failures Configure retry with backoff
No config refresh Changes need redeploy Use @RefreshScope
Synchronous everywhere Tight coupling Use async where appropriate
Quick Troubleshooting
Problem Diagnostic Fix
Service not found Check Eureka dashboard Verify registration
Config not loading Check config server logs Verify path, profile
Circuit always open Check failure threshold Tune thresholds
Gateway routing fails Check predicates Verify route config
Load balancing not working Check @LoadBalanced Add annotation
Reference Files
File Content
service-discovery.md Eureka Server/Client, Config Server
gateway.md API Gateway, Filters, Routes
resilience.md Circuit Breaker, Retry, Feign, Testing
External Documentation
-
Spring Cloud
-
Spring Cloud Gateway
-
Resilience4j