Arquitectura de software: Monolito vs Microservicios con API Gateway y Service Mesh
Este artículo es una introducción completa a la arquitectura de software, incluyendo monolito vs microservicios con API Gateway, Service Mesh, contenedores y orquestación con ejemplos prácticos.
En resumen
La arquitectura monolítica es un único bloque de código cohesionado, mientras que los microservicios son servicios pequeños e independientes. El API Gateway coordina la comunicación y el Service Mesh gestiona las interacciones entre microservicios.
Descripción técnica compacta
Arquitectura de software es la estructura fundamental de un sistema de software, compuesta por componentes, sus relaciones y los principios que guían su diseño y evolución.
Patrones de arquitectura:
Arquitectura monolítica
- Concepto: Aplicación unificada en una base de código única
- Deployment: Despliegue único de toda la aplicación
- Comunicación: Llamadas directas a métodos dentro de la aplicación
- Ventajas: Desarrollo simple, despliegue directo, debugging sencillo
- Desventajas: Problemas de escalado, restricciones tecnológicas
Arquitectura de microservicios
- Concepto: Servicios pequeños y autónomos con responsabilidades específicas
- Deployment: Capacidad de despliegue independiente
- Comunicación: Llamadas a API basadas en red
- Ventajas: Escalabilidad, flexibilidad tecnológica, resiliencia
- Desventajas: Complejidad, latencia de red, complejidad distribuida
API Gateway
- Concepto: Punto de entrada centralizado para solicitudes de clientes
- Funciones: Enrutamiento, autenticación, limitación de tasa, balanceo de carga
- Ventajas: Gestión centralizada, seguridad, monitoreo
- Implementación: Kong, NGINX, AWS API Gateway
Service Mesh
- Concepto: Capa de infraestructura para la comunicación entre microservicios
- Funciones: Service Discovery, balanceo de carga, circuit breaking
- Implementación: Istio, Linkerd, Consul Connect
- Ventajas: Transparencia, observabilidad, seguridad
Puntos clave para evaluaciones
- Arquitectura de software: Estructura fundamental de sistemas de software
- Monolito: Aplicación unificada con base de código compartida
- Microservicios: Servicios pequeños e independientes con ciclo de vida propio
- API Gateway: Punto de entrada centralizado para la comunicación de clientes
- Service Mesh: Infraestructura para interacciones entre microservicios
- Contenedores: Virtualización a nivel de sistema operativo
- Orquestación: Gestión automatizada de contenedores
- Relevante para evaluaciones: Decisiones y patrones de arquitectura modernos
Componentes principales
- Decisión de arquitectura: Monolito vs microservicios
- Gestión de API: Gateway, enrutamiento, seguridad
- Service Discovery: Descubrimiento automático de servicios
- Balanceo de carga: Distribución de solicitudes
- Circuit Breaking: Protección contra fallos en cascada
- Containerización: Docker, Podman
- Orquestación: Kubernetes, Docker Swarm
- Monitoreo: Logging, métricas, tracing
Ejemplos prácticos
1. Arquitectura monolítica con Java Spring Boot
package com.example.monolith;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.*;
import java.util.List;
import java.util.Optional;
// Aplicación principal
@SpringBootApplication
public class MonolithApplication {
public static void main(String[] args) {
SpringApplication.run(MonolithApplication.class, args);
}
}
// Entidad User
@Entity
@Table(name = "users")
class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String password;
// Getters y Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
// Entidad Order
@Entity
@Table(name = "orders")
class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(nullable = false)
private String product;
@Column(nullable = false)
private Double amount;
@Column(nullable = false)
private String status;
// Getters y Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
public String getProduct() { return product; }
public void setProduct(String product) { this.product = product; }
public Double getAmount() { return amount; }
public void setAmount(Double amount) { this.amount = amount; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}
// Interfaces Repository
interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
boolean existsByUsername(String username);
}
interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByUserId(Long userId);
List<Order> findByStatus(String status);
}
// Servicios de lógica de negocio
@Service
class UserService {
@Autowired
private UserRepository userRepository;
public User createUser(User user) {
if (userRepository.existsByUsername(user.getUsername())) {
throw new IllegalArgumentException("Username already exists");
}
return userRepository.save(user);
}
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public Optional<User> getUserByUsername(String username) {
return userRepository.findByUsername(username);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
@Service
class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private UserService userService;
public Order createOrder(Long userId, String product, Double amount) {
Optional<User> userOpt = userService.getUserById(userId);
if (!userOpt.isPresent()) {
throw new IllegalArgumentException("User not found");
}
Order order = new Order();
order.setUser(userOpt.get());
order.setProduct(product);
order.setAmount(amount);
order.setStatus("PENDING");
return orderRepository.save(order);
}
public Optional<Order> getOrderById(Long id) {
return orderRepository.findById(id);
}
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findByUserId(userId);
}
public Order updateOrderStatus(Long id, String status) {
Optional<Order> orderOpt = orderRepository.findById(id);
if (!orderOpt.isPresent()) {
throw new IllegalArgumentException("Order not found");
}
Order order = orderOpt.get();
order.setStatus(status);
return orderRepository.save(order);
}
public void deleteOrder(Long id) {
orderRepository.deleteById(id);
}
}
// Controladores REST
@RestController
@RequestMapping("/api/users")
class UserController {
@Autowired
private UserService userService;
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id)
.orElseThrow(() -> new IllegalArgumentException("User not found"));
}
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}
@RestController
@RequestMapping("/api/orders")
class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public Order createOrder(@RequestBody CreateOrderRequest request) {
return orderService.createOrder(request.getUserId(), request.getProduct(), request.getAmount());
}
@GetMapping("/{id}")
public Order getOrder(@PathVariable Long id) {
return orderService.getOrderById(id)
.orElseThrow(() -> new IllegalArgumentException("Order not found"));
}
@GetMapping("/user/{userId}")
public List<Order> getOrdersByUser(@PathVariable Long userId) {
return orderService.getOrdersByUserId(userId);
}
@PutMapping("/{id}/status")
public Order updateOrderStatus(@PathVariable Long id, @RequestBody String status) {
return orderService.updateOrderStatus(id, status);
}
@DeleteMapping("/{id}")
public void deleteOrder(@PathVariable Long id) {
orderService.deleteOrder(id);
}
}
// DTOs
class CreateOrderRequest {
private Long userId;
private String product;
private Double amount;
// Getters y Setters
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getProduct() { return product; }
public void setProduct(String product) { this.product = product; }
public Double getAmount() { return amount; }
public void setAmount(Double amount) { this.amount = amount; }
}
2. Arquitectura de Microservicios con Spring Cloud
// User Microservice
package com.example.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.*;
import java.util.List;
import java.util.Optional;
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
// User Entity (simplificada)
@Entity
class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
// Getters y Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
@Service
class UserService {
@Autowired
private UserRepository userRepository;
public User createUser(User user) {
return userRepository.save(user);
}
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public Optional<User> getUserByUsername(String username) {
return userRepository.findByUsername(username);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
@RestController
@RequestMapping("/api/users")
class UserController {
@Autowired
private UserService userService;
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
}
// Order Microservice
package com.example.orderservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.*;
import java.util.List;
import java.util.Optional;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
// Feign Client para User Service
@FeignClient(name = "user-service")
interface UserServiceClient {
@GetMapping("/api/users/{id}")
User getUser(@PathVariable("id") Long id);
}
// Order Entity
@Entity
class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private String product;
private Double amount;
private String status;
// Getters y Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getProduct() { return product; }
public void setProduct(String product) { this.product = product; }
public Double getAmount() { return amount; }
public void setAmount(Double amount) { this.amount = amount; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}
interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByUserId(Long userId);
}
@Service
class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private UserServiceClient userServiceClient;
public Order createOrder(Order order) {
// Validar usuario mediante llamada a microservicio
try {
User user = userServiceClient.getUser(order.getUserId());
if (user == null) {
throw new RuntimeException("User not found");
}
} catch (Exception e) {
throw new RuntimeException("User service unavailable");
}
order.setStatus("PENDING");
return orderRepository.save(order);
}
public Optional<Order> getOrderById(Long id) {
return orderRepository.findById(id);
}
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findByUserId(userId);
}
public Order updateOrderStatus(Long id, String status) {
Optional<Order> orderOpt = orderRepository.findById(id);
if (!orderOpt.isPresent()) {
throw new RuntimeException("Order not found");
}
Order order = orderOpt.get();
order.setStatus(status);
return orderRepository.save(order);
}
}
@RestController
@RequestMapping("/api/orders")
class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public Order createOrder(@RequestBody Order order) {
return orderService.createOrder(order);
}
@GetMapping("/{id}")
public Order getOrder(@PathVariable Long id) {
return orderService.getOrderById(id)
.orElseThrow(() -> new RuntimeException("Order not found"));
}
@GetMapping("/user/{userId}")
public List<Order> getOrdersByUser(@PathVariable Long userId) {
return orderService.getOrdersByUserId(userId);
}
@PutMapping("/{id}/status")
public Order updateOrderStatus(@PathVariable Long id, @RequestBody String status) {
return orderService.updateOrderStatus(id, status);
}
}
3. API Gateway con Spring Cloud Gateway
package com.example.apigateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// User Service Routes
.route("user-service", r -> r.path("/api/users/**")
.uri("lb://user-service"))
// Order Service Routes
.route("order-service", r -> r.path("/api/orders/**")
.uri("lb://order-service"))
// Product Service Routes
.route("product-service", r -> r.path("/api/products/**")
.uri("lb://product-service"))
// Authentication Service Routes
.route("auth-service", r -> r.path("/api/auth/**")
.uri("lb://auth-service"))
.build();
}
}
// Authentication Filter
@Component
class AuthenticationFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerHttpRequest request, GatewayFilterChain chain) {
String path = request.getURI().getPath();
// Endpoints públicos sin autenticación
if (path.startsWith("/api/auth/login") ||
path.startsWith("/api/auth/register") ||
path.startsWith("/actuator")) {
return chain.filter(request);
}
// Extraer token del encabezado
String authHeader = request.getHeaders().getFirst("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return Mono.error(new RuntimeException("Missing or invalid token"));
}
String token = authHeader.substring(7);
// Validar token (simplificado)
if (!isValidToken(token)) {
return Mono.error(new RuntimeException("Invalid token"));
}
// Agregar información del usuario al request
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-User-ID", getUserIdFromToken(token))
.header("X-User-Role", getUserRoleFromToken(token))
.build();
return chain.filter(modifiedRequest);
}
private boolean isValidToken(String token) {
// Validación de token (simplificada)
return token != null && !token.isEmpty();
}
private String getUserIdFromToken(String token) {
// Extraer ID de usuario del token
return "123"; // Ejemplo
}
private String getUserRoleFromToken(String token) {
// Extraer rol de usuario del token
return "USER"; // Ejemplo
}
@Override
public int getOrder() {
return -100; // Alta prioridad
}
}
// Rate Limiting Filter
@Component
class RateLimitingFilter implements GlobalFilter, Ordered {
private final Map<String, Map<String, Integer>> rateLimitMap = new ConcurrentHashMap<>();
private static final int RATE_LIMIT = 100; // 100 solicitudes por minuto
private static final int TIME_WINDOW = 60; // Segundos
@Override
public Mono<Void> filter(ServerHttpRequest request, GatewayFilterChain chain) {
String clientId = getClientId(request);
String currentTimeWindow = getCurrentTimeWindow();
rateLimitMap.putIfAbsent(clientId, new ConcurrentHashMap<>());
Map<String, Integer> clientRequests = rateLimitMap.get(clientId);
// Eliminar ventanas de tiempo antiguas
clientRequests.entrySet().removeIf(entry ->
!entry.getKey().equals(currentTimeWindow));
// Incrementar contador actual
int currentCount = clientRequests.getOrDefault(currentTimeWindow, 0);
if (currentCount >= RATE_LIMIT) {
return Mono.error(new RuntimeException("Rate limit exceeded"));
}
clientRequests.put(currentTimeWindow, currentCount + 1);
return chain.filter(request);
}
private String getClientId(ServerHttpRequest request) {
// Extraer ID del cliente desde IP o token
return request.getRemoteAddress() != null ?
request.getRemoteAddress().getAddress().getHostAddress() : "unknown";
}
private String getCurrentTimeWindow() {
return String.valueOf(System.currentTimeMillis() / (TIME_WINDOW * 1000));
}
@Override
public int getOrder() {
return -99; // Después de autenticación
}
}
// Load Balancing Configuration
@Component
class LoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> userServiceLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
// Circuit Breaker Configuration
@Component
class CircuitBreakerConfig {
@Bean
public CircuitBreaker circuitBreaker() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofMillis(1000))
.slidingWindowSize(20)
.minimumNumberOfCalls(10)
.build();
return CircuitBreaker.of("myCircuitBreaker", config);
}
}
// API Gateway Controller para Health Checks
@RestController
@RequestMapping("/gateway")
class GatewayController {
@GetMapping("/health")
public Map<String, String> health() {
Map<String, String> status = new HashMap<>();
status.put("status", "UP");
status.put("service", "api-gateway");
status.put("timestamp", Instant.now().toString());
return status;
}
@GetMapping("/routes")
public Map<String, Object> getRoutes() {
Map<String, Object> routes = new HashMap<>();
routes.put("user-service", "/api/users/**");
routes.put("order-service", "/api/orders/**");
routes.put("product-service", "/api/products/**");
routes.put("auth-service", "/api/auth/**");
return routes;
}
}
4. Contenedores Docker para Microservicios
# Dockerfile para User Service
FROM openjdk:11-jre-slim
# Working Directory
WORKDIR /app
# Application JAR kopieren
COPY target/user-service-0.0.1-SNAPSHOT.jar app.jar
# Expose Port
EXPOSE 8081
# Environment Variables
ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENV SPRING_PROFILES_ACTIVE=docker
# Health Check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8081/actuator/health || exit 1
# Application starten
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
# Dockerfile für Order Service
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/order-service-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8082
ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENV SPRING_PROFILES_ACTIVE=docker
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8082/actuator/health || exit 1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
# Dockerfile für API Gateway
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/api-gateway-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENV SPRING_PROFILES_ACTIVE=docker
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/gateway/health || exit 1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
5. Orquestación con Kubernetes
# Namespace
apiVersion: v1
kind: Namespace
metadata:
name: microservices
---
# User Service Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: microservices
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: user-service:latest
ports:
- containerPort: 8081
env:
- name: SPRING_PROFILES_ACTIVE
value: "kubernetes"
- name: SPRING_DATASOURCE_URL
value: "jdbc:postgresql://postgres-service:5432/userdb"
- name: SPRING_DATASOURCE_USERNAME
value: "postgres"
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
livenessProbe:
httpGet:
path: /actuator/health
port: 8081
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health
port: 8081
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
---
# User Service Service
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: microservices
spec:
selector:
app: user-service
ports:
- protocol: TCP
port: 8081
targetPort: 8081
type: ClusterIP
---
# Order Service Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: microservices
spec:
replicas: 2
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: order-service:latest
ports:
- containerPort: 8082
env:
- name: SPRING_PROFILES_ACTIVE
value: "kubernetes"
- name: USER_SERVICE_URL
value: "http://user-service:8081"
livenessProbe:
httpGet:
path: /actuator/health
port: 8082
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health
port: 8082
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
---
# Order Service Service
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: microservices
spec:
selector:
app: order-service
ports:
- protocol: TCP
port: 8082
targetPort: 8082
type: ClusterIP
---
# API Gateway Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
namespace: microservices
spec:
replicas: 2
selector:
matchLabels:
app: api-gateway
template:
metadata:
labels:
app: api-gateway
spec:
containers:
- name: api-gateway
image: api-gateway:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "kubernetes"
livenessProbe:
httpGet:
path: /gateway/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /gateway/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
---
# API Gateway Service
apiVersion: v1
kind: Service
metadata:
name: api-gateway
namespace: microservices
spec:
selector:
app: api-gateway
ports:
- protocol: TCP
port: 8080
targetPort: 8080
type: LoadBalancer
---
# Horizontal Pod Autoscaler für User Service
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: user-service-hpa
namespace: microservices
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: user-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
---
# ConfigMap für Konfiguration
apiVersion: v1
kind: ConfigMap
metadata:
name: microservices-config
namespace: microservices
data:
application.yml: |
spring:
cloud:
kubernetes:
discovery:
enabled: true
config:
enabled: true
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
---
# Secret für sensible Daten
apiVersion: v1
kind: Secret
metadata:
name: db-secret
namespace: microservices
type: Opaque
data:
password: cGFzc3dvcmQxMjM= # Base64 encoded "password123"
username: cG9zdGdyZXM= # Base64 encoded "postgres"
6. Service Mesh con Istio
# Istio Gateway
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: microservices-gateway
namespace: microservices
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
# Virtual Service für API Gateway
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: api-gateway-vs
namespace: microservices
spec:
hosts:
- "*"
gateways:
- microservices-gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: api-gateway
port:
number: 8080
---
# Destination Rules für Load Balancing
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: user-service-dr
namespace: microservices
spec:
host: user-service
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 10
circuitBreaker:
consecutiveErrors: 3
interval: 30s
baseEjectionTime: 30s
---
# Service Entry für externe Services
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: external-api
namespace: microservices
spec:
hosts:
- external-api.example.com
ports:
- number: 443
name: https
protocol: HTTPS
resolution: DNS
---
# Authorization Policy
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-api-access
namespace: microservices
spec:
selector:
matchLabels:
app: api-gateway
rules:
- from:
- source:
principals: ["cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]
- to:
- operation:
methods: ["GET", "POST", "PUT", "DELETE"]
---
# Request Authentication
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: jwt-auth
namespace: microservices
spec:
selector:
matchLabels:
app: api-gateway
jwtRules:
- issuer: "https://auth.example.com"
jwksUri: "https://auth.example.com/.well-known/jwks.json"
forwardOriginalToken: true
Comparativa de Arquitectura
Monolito vs Microservicios
| Criterio | Monolito | Microservicios |
|---|---|---|
| Complejidad | Baja | Alta |
| Deployment | Simple | Complejo |
| Escalado | Vertical | Horizontal |
| Tecnología | Uniforme | Flexible |
| Aislamiento de fallos | Deficiente | Bueno |
| Estructura de equipos | Centralizada | Distribuida |
| Desarrollo | Rápido | Más lento |
Estrategias de Deployment
| Estrategia | Descripción | Ventajas | Desventajas |
|---|---|---|---|
| Big Bang | Todo de una vez | Simple | Alto riesgo |
| Blue-Green | Sistemas paralelos | Zero-Downtime | Recursos |
| Canary | Gradual | Seguro | Complejo |
| Rolling | Gradual | Eficiente | Lento |
Tecnologías de Contenedores
Docker vs Podman
| Característica | Docker | Podman |
|---|---|---|
| Daemon | Sí | No |
| Rootless | Limitado | Completo |
| Pods | No | Sí |
| Dockerfile | Sí | Sí |
| Compose | Sí | Limitado |
Registros de Contenedores
| Registro | Características | Precio |
|---|---|---|
| Docker Hub | Público/Privado | Freemium |
| GitHub Packages | Integración | Gratis |
| AWS ECR | Nativo en la nube | Pago por uso |
| Google GCR | Nativo en la nube | Pago por uso |
Plataformas de Orquestación
Kubernetes vs Docker Swarm
| Característica | Kubernetes | Docker Swarm |
|---|---|---|
| Complejidad | Alta | Baja |
| Escalado | Automático | Manual |
| Service Mesh | Sí | No |
| Curva de aprendizaje | Pronunciada | Suave |
| Comunidad | Grande | Mediana |
Implementaciones de Service Mesh
Istio vs Linkerd
| Característica | Istio | Linkerd |
|---|---|---|
| Complejidad | Alta | Baja |
| Características | Exhaustivas | Enfocadas |
| Rendimiento | Bueno | Muy bueno |
| Recursos | Altos | Bajos |
| Integración | Exhaustiva | Simple |
Características de API Gateway
Funciones Básicas
- Routing: Enrutamiento de solicitudes a los servicios correctos
- Authentication: JWT, OAuth2, claves API
- Authorization: Control de acceso basado en roles
- Rate Limiting: Límites de solicitudes por cliente
- Load Balancing: Distribución de carga
- Caching: Almacenamiento en caché de respuestas
- Logging: Registro de solicitudes y respuestas
Características Avanzadas
- Circuit Breaking: Protección contra fallos en cascada
- Retry Logic: Reintentos automáticos
- Request/Response Transformation: Transformación de datos
- API Versioning: Gestión de versiones
- Analytics: Estadísticas de uso
Monitoreo y Observabilidad
Tres Pilares de la Observabilidad
- Metrics: Datos cuantitativos (Prometheus, Grafana)
- Logging: Datos basados en eventos (ELK Stack, Fluentd)
- Tracing: Rastreo de solicitudes (Jaeger, Zipkin)
Herramientas en el Contexto de Microservicios
# Prometheus Monitoring
apiVersion: v1
kind: ServiceMonitor
metadata:
name: user-service-monitor
namespace: microservices
spec:
selector:
matchLabels:
app: user-service
endpoints:
- port: http
path: /actuator/prometheus
interval: 30s
Mejores Prácticas
Diseño de Microservicios
- Single Responsibility: Un servicio, una responsabilidad
- Database per Service: Base de datos propia por servicio
- Async Communication: Comunicación basada en eventos
- Circuit Breaking: Protección contra fallos en cascada
- Health Checks: Monitoreo de la salud del servicio
Mejores Prácticas con Contenedores
# Best Practice Dockerfile
FROM alpine:latest
# Non-root User
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
USER appuser
# Minimal Image
COPY --from=builder /app/target/app.jar /app/app.jar
WORKDIR /app
EXPOSE 8080
# Health Check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]
Ventajas e Inconvenientes
Ventajas de los Microservicios
- Escalabilidad: Escalado independiente por servicio
- Flexibilidad: Diferentes tecnologías posibles
- Resiliencia: El fallo de un servicio no afecta a los otros
- Autonomía de equipos: Equipos de desarrollo independientes
- Despliegues más rápidos: Despliegues pequeños y frecuentes
Inconvenientes de los Microservicios
- Complejidad: Los sistemas distribuidos son complejos
- Latencia de red: Comunicación a través de la red
- Consistencia de datos: Transacciones distribuidas
- Monitoreo: Supervisión más compleja
- Debugging: Depuración más difícil
Preguntas Frecuentes de Examen
-
¿Cuáles son las diferencias principales entre monolito y microservicios? Un monolito es una unidad única, mientras que los microservicios son servicios pequeños e independientes, cada uno con su propia responsabilidad.
-
¡Explique el papel de un API Gateway! El API Gateway es el punto de entrada central para las solicitudes de clientes, proporcionando enrutamiento, autenticación y limitación de velocidad.
-
¿Cuándo se usan contenedores y cuándo orquestación? Los contenedores se usan para aislamiento y portabilidad, mientras que la orquestación se utiliza para la gestión automatizada de muchos contenedores.
-
¿Qué es Service Mesh y por qué se necesita? Service Mesh es una capa de infraestructura para la comunicación entre microservicios, proporcionando descubrimiento de servicios y seguridad.
Recursos Principales
- https://microservices.io/
- https://kubernetes.io/
- https://istio.io/
- https://spring.io/projects/spring-cloud



