Introducción#
En este post te mostraré cómo migrar una arquitectura de microservicios tradicional hacia una implementación con Spring Cloud. Partiremos de un sistema funcional con tres microservicios independientes (ms01producto, ms02reserva, ms03stock) y un frontend React, para evolucionarlo hacia una arquitectura más robusta y escalable utilizando los componentes de Spring Cloud.
📚 Lectura recomendada: Si quieres entender los conceptos teóricos de Spring Cloud antes de ver la implementación práctica, te recomiendo leer Spring Cloud: Claves para Sistemas Distribuidos Java donde explico los fundamentos y beneficios de esta tecnología.
📁 Repositorios del Proyecto#
Para seguir este tutorial paso a paso, puedes descargar los siguientes repositorios:
🚀 Proyecto Base (Punto de partida): spring-cloud-microservices-base
Contiene la arquitectura inicial de microservicios sin Spring Cloud✅ Proyecto Final (Resultado completo): spring-cloud-microservices-final
La implementación completa con todos los componentes de Spring Cloud integrados
🎯 Reto de Implementación: ¡Sería genial que uses el repositorio base como punto de partida y trates de implementar toda la migración a Spring Cloud por tu cuenta! Sigue los pasos de este tutorial como guía y luego compara tu resultado con el proyecto final. Es una excelente forma de consolidar el aprendizaje y desarrollar tus habilidades prácticas.
⚠️ Importante - Compatibilidad de Versiones:
Este tutorial utiliza Spring Cloud 2025.0.0 que es compatible con Spring Boot 3.5.x. Esta combinación asegura la máxima estabilidad y soporte para las últimas características. En todos los ejemplos depom.xml
encontrarás eldependencyManagement
configurado correctamente para esta versión.
🏗️ Arquitectura Inicial: Antes de Spring Cloud#
Nuestro punto de partida fue una arquitectura simple pero funcional:
Características de la Arquitectura Original#
- Comunicación directa: Los servicios se comunicaban mediante URLs hardcodeadas
- Configuración distribuida: Cada microservicio tenía su propia configuración
- Sin descubrimiento de servicios: Las URLs se configuraban manualmente
- Frontend directo: React se conectaba directamente a cada microservicio
- Sin punto de entrada único: Múltiples puertos expuestos al cliente
Stack Tecnológico Original#
# Microservicios existentes
ms01producto: # Puerto 8081 - MongoDB
ms02reserva: # Puerto 8080 - MySQL
ms03stock: # Puerto 8082 - MySQL
frontend: # Puerto 3000 - React + Nginx
🚀 Arquitectura Final: Con Spring Cloud#
La arquitectura evolucionada incluye todos los componentes de Spring Cloud:
Nuevos Componentes Agregados#
- Config Server (Puerto 8888) - Configuración centralizada
- Service Discovery (Puerto 8761) - Eureka Server
- API Gateway (Puerto 8090) - Punto de entrada único
📋 Paso a Paso: Implementación de Spring Cloud#
1. Config Server - Configuración Centralizada#
Primero creamos el servidor de configuración centralizada.
Crear el proyecto ms-config#
# Estructura del proyecto
ms-config/
├── src/main/java/com/indra/ms_config/
│ └── MsConfigApplication.java
├── src/main/resources/
│ └── application.properties
├── config-repo/
│ └── ms01producto.properties
└── pom.xml
Dependencias en pom.xml#
<properties>
<java.version>21</java.version>
<spring-cloud.version>2025.0.0</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Clase Principal#
package com.indra.ms_config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer // ⭐ Habilita el Config Server
public class MsConfigApplication {
public static void main(String[] args) {
SpringApplication.run(MsConfigApplication.class, args);
}
}
Configuración application.properties#
spring.application.name=ms-config
server.port=8888
# Configuración para archivos locales
spring.profiles.active=native
spring.cloud.config.server.native.search-locations=file:///config-repo
# Actuator endpoints
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
Configuración centralizada para ms01producto#
# config-repo/ms01producto.properties
spring.application.name=ms01producto
server.port=${SERVER_PORT:8081}
# MongoDB configuration
spring.data.mongodb.uri=${SPRING_DATA_MONGODB_URI:mongodb://localhost:27017/productos_db}
# Actuator configuration
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=always
management.info.env.enabled=true
2. Service Discovery - Eureka Server#
Implementamos el servidor de descubrimiento de servicios.
Crear el proyecto ms-discovery#
# Estructura del proyecto
ms-discovery/
├── src/main/java/com/indra/ms_discovery/
│ └── MsDiscoveryApplication.java
├── src/main/resources/
│ └── application.properties
└── pom.xml
Dependencias específicas#
<properties>
<java.version>21</java.version>
<spring-cloud.version>2025.0.0</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Clase Principal#
package com.indra.ms_discovery;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer // ⭐ Habilita Eureka Server
public class MsDiscoveryApplication {
public static void main(String[] args) {
SpringApplication.run(MsDiscoveryApplication.class, args);
}
}
Configuración del Eureka Server#
spring.application.name=ms-discovery
server.port=8761
# Configuración de Eureka Server
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.server.enable-self-preservation=false
eureka.client.service-url.defaultZone=${EUREKA_CLIENT_SERVICE_URL:http://localhost:8761/eureka/}
# Actuator
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
3. API Gateway - Punto de Entrada Único#
Creamos el gateway para centralizar todas las peticiones.
Crear el proyecto ms-gateway#
# Estructura del proyecto
ms-gateway/
├── src/main/java/com/indra/ms_gateway/
│ └── MsGatewayApplication.java
├── src/main/resources/
│ └── application.properties
└── pom.xml
Dependencias del Gateway#
<properties>
<java.version>21</java.version>
<spring-cloud.version>2025.0.0</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Clase Principal (Simple)#
package com.indra.ms_gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MsGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(MsGatewayApplication.class, args);
}
}
Configuración de Rutas y CORS#
spring.application.name=ms-gateway
server.port=8090
# Eureka client configuration
eureka.client.service-url.defaultZone=${EUREKA_CLIENT_SERVICE_URL:http://localhost:8761/eureka/}
eureka.instance.instance-id=${spring.application.name}:${spring.application.instance_id:${random.value}}
# Gateway Discovery Locator
spring.cloud.gateway.server.webflux.discovery.locator.enabled=true
spring.cloud.gateway.server.webflux.discovery.locator.lower-case-service-id=true
# CORS Configuration
spring.cloud.gateway.server.webflux.globalcors.add-to-simple-url-handler-mapping=true
spring.cloud.gateway.server.webflux.globalcors.cors-configurations.[/**].allowed-origins=http://localhost:3000
spring.cloud.gateway.server.webflux.globalcors.cors-configurations.[/**].allowed-methods=GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH
spring.cloud.gateway.server.webflux.globalcors.cors-configurations.[/**].allowed-headers=*
# Routes Configuration
spring.cloud.gateway.server.webflux.routes[0].id=ms01producto-routes
spring.cloud.gateway.server.webflux.routes[0].uri=lb://ms01producto
spring.cloud.gateway.server.webflux.routes[0].predicates[0]=Path=/ms01producto/**
spring.cloud.gateway.server.webflux.routes[0].filters[0]=StripPrefix=1
spring.cloud.gateway.server.webflux.routes[1].id=ms02reserva-routes
spring.cloud.gateway.server.webflux.routes[1].uri=lb://ms02reserva
spring.cloud.gateway.server.webflux.routes[1].predicates[0]=Path=/ms02reserva/**
spring.cloud.gateway.server.webflux.routes[1].filters[0]=StripPrefix=1
spring.cloud.gateway.server.webflux.routes[2].id=ms03stock-routes
spring.cloud.gateway.server.webflux.routes[2].uri=lb://ms03stock
spring.cloud.gateway.server.webflux.routes[2].predicates[0]=Path=/ms03stock/**
spring.cloud.gateway.server.webflux.routes[2].filters[0]=StripPrefix=1
4. Modificaciones a los Microservicios Existentes#
Agregar dependencias Spring Cloud#
En cada microservicio (ms01producto, ms02reserva, ms03stock):
<!-- Agregar al pom.xml -->
<properties>
<java.version>21</java.version>
<spring-cloud.version>2025.0.0</spring-cloud.version>
</properties>
<!-- Agregar estas dependencias -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- Agregar el dependency management -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
💡 Nota sobre spring-cloud-starter-bootstrap:
Esta dependencia es obligatoria en Spring Boot 3.x para que funcionebootstrap.properties
. Sin ella, Spring Cloud Config no podrá cargar la configuración antes del contexto de la aplicación, causando errores de conexión al Config Server.
Habilitar Discovery Client#
// En ms01producto/src/main/java/com/indra/Ms01productoApplication.java
@SpringBootApplication
@EnableDiscoveryClient // ⭐ Agregar esta anotación
public class Ms01productoApplication {
public static void main(String[] args) {
SpringApplication.run(Ms01productoApplication.class, args);
}
}
Configurar bootstrap.properties#
📝 Importante: En Spring Boot 3.x, para usar
bootstrap.properties
es necesario agregar la dependenciaspring-cloud-starter-bootstrap
que se incluye en las dependencias mostradas arriba. Sin esta dependencia, el archivobootstrap.properties
será ignorado.
# ms01producto/src/main/resources/bootstrap.properties
spring.application.name=ms01producto
spring.cloud.config.uri=${SPRING_CLOUD_CONFIG_URI:http://localhost:8888}
# Config Server retry configuration
spring.cloud.config.fail-fast=true
spring.cloud.config.retry.initial-interval=1500
spring.cloud.config.retry.max-interval=10000
spring.cloud.config.retry.max-attempts=10
# Eureka client configuration
eureka.client.service-url.defaultZone=${EUREKA_CLIENT_SERVICE_URL:http://localhost:8761/eureka/}
eureka.instance.instance-id=${spring.application.name}:${spring.application.instance_id:${random.value}}
Actualizar Comunicación Entre Servicios#
// En ms02reserva - InventoryClientService
@Service
@RequiredArgsConstructor
public class InventoryClientService {
private final RestTemplate restTemplate;
private final DiscoveryClient discoveryClient; // ⭐ Inyección de DiscoveryClient
@Value("${stock.service.url:http://ms03stock:8082}")
private String stockServiceUrl;
public Boolean stockAvailable(String code) {
// ⭐ Uso de Service Discovery
ServiceInstance instance = discoveryClient.getInstances("MS03STOCK")
.stream().findFirst().orElse(null);
String url = instance.getUri() + "/api/stock/" + code;
RestTemplate directRestTemplate = new RestTemplate();
return directRestTemplate.getForObject(url, Boolean.class);
}
}
5. Actualización del Docker Compose#
Generación con docker init#
Primero usamos docker init
para generar los Dockerfiles base:
# Para cada microservicio
cd ms-config && docker init
cd ../ms-discovery && docker init
cd ../ms-gateway && docker init
cd ../ms01producto && docker init
cd ../ms02reserva && docker init
cd ../ms03stock && docker init
Docker Compose Final#
services:
# Bases de datos (sin cambios)
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_USER: user
MYSQL_PASSWORD: pass
ports:
- "3307:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./infra/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
mongo:
image: mongo:6.0
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
- ./infra/mongo/products.json:/data/products.json:ro
- ./infra/mongo/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh:ro
command: ["bash", "/docker-entrypoint-initdb.d/mongo-init.sh"]
# ⭐ Spring Cloud Components
ms-config:
build:
context: ./ms-config
environment:
- SPRING_CLOUD_CONFIG_SERVER_NATIVE_SEARCH_LOCATIONS=file:///config-repo
volumes:
- ./ms-config/config-repo:/config-repo:ro
ports:
- 8888:8888
ms-discovery:
build:
context: ./ms-discovery
environment:
- EUREKA_CLIENT_SERVICE_URL=http://ms-discovery:8761/eureka/
ports:
- 8761:8761
ms-gateway:
build:
context: ./ms-gateway
environment:
- EUREKA_CLIENT_SERVICE_URL=http://ms-discovery:8761/eureka/
ports:
- 8090:8090
depends_on:
- ms-config
- ms-discovery
- ms01producto
- ms02reserva
- ms03stock
# ⭐ Microservicios actualizados
ms01producto:
build:
context: ./ms01producto
environment:
- SPRING_CLOUD_CONFIG_URI=http://ms-config:8888
- EUREKA_CLIENT_SERVICE_URL=http://ms-discovery:8761/eureka/
- SPRING_DATA_MONGODB_URI=mongodb://mongo:27017/productos_db
ports:
- 8081:8081
depends_on:
- ms-config
- ms-discovery
- mongo
ms02reserva:
build:
context: ./ms02reserva
environment:
- EUREKA_CLIENT_SERVICE_URL=http://ms-discovery:8761/eureka/
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/reservas_db?useSSL=false&allowPublicKeyRetrieval=true
- SPRING_DATASOURCE_USERNAME=user
- SPRING_DATASOURCE_PASSWORD=pass
- STOCK_SERVICE_URL=http://ms03stock:8082
ports:
- 8080:8080
depends_on:
- ms-config
- ms-discovery
- mysql
ms03stock:
build:
context: ./ms03stock
environment:
- EUREKA_CLIENT_SERVICE_URL=http://ms-discovery:8761/eureka/
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/stock_db?useSSL=false&allowPublicKeyRetrieval=true
- SPRING_DATASOURCE_USERNAME=user
- SPRING_DATASOURCE_PASSWORD=pass
ports:
- 8082:8082
depends_on:
- ms-config
- ms-discovery
- mysql
# Frontend (sin cambios significativos)
frontend:
build:
context: ./frontend
ports:
- 3000:80
depends_on:
- ms-gateway
volumes:
mysql-data:
mongo-data:
🔄 Comparación: Antes vs Después#
Comunicación de Servicios#
Antes:
// Frontend conectando directamente
const API_URLS = {
productos: 'http://localhost:8081/api/productos',
reservas: 'http://localhost:8080/api/reservas',
stock: 'http://localhost:8082/api/stock'
};
Después:
// Frontend usando API Gateway
const API_BASE = 'http://localhost:8090';
const API_URLS = {
productos: `${API_BASE}/ms01producto/api/productos`,
reservas: `${API_BASE}/ms02reserva/api/reservas`,
stock: `${API_BASE}/ms03stock/api/stock`
};
🚀 Comandos de Ejecución#
Desarrollo Local#
# 1. Iniciar bases de datos
docker compose up mysql mongo -d
# 2. Iniciar Spring Cloud components (en orden)
./mvnw spring-boot:run -f ms-config/pom.xml
./mvnw spring-boot:run -f ms-discovery/pom.xml
./mvnw spring-boot:run -f ms-gateway/pom.xml
# 3. Iniciar microservicios
./mvnw spring-boot:run -f ms01producto/pom.xml
./mvnw spring-boot:run -f ms02reserva/pom.xml
./mvnw spring-boot:run -f ms03stock/pom.xml
# 4. Iniciar frontend
cd frontend && npm start
Producción con Docker#
# Todo en un comando
docker compose up --build -d
# Verificar servicios
docker compose ps
docker compose logs ms-discovery # Ver registro Eureka
📊 Beneficios Obtenidos#
1. Configuración Centralizada#
- ✅ Un solo lugar para configurar todos los servicios
- ✅ Actualizaciones en caliente sin reinicios
- ✅ Configuraciones por ambiente (dev, prod)
2. Service Discovery#
- ✅ Auto-registro de servicios
- ✅ Load balancing automático
- ✅ Health checking integrado
3. API Gateway#
- ✅ Punto de entrada único para clientes
- ✅ Enrutamiento dinámico basado en servicios
- ✅ CORS centralizado
- ✅ Posibilidad de agregar autenticación/autorización
4. Monitoreo y Observabilidad#
- ✅ Endpoints de Actuator en todos los servicios
- ✅ Dashboard de Eureka para ver estado de servicios
- ✅ Métricas centralizadas
🔍 URLs de Acceso#
Interfaces de Administración#
- Eureka Dashboard: http://localhost:8761
- Config Server: http://localhost:8888/ms01producto/default
- Gateway Health: http://localhost:8090/actuator/health
APIs (via Gateway)#
- Productos: http://localhost:8090/ms01producto/api/productos
- Reservas: http://localhost:8090/ms02reserva/api/reservas
- Stock: http://localhost:8090/ms03stock/api/stock
Frontend#
- React App: http://localhost:3000
🎯 Próximos Pasos#
Esta implementación sienta las bases para agregar:
- Circuit Breakers con Resilience4j
- Distributed Tracing con Sleuth/Zipkin
- Security con Spring Security OAuth2
- Metrics con Micrometer/Prometheus
- Caching distribuido con Redis
📝 Conclusión#
La migración de una arquitectura de microservicios tradicional a Spring Cloud proporciona beneficios significativos en términos de:
- Mantenabilidad: Configuración centralizada
- Escalabilidad: Discovery automático y load balancing
- Observabilidad: Monitoreo integrado
- Resiliencia: Base para circuit breakers y retry policies
El proceso, aunque requiere cierta inversión inicial, proporciona una base sólida para el crecimiento y evolución del sistema a largo plazo.
Este post documenta la evolución completa de nuestro sistema Doggie Inn Pet Shop Management System hacia una arquitectura moderna con Spring Cloud.