16. Spring Cloud
☁️ 16.1 Introducción a Spring Cloud
Section titled “☁️ 16.1 Introducción a Spring Cloud”¿Qué es Spring Cloud?
Section titled “¿Qué es Spring Cloud?”Spring Cloud es un conjunto de herramientas para construir aplicaciones distribuidas y microservicios en la nube.
Componentes principales
Section titled “Componentes principales”| Componente | Función |
|---|---|
| Config Server | Configuración centralizada |
| Eureka | Service Discovery |
| Gateway | API Gateway |
| Resilience4j | Circuit Breaker |
| Sleuth/Micrometer | Distributed Tracing |
BOM de Spring Cloud
Section titled “BOM de Spring Cloud”<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement>⚙️ 16.2 Config Server
Section titled “⚙️ 16.2 Config Server”Servidor de configuración
Section titled “Servidor de configuración”<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId></dependency>@SpringBootApplication@EnableConfigServerpublic class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); }}# application.properties (Config Server)server.port=8888spring.cloud.config.server.git.uri=https://github.com/miorg/config-repospring.cloud.config.server.git.default-label=mainspring.cloud.config.server.git.search-paths=configEstructura del repositorio de configuración
Section titled “Estructura del repositorio de configuración”config-repo/├── application.yml # Configuración común├── usuario-service.yml # Configuración específica├── usuario-service-dev.yml # Perfil dev├── usuario-service-prod.yml # Perfil prod└── pedido-service.ymlCliente de configuración
Section titled “Cliente de configuración”<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId></dependency># application.properties (Microservicio)spring.application.name=usuario-servicespring.config.import=optional:configserver:http://localhost:8888spring.profiles.active=devRefrescar configuración
Section titled “Refrescar configuración”<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency>@RestController@RefreshScope // Permite refrescar beanspublic class ConfigController {
@Value("${app.mensaje}") private String mensaje;
@GetMapping("/mensaje") public String getMensaje() { return mensaje; }}
// POST /actuator/refresh para recargar configuración🔍 16.3 Eureka Server
Section titled “🔍 16.3 Eureka Server”Servidor de descubrimiento
Section titled “Servidor de descubrimiento”<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>@SpringBootApplication@EnableEurekaServerpublic class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); }}# application.propertiesserver.port=8761spring.application.name=eureka-server
eureka.client.register-with-eureka=falseeureka.client.fetch-registry=false
eureka.server.enable-self-preservation=falseeureka.server.eviction-interval-timer-in-ms=5000Cliente Eureka
Section titled “Cliente Eureka”<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency># application.properties (Microservicio)spring.application.name=usuario-serviceserver.port=8081
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/eureka.instance.prefer-ip-address=trueeureka.instance.instance-id=${spring.application.name}:${random.uuid}Dashboard de Eureka
Section titled “Dashboard de Eureka”# Acceder a http://localhost:8761# Verás todos los servicios registrados con su estado🚪 16.4 API Gateway
Section titled “🚪 16.4 API Gateway”Spring Cloud Gateway
Section titled “Spring Cloud Gateway”<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>Configuración de rutas
Section titled “Configuración de rutas”# application.ymlserver:port: 8080
spring:application: name: api-gatewaycloud: gateway: discovery: locator: enabled: true lower-case-service-id: true routes: - id: usuario-service uri: lb://usuario-service predicates: - Path=/api/usuarios/** filters: - StripPrefix=1
- id: producto-service uri: lb://producto-service predicates: - Path=/api/productos/** filters: - StripPrefix=1
- id: pedido-service uri: lb://pedido-service predicates: - Path=/api/pedidos/** filters: - StripPrefix=1Filtros personalizados
Section titled “Filtros personalizados”@Componentpublic class AuthFilter implements GlobalFilter, Ordered {
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest();
// Verificar token if (!request.getHeaders().containsKey("Authorization")) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); }
String token = request.getHeaders().getFirst("Authorization");
if (!validarToken(token)) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); }
return chain.filter(exchange); }
@Override public int getOrder() { return -1; // Ejecutar primero }}Rate Limiting
Section titled “Rate Limiting”spring:cloud: gateway: routes: - id: usuario-service uri: lb://usuario-service predicates: - Path=/api/usuarios/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 key-resolver: "#{@userKeyResolver}"@Configurationpublic class RateLimiterConfig {
@Bean public KeyResolver userKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() ); }}🛡️ 16.5 Resilience4j
Section titled “🛡️ 16.5 Resilience4j”Dependencia
Section titled “Dependencia”<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId></dependency>Configuración completa
Section titled “Configuración completa”# application.ymlresilience4j:circuitbreaker: configs: default: register-health-indicator: true sliding-window-size: 10 minimum-number-of-calls: 5 failure-rate-threshold: 50 wait-duration-in-open-state: 10s permitted-number-of-calls-in-half-open-state: 3 automatic-transition-from-open-to-half-open-enabled: true instances: productoService: base-config: default
retry: configs: default: max-attempts: 3 wait-duration: 1s retry-exceptions: - java.io.IOException - java.net.SocketTimeoutException instances: productoService: base-config: default
timelimiter: configs: default: timeout-duration: 3s cancel-running-future: true instances: productoService: base-config: default
bulkhead: configs: default: max-concurrent-calls: 25 max-wait-duration: 0 instances: productoService: base-config: defaultUso en código
Section titled “Uso en código”@Service@Slf4jpublic class ProductoService {
private final ProductoClient productoClient;
@CircuitBreaker(name = "productoService", fallbackMethod = "fallback") @Retry(name = "productoService") @Bulkhead(name = "productoService") @TimeLimiter(name = "productoService") public CompletableFuture<List<Producto>> listarProductos() { return CompletableFuture.supplyAsync(productoClient::listar); }
public CompletableFuture<List<Producto>> fallback(Throwable t) { log.error("Fallback activado: {}", t.getMessage()); return CompletableFuture.completedFuture(Collections.emptyList()); }}📊 16.6 Observabilidad
Section titled “📊 16.6 Observabilidad”Micrometer + Zipkin
Section titled “Micrometer + Zipkin”<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-brave</artifactId></dependency><dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-reporter-brave</artifactId></dependency># application.propertiesmanagement.tracing.sampling.probability=1.0management.zipkin.tracing.endpoint=http://localhost:9411/api/v2/spansMétricas con Prometheus
Section titled “Métricas con Prometheus”<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId></dependency># application.propertiesmanagement.endpoints.web.exposure.include=health,info,prometheus,metricsmanagement.metrics.export.prometheus.enabled=true🏛️ 16.7 Arquitectura Completa
Section titled “🏛️ 16.7 Arquitectura Completa”Diagrama de arquitectura
Section titled “Diagrama de arquitectura” ┌─────────────────┐ │ Config Server │ │ :8888 │ └────────┬────────┘ │┌──────────────┐ ┌────────┴────────┐ ┌──────────────┐│ Zipkin │ │ Eureka Server │ │ Prometheus ││ :9411 │ │ :8761 │ │ :9090 │└──────────────┘ └────────┬────────┘ └──────────────┘ │ ┌────────┴────────┐ │ API Gateway │ │ :8080 │ └────────┬────────┘ │ ┌────────────────────┼────────────────────┐ │ │ │┌───────┴───────┐ ┌───────┴───────┐ ┌───────┴───────┐│ Usuario Svc │ │ Producto Svc │ │ Pedido Svc ││ :8081 │ │ :8082 │ │ :8083 │└───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │ │ │┌───────┴───────┐ ┌───────┴───────┐ ┌───────┴───────┐│ MySQL │ │ MongoDB │ │ PostgreSQL │└───────────────┘ └───────────────┘ └───────────────┘Docker Compose
Section titled “Docker Compose”version: '3.8'
services:config-server: build: ./config-server ports: - "8888:8888" environment: - SPRING_CLOUD_CONFIG_SERVER_GIT_URI=https://github.com/miorg/config
eureka-server: build: ./eureka-server ports: - "8761:8761" depends_on: - config-server
api-gateway: build: ./api-gateway ports: - "8080:8080" depends_on: - eureka-server environment: - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
usuario-service: build: ./usuario-service ports: - "8081:8081" depends_on: - eureka-server - mysql environment: - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/usuarios - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
mysql: image: mysql:8 environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=usuarios volumes: - mysql_data:/var/lib/mysql
zipkin: image: openzipkin/zipkin ports: - "9411:9411"
prometheus: image: prom/prometheus ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml
volumes:mysql_data:✅ 16.8 Buenas Prácticas
Section titled “✅ 16.8 Buenas Prácticas”Checklist de microservicios
Section titled “Checklist de microservicios”✅ Cada servicio tiene su propia base de datos✅ Comunicación asíncrona cuando sea posible✅ Circuit breakers en todas las llamadas externas✅ Configuración centralizada✅ Service discovery habilitado✅ API Gateway como punto de entrada único✅ Trazabilidad distribuida configurada✅ Health checks en todos los servicios✅ Métricas expuestas para monitoreo✅ Logs estructurados con correlation IDs
🐝