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.xmlencontrarás eldependencyManagementconfigurado 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.propertieses necesario agregar la dependenciaspring-cloud-starter-bootstrapque se incluye en las dependencias mostradas arriba. Sin esta dependencia, el archivobootstrap.propertiesserá 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.
