Patrones de Diseño - Aplicaciones Avanzadas
Aprende aplicaciones avanzadas de patrones de diseño en arquitecturas empresariales como microservicios, CQRS, event sourcing y más con ejemplos prácticos en C++.
¡Domina las aplicaciones avanzadas de patrones de diseño! En este tutorial especializado te guiaré paso a paso para que aprendas cómo aplicar patrones en arquitecturas empresariales modernas, incluyendo microservicios, CQRS, event sourcing, circuit breaker y API gateway, con ejemplos prácticos y casos de uso reales en C++.
Objetivo: Aprender aplicaciones avanzadas de patrones de diseño en arquitecturas empresariales, sus implementaciones en C++, ventajas, desventajas y cuándo aplicarlos en proyectos de gran escala.
¿Por qué C++ para patrones avanzados?
C++ es una excelente elección para implementar patrones de diseño avanzados en aplicaciones empresariales por varias razones fundamentales:
Rendimiento y Control de Recursos
- Gestión de memoria precisa: Control total sobre la creación y destrucción de objetos
- Bajo overhead: Sin garbage collector, ideal para sistemas de alta performance
- Multi-threading nativo: Soporte robusto para concurrencia con std::thread y async/await
Escalabilidad y Robustez
- Compilación estricta: Errores detectados en tiempo de compilación
- Abstracciones de bajo nivel: Control sobre optimizaciones del compilador
- Estabilidad: Código que funciona consistentemente en producción
Ecosistema Maduro
- Bibliotecas estándar robustas: STL, Boost, ASIO para networking
- Herramientas de testing: Google Test, Catch2
- Profiling avanzado: Valgrind, gprof, perf
Aplicación en la Industria
C++ es ampliamente utilizado en:
- Sistemas de alta frecuencia (trading, telecomunicaciones)
- Motores de juegos (Unreal Engine, Unity C++)
- Sistemas embebidos y de tiempo real
- Aplicaciones de alto rendimiento (bases de datos, servidores web)
Índice
- Paso 1: Introducción a patrones avanzados
- Paso 2: Circuit Breaker - Tolerancia a fallos
- Paso 3: API Gateway - Puerta de enlace
- Paso 4: Arquitectura de microservicios
- Paso 5: CQRS - Separación de lecturas y escrituras
- Paso 6: Event Sourcing - Fuente de eventos
- Paso 7: Saga Pattern - Transacciones distribuidas
- Paso 8: Caso de estudio - Sistema de e-commerce
- Paso 9: Consideraciones de rendimiento y escalabilidad
- Conclusión
- 💡 Tip Importante
Paso 1: Introducción a patrones avanzados
Los patrones avanzados de diseño se enfocan en resolver problemas de arquitectura a gran escala, especialmente en sistemas distribuidos y aplicaciones empresariales complejas.
¿Por qué son importantes?
- Escalabilidad: Permiten manejar crecimiento exponencial
- Resiliencia: Sistemas que se recuperan de fallos automáticamente
- Mantenibilidad: Arquitecturas que evolucionan sin romperse
- Performance: Optimización para cargas de trabajo intensivas
Patrones avanzados comunes
| Patrón | Propósito | Complejidad |
|---|---|---|
| Circuit Breaker | Prevenir fallos en cascada | Media |
| API Gateway | Punto de entrada único | Alta |
| Microservicios | Arquitectura distribuida | Alta |
| CQRS | Separar lecturas/escrituras | Alta |
| Event Sourcing | Almacenar eventos en lugar de estado | Alta |
| Saga Pattern | Transacciones distribuidas | Alta |
Paso 2: Circuit Breaker - Tolerancia a fallos
El patrón Circuit Breaker previene fallos en cascada en sistemas distribuidos, detectando fallos y evitando llamadas repetidas a servicios que están fallando.
Implementación básica
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <functional>
#include <stdexcept>
#include <random>
#include <memory>
enum class EstadoCircuitBreaker {
CERRADO, // Funcionamiento normal
ABIERTO, // Circuito abierto, no permite llamadas
MEDIO_ABIERTO // Probando si el servicio se recuperó
};
class CircuitBreaker {
public:
CircuitBreaker(const std::string& nombre, int umbral_fallos = 3, int tiempo_timeout_segundos = 5)
: nombre_(nombre), umbral_fallos_(umbral_fallos), tiempo_timeout_segundos_(tiempo_timeout_segundos),
estado_(EstadoCircuitBreaker::CERRADO), contador_fallos_(0) {}
template<typename Func, typename... Args>
auto ejecutar(Func&& funcion, Args&&... args) -> decltype(funcion(args...)) {
auto tiempo_actual = std::chrono::steady_clock::now();
if (estado_ == EstadoCircuitBreaker::ABIERTO) {
// Verificar si es tiempo de intentar recuperación
auto tiempo_desde_fallo = tiempo_actual - ultimo_fallo_tiempo_;
if (tiempo_desde_fallo > std::chrono::seconds(tiempo_timeout_segundos_)) {
estado_ = EstadoCircuitBreaker::MEDIO_ABIERTO;
std::cout << "🔄 " << nombre_ << ": Cambiando a estado MEDIO_ABIERTO" << std::endl;
} else {
throw std::runtime_error("Circuito " + nombre_ + " ABIERTO - Servicio no disponible");
}
}
try {
auto resultado = std::forward<Func>(funcion)(std::forward<Args>(args)...);
// Si la llamada es exitosa, resetear contadores
if (estado_ == EstadoCircuitBreaker::MEDIO_ABIERTO) {
estado_ = EstadoCircuitBreaker::CERRADO;
contador_fallos_ = 0;
std::cout << "✅ " << nombre_ << ": Servicio recuperado, circuito CERRADO" << std::endl;
}
ultimo_exito_tiempo_ = tiempo_actual;
return resultado;
} catch (const std::exception& e) {
contador_fallos_++;
ultimo_fallo_tiempo_ = tiempo_actual;
std::cout << "❌ " << nombre_ << ": Fallo #" << contador_fallos_ << " - " << e.what() << std::endl;
if (estado_ == EstadoCircuitBreaker::CERRADO && contador_fallos_ >= umbral_fallos_) {
estado_ = EstadoCircuitBreaker::ABIERTO;
std::cout << "🚨 " << nombre_ << ": Umbral de fallos alcanzado, circuito ABIERTO" << std::endl;
} else if (estado_ == EstadoCircuitBreaker::MEDIO_ABIERTO) {
estado_ = EstadoCircuitBreaker::ABIERTO;
std::cout << "🚨 " << nombre_ << ": Fallo en prueba de recuperación, circuito ABIERTO" << std::endl;
}
throw;
}
}
private:
std::string nombre_;
int umbral_fallos_;
int tiempo_timeout_segundos_;
EstadoCircuitBreaker estado_;
int contador_fallos_;
std::chrono::steady_clock::time_point ultimo_fallo_tiempo_;
std::chrono::steady_clock::time_point ultimo_exito_tiempo_;
};
// Servicio externo simulado con fallos
class ServicioExterno {
public:
ServicioExterno(double tasa_fallo = 0.3) : tasa_fallo_(tasa_fallo), llamadas_(0) {}
std::string llamar() {
llamadas_++;
// Simular fallo aleatorio
static std::random_device rd;
static std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0.0, 1.0);
if (dis(gen) < tasa_fallo_) {
throw std::runtime_error("Servicio externo no disponible");
}
return "Respuesta del servicio #" + std::to_string(llamadas_);
}
private:
double tasa_fallo_;
int llamadas_;
};
// Cliente que usa Circuit Breaker
class ClienteServicio {
public:
ClienteServicio(std::shared_ptr<ServicioExterno> servicio)
: servicio_(servicio) {
circuit_breaker_ = std::make_unique<CircuitBreaker>(
"servicio_externo", 2, 3
);
}
std::string hacer_llamada() {
return circuit_breaker_->ejecutar([this]() { return servicio_->llamar(); });
}
private:
std::shared_ptr<ServicioExterno> servicio_;
std::unique_ptr<CircuitBreaker> circuit_breaker_;
};
// Demo
void demo_circuit_breaker() {
auto servicio = std::make_shared<ServicioExterno>(0.7); // 70% de fallos
ClienteServicio cliente(servicio);
for (int i = 0; i < 10; ++i) {
try {
std::string resultado = cliente.hacer_llamada();
std::cout << "✅ Llamada " << (i+1) << ": " << resultado << std::endl;
} catch (const std::exception& e) {
std::cout << "❌ Llamada " << (i+1) << ": " << e.what() << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
int main() {
demo_circuit_breaker();
return 0;
}
Paso 3: API Gateway - Puerta de enlace
El patrón API Gateway actúa como punto de entrada único para todas las solicitudes, manejando enrutamiento, composición y transformación.
Implementación básica
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <chrono>
#include <thread>
#include <stdexcept>
#include <algorithm>
#include <sstream>
// Estructuras para Request/Response
struct Request {
std::string method;
std::string path;
std::map<std::string, std::string> headers;
std::string body;
std::map<std::string, std::string> query_params;
};
struct Response {
int status_code;
std::map<std::string, std::string> headers;
std::string body;
Response(int code, const std::string& body_content = "")
: status_code(code), body(body_content) {}
};
// Clase base abstracta para Gateway
class Gateway {
public:
virtual ~Gateway() = default;
virtual Response manejar_request(const Request& request) = 0;
};
// Gateway simple
class GatewaySimple : public Gateway {
public:
GatewaySimple() = default;
void agregar_ruta(const std::string& path, const std::string& destino) {
rutas_[path] = destino;
}
void agregar_middleware(std::unique_ptr<class Middleware> middleware) {
middlewares_.push_back(std::move(middleware));
}
Response manejar_request(const Request& request) override {
Request processed_request = request;
// Aplicar middlewares pre-procesamiento
for (const auto& middleware : middlewares_) {
processed_request = middleware->pre_procesar(processed_request);
}
// Enrutar
std::string destino = encontrar_destino(request.path);
if (destino.empty()) {
return Response(404, "{\"error\": \"Ruta no encontrada\"}");
}
Response response(200);
// Procesar request
try {
if (destino.find("http") == 0) {
// Llamada HTTP
response = llamar_servicio_externo(processed_request, destino);
} else {
// Lógica interna
response = procesar_interno(processed_request, destino);
}
} catch (const std::exception& e) {
response = Response(500, "{\"error\": \"" + std::string(e.what()) + "\"}");
}
// Aplicar middlewares post-procesamiento
for (auto it = middlewares_.rbegin(); it != middlewares_.rend(); ++it) {
response = (*it)->post_procesar(response);
}
return response;
}
private:
std::map<std::string, std::string> rutas_;
std::vector<std::unique_ptr<class Middleware>> middlewares_;
std::string encontrar_destino(const std::string& path) {
for (const auto& [ruta, destino] : rutas_) {
if (path.find(ruta) == 0) {
return destino;
}
}
return "";
}
Response llamar_servicio_externo(const Request& request, const std::string& destino) {
std::string url = destino + request.path;
// Simular llamada HTTP
std::cout << "🔄 Llamando a " << url << " con método " << request.method << std::endl;
// En un caso real usaríamos una librería como curl o boost::asio
if (request.method == "GET") {
return Response(200, "{\"data\": \"Respuesta de " + destino + "\"}");
} else {
return Response(200, "{\"message\": \"Operación completada\"}");
}
}
Response procesar_interno(const Request& request, const std::string& destino) {
if (destino == "auth") {
return procesar_auth(request);
} else if (destino == "metrics") {
return procesar_metrics(request);
} else {
return Response(404, "{\"error\": \"Destino interno no encontrado\"}");
}
}
Response procesar_auth(const Request& request) {
auto it = request.headers.find("Authorization");
std::string token = (it != request.headers.end()) ? it->second : "";
if (token == "Bearer valid-token") {
return Response(200, "{\"authenticated\": true, \"user\": \"admin\"}");
} else {
return Response(401, "{\"error\": \"No autorizado\"}");
}
}
Response procesar_metrics(const Request& request) {
return Response(200, "{\"requests_handled\": 1000, \"uptime\": \"24h\", \"status\": \"healthy\"}");
}
};
// Clase base abstracta para Middleware
class Middleware {
public:
virtual ~Middleware() = default;
virtual Request pre_procesar(const Request& request) { return request; }
virtual Response post_procesar(const Response& response) { return response; }
};
// Middleware de logging
class LoggingMiddleware : public Middleware {
public:
Request pre_procesar(const Request& request) override {
std::cout << "📝 Request: " << request.method << " " << request.path << std::endl;
return request;
}
Response post_procesar(const Response& response) override {
std::cout << "📝 Response: " << response.status_code << std::endl;
return response;
}
};
// Middleware de autenticación
class AuthMiddleware : public Middleware {
public:
Request pre_procesar(const Request& request) override {
// Rutas que no requieren autenticación
std::vector<std::string> rutas_publicas = {"/auth", "/health", "/metrics"};
bool es_ruta_publica = std::any_of(rutas_publicas.begin(), rutas_publicas.end(),
[&request](const std::string& ruta) {
return request.path.find(ruta) == 0;
});
if (es_ruta_publica) {
return request;
}
// Verificar token
auto it = request.headers.find("Authorization");
std::string token = (it != request.headers.end()) ? it->second : "";
if (token != "Bearer valid-token") {
throw std::runtime_error("No autorizado");
}
return request;
}
};
// Middleware de rate limiting
class RateLimitingMiddleware : public Middleware {
public:
RateLimitingMiddleware(int limite_por_segundo = 10)
: limite_(limite_por_segundo), contador_(0) {}
Request pre_procesar(const Request& request) override {
auto ahora = std::chrono::steady_clock::now();
auto tiempo_desde_reset = ahora - ultimo_reset_;
if (tiempo_desde_reset > std::chrono::seconds(1)) {
contador_ = 0;
ultimo_reset_ = ahora;
}
if (contador_ >= limite_) {
throw std::runtime_error("Límite de tasa excedido");
}
contador_++;
return request;
}
private:
int limite_;
int contador_;
std::chrono::steady_clock::time_point ultimo_reset_;
};
// Demo
void demo_api_gateway() {
auto gateway = std::make_unique<GatewaySimple>();
// Configurar rutas
gateway->agregar_ruta("/api/users", "http://users-service:3000");
gateway->agregar_ruta("/api/products", "http://products-service:3001");
gateway->agregar_ruta("/auth", "auth");
gateway->agregar_ruta("/metrics", "metrics");
// Agregar middlewares
gateway->agregar_middleware(std::make_unique<LoggingMiddleware>());
gateway->agregar_middleware(std::make_unique<AuthMiddleware>());
gateway->agregar_middleware(std::make_unique<RateLimitingMiddleware>(5));
// Simular requests
std::vector<Request> requests = {
{"GET", "/auth", {{"Authorization", "Bearer valid-token"}}, "", {}},
{"GET", "/api/users", {{"Authorization", "Bearer valid-token"}}, "", {}},
{"GET", "/metrics", {{"Authorization", "Bearer valid-token"}}, "", {}},
{"GET", "/api/products", {{"Authorization", "invalid-token"}}, "", {}}
};
for (size_t i = 0; i < requests.size(); ++i) {
std::cout << "\n--- Request " << (i+1) << " ---" << std::endl;
try {
Response response = gateway->manejar_request(requests[i]);
std::cout << "✅ Status: " << response.status_code << std::endl;
std::cout << "Body: " << response.body << std::endl;
} catch (const std::exception& e) {
std::cout << "❌ Error: " << e.what() << std::endl;
}
}
}
int main() {
demo_api_gateway();
return 0;
}
Paso 4: Arquitectura de microservicios
La arquitectura de microservicios estructura una aplicación como una colección de servicios débilmente acoplados.
Ejemplo de microservicios
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <chrono>
#include <stdexcept>
#include <algorithm>
// Usuario
struct Usuario {
int id;
std::string nombre;
std::string email;
std::chrono::steady_clock::time_point fecha_creacion;
Usuario(int id, const std::string& nombre, const std::string& email)
: id(id), nombre(nombre), email(email),
fecha_creacion(std::chrono::steady_clock::now()) {}
};
// Producto
struct Producto {
int id;
std::string nombre;
double precio;
int stock;
Producto(int id, const std::string& nombre, double precio)
: id(id), nombre(nombre), precio(precio), stock(0) {}
};
// Pedido
struct Pedido {
int id;
int usuario_id;
std::vector<std::map<std::string, int>> items; // {producto_id, cantidad}
double total;
std::string estado;
std::chrono::steady_clock::time_point fecha_creacion;
Pedido(int id, int usuario_id)
: id(id), usuario_id(usuario_id), total(0.0), estado("creado"),
fecha_creacion(std::chrono::steady_clock::now()) {}
};
// Servicio de usuarios
class ServicioUsuarios {
public:
ServicioUsuarios() = default;
Usuario crear_usuario(const std::string& nombre, const std::string& email) {
int usuario_id = usuarios_.size() + 1;
Usuario usuario(usuario_id, nombre, email);
usuarios_[usuario_id] = usuario;
return usuario;
}
std::shared_ptr<Usuario> obtener_usuario(int id) {
auto it = usuarios_.find(id);
if (it != usuarios_.end()) {
return std::make_shared<Usuario>(it->second);
}
return nullptr;
}
std::vector<Usuario> listar_usuarios() {
std::vector<Usuario> resultado;
for (const auto& [id, usuario] : usuarios_) {
resultado.push_back(usuario);
}
return resultado;
}
private:
std::map<int, Usuario> usuarios_;
};
// Servicio de productos
class ServicioProductos {
public:
ServicioProductos() = default;
Producto crear_producto(const std::string& nombre, double precio) {
int producto_id = productos_.size() + 1;
Producto producto(producto_id, nombre, precio);
productos_[producto_id] = producto;
return producto;
}
bool actualizar_stock(int producto_id, int cantidad) {
auto it = productos_.find(producto_id);
if (it != productos_.end()) {
it->second.stock += cantidad;
return true;
}
return false;
}
std::shared_ptr<Producto> obtener_producto(int id) {
auto it = productos_.find(id);
if (it != productos_.end()) {
return std::make_shared<Producto>(it->second);
}
return nullptr;
}
std::vector<Producto> listar_productos() {
std::vector<Producto> resultado;
for (const auto& [id, producto] : productos_) {
resultado.push_back(producto);
}
return resultado;
}
private:
std::map<int, Producto> productos_;
};
// Servicio de pedidos (orquestador)
class ServicioPedidos {
public:
ServicioPedidos(std::shared_ptr<ServicioUsuarios> servicio_usuarios,
std::shared_ptr<ServicioProductos> servicio_productos)
: servicio_usuarios_(servicio_usuarios),
servicio_productos_(servicio_productos) {}
Pedido crear_pedido(int usuario_id, const std::vector<std::map<std::string, int>>& items) {
// Verificar usuario
auto usuario = servicio_usuarios_->obtener_usuario(usuario_id);
if (!usuario) {
throw std::runtime_error("Usuario no encontrado");
}
// Verificar productos y stock
double total = 0.0;
for (const auto& item : items) {
int producto_id = item.at("producto_id");
int cantidad = item.at("cantidad");
auto producto = servicio_productos_->obtener_producto(producto_id);
if (!producto) {
throw std::runtime_error("Producto " + std::to_string(producto_id) + " no encontrado");
}
if (producto->stock < cantidad) {
throw std::runtime_error("Stock insuficiente para producto " + producto->nombre);
}
total += producto->precio * cantidad;
}
// Crear pedido
int pedido_id = pedidos_.size() + 1;
Pedido pedido(pedido_id, usuario_id);
pedido.items = items;
pedido.total = total;
pedidos_[pedido_id] = pedido;
// Actualizar stock (en un caso real sería transaccional)
for (const auto& item : items) {
servicio_productos_->actualizar_stock(item.at("producto_id"), -item.at("cantidad"));
}
return pedido;
}
std::vector<Pedido> listar_pedidos() {
std::vector<Pedido> resultado;
for (const auto& [id, pedido] : pedidos_) {
resultado.push_back(pedido);
}
return resultado;
}
private:
std::shared_ptr<ServicioUsuarios> servicio_usuarios_;
std::shared_ptr<ServicioProductos> servicio_productos_;
std::map<int, Pedido> pedidos_;
};
// Service Discovery
class ServiceDiscovery {
public:
ServiceDiscovery() = default;
void registrar_servicio(const std::string& nombre, std::shared_ptr<void> instancia) {
servicios_[nombre] = instancia;
}
template<typename T>
std::shared_ptr<T> obtener_servicio(const std::string& nombre) {
auto it = servicios_.find(nombre);
if (it != servicios_.end()) {
return std::static_pointer_cast<T>(it->second);
}
return nullptr;
}
private:
std::map<std::string, std::shared_ptr<void>> servicios_;
};
// Configuración del sistema de microservicios
std::unique_ptr<ServiceDiscovery> configurar_microservicios() {
auto discovery = std::make_unique<ServiceDiscovery>();
// Crear servicios
auto servicio_usuarios = std::make_shared<ServicioUsuarios>();
auto servicio_productos = std::make_shared<ServicioProductos>();
auto servicio_pedidos = std::make_shared<ServicioPedidos>(servicio_usuarios, servicio_productos);
// Registrar servicios
discovery->registrar_servicio<ServicioUsuarios>("usuarios", servicio_usuarios);
discovery->registrar_servicio<ServicioProductos>("productos", servicio_productos);
discovery->registrar_servicio<ServicioPedidos>("pedidos", servicio_pedidos);
return discovery;
}
// Demo
void demo_microservicios() {
auto discovery = configurar_microservicios();
// Obtener servicios
auto usuarios = discovery->obtener_servicio<ServicioUsuarios>("usuarios");
auto productos = discovery->obtener_servicio<ServicioProductos>("productos");
auto pedidos = discovery->obtener_servicio<ServicioPedidos>("pedidos");
if (!usuarios || !productos || !pedidos) {
std::cout << "Error: No se pudieron obtener los servicios" << std::endl;
return;
}
// Crear datos de prueba
Usuario usuario = usuarios->crear_usuario("Ana García", "[email protected]");
std::cout << "Usuario creado: " << usuario.nombre << std::endl;
Producto producto1 = productos->crear_producto("Laptop", 1200.00);
Producto producto2 = productos->crear_producto("Mouse", 45.99);
productos->actualizar_stock(producto1.id, 10);
productos->actualizar_stock(producto2.id, 20);
auto productos_lista = productos->listar_productos();
std::cout << "Productos creados: " << productos_lista.size() << std::endl;
// Crear pedido
std::vector<std::map<std::string, int>> items = {
{{"producto_id", producto1.id}, {"cantidad", 1}},
{{"producto_id", producto2.id}, {"cantidad", 2}}
};
try {
Pedido pedido = pedidos->crear_pedido(usuario.id, items);
std::cout << "Pedido creado: $" << pedido.total << std::endl;
std::cout << "Estado: " << pedido.estado << std::endl;
} catch (const std::exception& e) {
std::cout << "Error al crear pedido: " << e.what() << std::endl;
}
// Mostrar estado final
auto usuarios_lista = usuarios->listar_usuarios();
auto productos_lista_final = productos->listar_productos();
auto pedidos_lista = pedidos->listar_pedidos();
std::cout << "Usuarios: " << usuarios_lista.size() << std::endl;
std::cout << "Productos: " << productos_lista_final.size() << std::endl;
std::cout << "Pedidos: " << pedidos_lista.size() << std::endl;
}
int main() {
demo_microservicios();
return 0;
}
Paso 5: CQRS - Separación de lecturas y escrituras
El patrón CQRS (Command Query Responsibility Segregation) separa las operaciones de lectura y escritura en modelos diferentes.
Implementación básica
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <stdexcept>
#include <algorithm>
#include <chrono>
// Commands - Escrituras
struct CrearUsuarioCommand {
std::string nombre;
std::string email;
std::string direccion;
CrearUsuarioCommand(const std::string& n, const std::string& e, const std::string& d)
: nombre(n), email(e), direccion(d) {}
};
struct CrearProductoCommand {
std::string nombre;
double precio;
std::string categoria;
int stock;
CrearProductoCommand(const std::string& n, double p, const std::string& c, int s = 0)
: nombre(n), precio(p), categoria(c), stock(s) {}
};
// Queries - Lecturas
struct ObtenerUsuarioQuery {
std::string usuario_id;
ObtenerUsuarioQuery(const std::string& id) : usuario_id(id) {}
};
struct ListarProductosQuery {
std::string categoria;
ListarProductosQuery(const std::string& cat = "") : categoria(cat) {}
};
// Modelos de datos
struct Usuario {
std::string id;
std::string nombre;
std::string email;
std::string direccion;
std::chrono::steady_clock::time_point fecha_creacion;
Usuario(const std::string& id, const std::string& n, const std::string& e, const std::string& d)
: id(id), nombre(n), email(e), direccion(d),
fecha_creacion(std::chrono::steady_clock::now()) {}
};
struct Producto {
std::string id;
std::string nombre;
double precio;
std::string categoria;
int stock;
Producto(const std::string& id, const std::string& n, double p, const std::string& c, int s = 0)
: id(id), nombre(n), precio(p), categoria(c), stock(s) {}
};
// Command Handler base
class CommandHandler {
public:
virtual ~CommandHandler() = default;
virtual void manejar(const void* command) = 0;
};
// Query Handler base
class QueryHandler {
public:
virtual ~QueryHandler() = default;
virtual void* manejar(const void* query) = 0;
};
// Repositorios
class RepositorioUsuarios {
public:
RepositorioUsuarios() = default;
void guardar(const Usuario& usuario) {
usuarios_[usuario.id] = usuario;
}
std::shared_ptr<Usuario> obtener_por_id(const std::string& id) {
auto it = usuarios_.find(id);
if (it != usuarios_.end()) {
return std::make_shared<Usuario>(it->second);
}
return nullptr;
}
std::vector<Usuario> listar_todos() {
std::vector<Usuario> resultado;
for (const auto& [id, usuario] : usuarios_) {
resultado.push_back(usuario);
}
return resultado;
}
private:
std::map<std::string, Usuario> usuarios_;
};
class RepositorioProductos {
public:
RepositorioProductos() = default;
void guardar(const Producto& producto) {
productos_[producto.id] = producto;
}
std::shared_ptr<Producto> obtener_por_id(const std::string& id) {
auto it = productos_.find(id);
if (it != productos_.end()) {
return std::make_shared<Producto>(it->second);
}
return nullptr;
}
std::vector<Producto> listar_todos() {
std::vector<Producto> resultado;
for (const auto& [id, producto] : productos_) {
resultado.push_back(producto);
}
return resultado;
}
private:
std::map<std::string, Producto> productos_;
};
// Command Handlers
class CrearUsuarioHandler : public CommandHandler {
public:
CrearUsuarioHandler(std::shared_ptr<RepositorioUsuarios> repo) : repositorio_(repo) {}
void manejar(const void* command) override {
const auto* cmd = static_cast<const CrearUsuarioCommand*>(command);
// Validar que el email no exista
auto usuarios = repositorio_->listar_todos();
for (const auto& usuario : usuarios) {
if (usuario.email == cmd->email) {
throw std::runtime_error("Email ya registrado");
}
}
std::string usuario_id = generar_id();
Usuario usuario(usuario_id, cmd->nombre, cmd->email, cmd->direccion);
repositorio_->guardar(usuario);
// Guardar resultado para acceso posterior
resultado_ = std::make_shared<Usuario>(usuario);
}
std::shared_ptr<Usuario> obtener_resultado() { return resultado_; }
private:
std::shared_ptr<RepositorioUsuarios> repositorio_;
std::shared_ptr<Usuario> resultado_;
std::string generar_id() {
static int contador = 0;
return "user_" + std::to_string(++contador);
}
};
class CrearProductoHandler : public CommandHandler {
public:
CrearProductoHandler(std::shared_ptr<RepositorioProductos> repo) : repositorio_(repo) {}
void manejar(const void* command) override {
const auto* cmd = static_cast<const CrearProductoCommand*>(command);
std::string producto_id = generar_id();
Producto producto(producto_id, cmd->nombre, cmd->precio, cmd->categoria, cmd->stock);
repositorio_->guardar(producto);
resultado_ = std::make_shared<Producto>(producto);
}
std::shared_ptr<Producto> obtener_resultado() { return resultado_; }
private:
std::shared_ptr<RepositorioProductos> repositorio_;
std::shared_ptr<Producto> resultado_;
std::string generar_id() {
static int contador = 0;
return "prod_" + std::to_string(++contador);
}
};
// Query Handlers
class ObtenerUsuarioHandler : public QueryHandler {
public:
ObtenerUsuarioHandler(std::shared_ptr<RepositorioUsuarios> repo) : repositorio_(repo) {}
void* manejar(const void* query) override {
const auto* q = static_cast<const ObtenerUsuarioQuery*>(query);
return repositorio_->obtener_por_id(q->usuario_id).release();
}
private:
std::shared_ptr<RepositorioUsuarios> repositorio_;
};
class ListarProductosHandler : public QueryHandler {
public:
ListarProductosHandler(std::shared_ptr<RepositorioProductos> repo) : repositorio_(repo) {}
void* manejar(const void* query) override {
const auto* q = static_cast<const ListarProductosQuery*>(query);
auto productos = repositorio_->listar_todos();
if (!q->categoria.empty()) {
productos.erase(
std::remove_if(productos.begin(), productos.end(),
[q](const Producto& p) { return p.categoria != q->categoria; }),
productos.end()
);
}
auto* resultado = new std::vector<Producto>(productos);
return resultado;
}
private:
std::shared_ptr<RepositorioProductos> repositorio_;
};
// Bus de comandos y consultas
class CommandBus {
public:
CommandBus() = default;
void registrar_handler(std::type_index tipo, std::unique_ptr<CommandHandler> handler) {
handlers_[tipo] = std::move(handler);
}
void enviar(std::unique_ptr<void> command) {
std::type_index tipo = std::type_index(typeid(*command));
auto it = handlers_.find(tipo);
if (it == handlers_.end()) {
throw std::runtime_error("No hay handler para el comando");
}
it->second->manejar(command.get());
}
private:
std::map<std::type_index, std::unique_ptr<CommandHandler>> handlers_;
};
class QueryBus {
public:
QueryBus() = default;
void registrar_handler(std::type_index tipo, std::unique_ptr<QueryHandler> handler) {
handlers_[tipo] = std::move(handler);
}
std::unique_ptr<void> enviar(std::unique_ptr<void> query) {
std::type_index tipo = std::type_index(typeid(*query));
auto it = handlers_.find(tipo);
if (it == handlers_.end()) {
throw std::runtime_error("No hay handler para la consulta");
}
return std::unique_ptr<void>(it->second->manejar(query.get()));
}
private:
std::map<std::type_index, std::unique_ptr<QueryHandler>> handlers_;
};
// Demo CQRS
void demo_cqrs() {
// Configurar repositorios
auto repo_usuarios = std::make_shared<RepositorioUsuarios>();
auto repo_productos = std::make_shared<RepositorioProductos>();
// Configurar command bus
CommandBus command_bus;
command_bus.registrar_handler(
std::type_index(typeid(CrearUsuarioCommand)),
std::make_unique<CrearUsuarioHandler>(repo_usuarios)
);
command_bus.registrar_handler(
std::type_index(typeid(CrearProductoCommand)),
std::make_unique<CrearProductoHandler>(repo_productos)
);
// Configurar query bus
QueryBus query_bus;
query_bus.registrar_handler(
std::type_index(typeid(ObtenerUsuarioQuery)),
std::make_unique<ObtenerUsuarioHandler>(repo_usuarios)
);
query_bus.registrar_handler(
std::type_index(typeid(ListarProductosQuery)),
std::make_unique<ListarProductosHandler>(repo_productos)
);
// Ejecutar comandos (escrituras)
std::cout << "=== Ejecutando Comandos ===" << std::endl;
auto cmd_usuario = std::make_unique<CrearUsuarioCommand>(
"Ana García", "[email protected]", "Calle Principal 123"
);
command_bus.enviar(std::move(cmd_usuario));
auto cmd_producto1 = std::make_unique<CrearProductoCommand>(
"Laptop Gaming", 1200.00, "tecnologia", 10
);
command_bus.enviar(std::move(cmd_producto1));
auto cmd_producto2 = std::make_unique<CrearProductoCommand>(
"Mouse Inalámbrico", 45.99, "tecnologia", 20
);
command_bus.enviar(std::move(cmd_producto2));
std::cout << "Productos creados: " << repo_productos->listar_todos().size() << std::endl;
// Ejecutar consultas (lecturas)
std::cout << "\n=== Ejecutando Consultas ===" << std::endl;
auto query_usuario = std::make_unique<ObtenerUsuarioQuery>("user_1");
auto usuario_result = query_bus.enviar(std::move(query_usuario));
auto usuario = static_cast<Usuario*>(usuario_result.get());
std::cout << "Usuario consultado: " << usuario->nombre << std::endl;
auto query_productos = std::make_unique<ListarProductosQuery>("tecnologia");
auto productos_result = query_bus.enviar(std::move(query_productos));
auto productos = static_cast<std::vector<Producto>*>(productos_result.get());
std::cout << "Productos de tecnología: " << productos->size() << std::endl;
for (const auto& producto : *productos) {
std::cout << " - " << producto.nombre << ": $" << producto.precio << std::endl;
}
// Liberar memoria
delete usuario;
delete productos;
}
int main() {
demo_cqrs();
return 0;
}
Paso 6: Event Sourcing - Fuente de eventos
El patrón Event Sourcing almacena el estado de una aplicación como una secuencia de eventos en lugar de solo el estado actual.
Implementación básica
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <chrono>
#include <stdexcept>
#include <algorithm>
#include <sstream>
// Evento base
struct Evento {
std::string id;
std::string tipo;
std::map<std::string, std::string> datos;
std::chrono::steady_clock::time_point timestamp;
int version;
Evento(const std::string& tipo_evento, const std::map<std::string, std::string>& datos_evento)
: tipo(tipo_evento), datos(datos_evento), timestamp(std::chrono::steady_clock::now()), version(1) {
static int contador = 0;
id = "evt_" + std::to_string(++contador);
}
};
// Eventos específicos
struct ProductoCreadoEvent : public Evento {
ProductoCreadoEvent(const std::string& producto_id, const std::string& nombre,
double precio, const std::string& categoria)
: Evento("ProductoCreado", {
{"producto_id", producto_id},
{"nombre", nombre},
{"precio", std::to_string(precio)},
{"categoria", categoria}
}) {}
};
struct StockActualizadoEvent : public Evento {
StockActualizadoEvent(const std::string& producto_id, int cantidad, int nuevo_stock)
: Evento("StockActualizado", {
{"producto_id", producto_id},
{"cantidad", std::to_string(cantidad)},
{"nuevo_stock", std::to_string(nuevo_stock)}
}) {}
};
// Aggregate (Agregado)
class ProductoAggregate {
public:
ProductoAggregate(const std::string& producto_id = "")
: producto_id_(producto_id), nombre_(""), precio_(0.0), categoria_(""),
stock_(0), version_(0) {}
std::string crear(const std::string& nombre, double precio, const std::string& categoria, int stock = 0) {
producto_id_ = generar_id();
auto evento = std::make_shared<ProductoCreadoEvent>(producto_id_, nombre, precio, categoria);
aplicar_cambio(evento);
if (stock > 0) {
actualizar_stock(stock);
}
return producto_id_;
}
void actualizar_stock(int cantidad) {
int nuevo_stock = stock_ + cantidad;
auto evento = std::make_shared<StockActualizadoEvent>(producto_id_, cantidad, nuevo_stock);
aplicar_cambio(evento);
}
void aplicar_cambio(std::shared_ptr<Evento> evento) {
cambios_pendientes_.push_back(evento);
aplicar(*evento);
}
void aplicar(const Evento& evento) {
if (evento.tipo == "ProductoCreado") {
nombre_ = evento.datos.at("nombre");
precio_ = std::stod(evento.datos.at("precio"));
categoria_ = evento.datos.at("categoria");
} else if (evento.tipo == "StockActualizado") {
stock_ = std::stoi(evento.datos.at("nuevo_stock"));
}
version_++;
}
std::vector<std::shared_ptr<Evento>> obtener_cambios() {
return cambios_pendientes_;
}
void limpiar_cambios() {
cambios_pendientes_.clear();
}
// Getters
std::string get_producto_id() const { return producto_id_; }
std::string get_nombre() const { return nombre_; }
double get_precio() const { return precio_; }
std::string get_categoria() const { return categoria_; }
int get_stock() const { return stock_; }
int get_version() const { return version_; }
private:
std::string producto_id_;
std::string nombre_;
double precio_;
std::string categoria_;
int stock_;
int version_;
std::vector<std::shared_ptr<Evento>> cambios_pendientes_;
std::string generar_id() {
static int contador = 0;
return "prod_" + std::to_string(++contador);
}
};
// Event Store
class EventStore {
public:
EventStore() = default;
void guardar_eventos(const std::string& aggregate_id,
const std::vector<std::shared_ptr<Evento>>& eventos,
int version_esperada) {
if (eventos_.find(aggregate_id) == eventos_.end()) {
eventos_[aggregate_id] = {};
}
// Verificar concurrencia (optimistic concurrency)
if (version_esperada != -1 && eventos_[aggregate_id].size() != static_cast<size_t>(version_esperada)) {
throw std::runtime_error("Concurrency conflict");
}
for (const auto& evento : eventos) {
eventos_[aggregate_id].push_back(evento);
}
}
std::vector<std::shared_ptr<Evento>> obtener_eventos(const std::string& aggregate_id) {
auto it = eventos_.find(aggregate_id);
if (it != eventos_.end()) {
return it->second;
}
return {};
}
std::vector<std::shared_ptr<Evento>> obtener_todos_eventos() {
std::vector<std::shared_ptr<Evento>> todos_eventos;
for (const auto& [aggregate_id, eventos] : eventos_) {
todos_eventos.insert(todos_eventos.end(), eventos.begin(), eventos.end());
}
std::sort(todos_eventos.begin(), todos_eventos.end(),
[](const std::shared_ptr<Evento>& a, const std::shared_ptr<Evento>& b) {
return a->timestamp < b->timestamp;
});
return todos_eventos;
}
private:
std::map<std::string, std::vector<std::shared_ptr<Evento>>> eventos_;
};
// Repository para Event Sourcing
class ProductoRepository {
public:
ProductoRepository(std::shared_ptr<EventStore> event_store) : event_store_(event_store) {}
void guardar(std::shared_ptr<ProductoAggregate> producto) {
auto eventos = producto->obtener_cambios();
int version_esperada = event_store_->obtener_eventos(producto->get_producto_id()).size();
event_store_->guardar_eventos(producto->get_producto_id(), eventos, version_esperada);
producto->limpiar_cambios();
}
std::shared_ptr<ProductoAggregate> obtener_por_id(const std::string& producto_id) {
auto eventos = event_store_->obtener_eventos(producto_id);
if (eventos.empty()) {
return nullptr;
}
auto producto = std::make_shared<ProductoAggregate>(producto_id);
for (const auto& evento : eventos) {
producto->aplicar(*evento);
}
return producto;
}
private:
std::shared_ptr<EventStore> event_store_;
};
// Proyecciones (Projections)
class ProductoProjection {
public:
ProductoProjection(std::shared_ptr<EventStore> event_store) : event_store_(event_store) {}
void actualizar() {
auto eventos = event_store_->obtener_todos_eventos();
for (const auto& evento : eventos) {
if (evento->tipo == "ProductoCreado") {
productos_[evento->datos.at("producto_id")] = {
{"nombre", evento->datos.at("nombre")},
{"precio", evento->datos.at("precio")},
{"categoria", evento->datos.at("categoria")},
{"stock", "0"}
};
} else if (evento->tipo == "StockActualizado") {
auto it = productos_.find(evento->datos.at("producto_id"));
if (it != productos_.end()) {
it->second["stock"] = evento->datos.at("nuevo_stock");
}
}
}
}
std::vector<std::map<std::string, std::string>> obtener_todos() {
std::vector<std::map<std::string, std::string>> resultado;
for (const auto& [id, producto] : productos_) {
resultado.push_back(producto);
}
return resultado;
}
std::vector<std::map<std::string, std::string>> obtener_por_categoria(const std::string& categoria) {
std::vector<std::map<std::string, std::string>> resultado;
for (const auto& [id, producto] : productos_) {
if (producto.at("categoria") == categoria) {
resultado.push_back(producto);
}
}
return resultado;
}
private:
std::shared_ptr<EventStore> event_store_;
std::map<std::string, std::map<std::string, std::string>> productos_;
};
// Demo Event Sourcing
void demo_event_sourcing() {
// Configurar
auto event_store = std::make_shared<EventStore>();
auto producto_repo = std::make_shared<ProductoRepository>(event_store);
auto projection = std::make_shared<ProductoProjection>(event_store);
std::cout << "=== Event Sourcing Demo ===" << std::endl;
// Crear producto con eventos
auto producto = std::make_shared<ProductoAggregate>();
producto->crear("Laptop Gaming", 1200.00, "tecnologia", 10);
producto_repo->guardar(producto);
std::cout << "Producto creado: " << producto->get_nombre() << std::endl;
std::cout << "Stock inicial: " << producto->get_stock() << std::endl;
// Actualizar stock con eventos
producto->actualizar_stock(5);
producto_repo->guardar(producto);
std::cout << "Stock después de agregar 5: " << producto->get_stock() << std::endl;
// Cargar desde eventos
std::cout << "\n=== Cargando desde Event Store ===" << std::endl;
auto producto_cargado = producto_repo->obtener_por_id(producto->get_producto_id());
if (producto_cargado) {
std::cout << "Producto cargado: " << producto_cargado->get_nombre() << std::endl;
std::cout << "Stock cargado: " << producto_cargado->get_stock() << std::endl;
}
// Usar proyección
std::cout << "\n=== Usando Proyección ===" << std::endl;
projection->actualizar();
auto productos = projection->obtener_todos();
std::cout << "Productos en proyección: " << productos.size() << std::endl;
for (const auto& p : productos) {
std::cout << " - " << p.at("nombre") << ": $" << p.at("precio")
<< " (Stock: " << p.at("stock") << ")" << std::endl;
}
// Mostrar todos los eventos
std::cout << "\n=== Todos los Eventos ===" << std::endl;
auto eventos = event_store->obtener_todos_eventos();
for (const auto& evento : eventos) {
std::cout << evento->tipo << ": ";
for (const auto& [clave, valor] : evento->datos) {
std::cout << clave << "=" << valor << " ";
}
std::cout << std::endl;
}
}
int main() {
demo_event_sourcing();
return 0;
}
Paso 7: Saga Pattern - Transacciones distribuidas
El patrón Saga maneja transacciones distribuidas de larga duración mediante una secuencia de transacciones locales.
Implementación básica
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <functional>
#include <chrono>
#include <thread>
#include <stdexcept>
#include <random>
#include <algorithm>
enum class EstadoSaga {
INICIADA,
EN_PROGRESO,
COMPLETADA,
COMPENSANDO,
COMPENSADA,
FALLIDA
};
struct Saga {
std::string id;
std::string tipo;
std::vector<std::shared_ptr<struct PasoSaga>> pasos;
EstadoSaga estado;
std::map<std::string, std::string> datos;
std::chrono::steady_clock::time_point fecha_creacion;
std::chrono::steady_clock::time_point fecha_actualizacion;
Saga(const std::string& id_saga, const std::string& tipo_saga,
const std::map<std::string, std::string>& datos_iniciales)
: id(id_saga), tipo(tipo_saga), estado(EstadoSaga::INICIADA),
datos(datos_iniciales),
fecha_creacion(std::chrono::steady_clock::now()),
fecha_actualizacion(std::chrono::steady_clock::now()) {}
};
struct PasoSaga {
std::string nombre;
std::function<std::map<std::string, std::string>(const std::map<std::string, std::string>&)> accion;
std::function<void(const std::map<std::string, std::string>&)> compensacion;
int orden;
PasoSaga(const std::string& nombre_paso,
std::function<std::map<std::string, std::string>(const std::map<std::string, std::string>&)> accion_func,
std::function<void(const std::map<std::string, std::string>&)> compensacion_func,
int orden_paso)
: nombre(nombre_paso), accion(accion_func), compensacion(compensacion_func), orden(orden_paso) {}
};
class SagaManager {
public:
SagaManager() = default;
void registrar_paso(const std::string& nombre,
std::function<std::map<std::string, std::string>(const std::map<std::string, std::string>&)> accion,
std::function<void(const std::map<std::string, std::string>&)> compensacion) {
pasos_registrados_[nombre] = std::make_shared<PasoSaga>(
nombre, accion, compensacion, static_cast<int>(pasos_registrados_.size())
);
}
std::shared_ptr<Saga> iniciar_saga(const std::string& tipo, const std::map<std::string, std::string>& datos_iniciales) {
std::string saga_id = generar_id();
auto saga = std::make_shared<Saga>(saga_id, tipo, datos_iniciales);
sagas_[saga_id] = saga;
return saga;
}
void agregar_paso(const std::string& saga_id, const std::string& nombre_paso) {
auto saga_it = sagas_.find(saga_id);
if (saga_it == sagas_.end()) {
throw std::runtime_error("Saga no encontrada");
}
auto paso_it = pasos_registrados_.find(nombre_paso);
if (paso_it == pasos_registrados_.end()) {
throw std::runtime_error("Paso '" + nombre_paso + "' no registrado");
}
auto saga = saga_it->second;
auto paso_base = paso_it->second;
// Crear nueva instancia del paso para esta saga
auto nuevo_paso = std::make_shared<PasoSaga>(
paso_base->nombre, paso_base->accion, paso_base->compensacion,
static_cast<int>(saga->pasos.size())
);
saga->pasos.push_back(nuevo_paso);
saga->fecha_actualizacion = std::chrono::steady_clock::now();
}
std::shared_ptr<Saga> ejecutar_saga(const std::string& saga_id) {
auto saga_it = sagas_.find(saga_id);
if (saga_it == sagas_.end()) {
throw std::runtime_error("Saga no encontrada");
}
auto saga = saga_it->second;
saga->estado = EstadoSaga::EN_PROGRESO;
saga->fecha_actualizacion = std::chrono::steady_clock::now();
std::vector<std::shared_ptr<PasoSaga>> pasos_ejecutados;
try {
for (size_t i = 0; i < saga->pasos.size(); ++i) {
auto paso = saga->pasos[i];
std::cout << "🏃 Ejecutando paso " << (i+1) << ": " << paso->nombre << std::endl;
// Ejecutar acción
auto resultado = paso->accion(saga->datos);
for (const auto& [clave, valor] : resultado) {
saga->datos[clave] = valor;
}
pasos_ejecutados.push_back(paso);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
saga->estado = EstadoSaga::COMPLETADA;
std::cout << "✅ Saga completada exitosamente" << std::endl;
} catch (const std::exception& e) {
std::cout << "❌ Error en saga: " << e.what() << std::endl;
saga->estado = EstadoSaga::FALLIDA;
compensar_saga(saga, pasos_ejecutados);
}
saga->fecha_actualizacion = std::chrono::steady_clock::now();
return saga;
}
private:
std::map<std::string, std::shared_ptr<Saga>> sagas_;
std::map<std::string, std::shared_ptr<PasoSaga>> pasos_registrados_;
void compensar_saga(std::shared_ptr<Saga> saga, const std::vector<std::shared_ptr<PasoSaga>>& pasos_ejecutados) {
saga->estado = EstadoSaga::COMPENSANDO;
std::cout << "🔄 Compensando saga..." << std::endl;
// Compensar en orden inverso
for (auto it = pasos_ejecutados.rbegin(); it != pasos_ejecutados.rend(); ++it) {
try {
std::cout << "↩️ Compensando paso: " << (*it)->nombre << std::endl;
(*it)->compensacion(saga->datos);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} catch (const std::exception& e) {
std::cout << "⚠️ Error compensando paso " << (*it)->nombre << ": " << e.what() << std::endl;
}
}
saga->estado = EstadoSaga::COMPENSADA;
std::cout << "✅ Saga compensada" << std::endl;
}
std::string generar_id() {
static int contador = 0;
return "saga_" + std::to_string(++contador);
}
};
// Servicios para la saga
class ServicioInventario {
public:
std::map<std::string, std::string> reservar_stock(const std::map<std::string, std::string>& datos) {
std::string producto_id = datos.at("producto_id");
int cantidad = std::stoi(datos.at("cantidad"));
std::cout << "📦 Reservando " << cantidad << " unidades del producto " << producto_id << std::endl;
// Simular fallo aleatorio
static std::random_device rd;
static std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0.0, 1.0);
if (dis(gen) < 0.3) {
throw std::runtime_error("Error al reservar stock");
}
return {{"stock_reservado", "true"}};
}
void compensar_reserva(const std::map<std::string, std::string>& datos) {
std::string producto_id = datos.at("producto_id");
int cantidad = std::stoi(datos.at("cantidad"));
std::cout << "↩️ Liberando reserva de " << cantidad << " unidades del producto " << producto_id << std::endl;
}
};
class ServicioPagos {
public:
std::map<std::string, std::string> procesar_pago(const std::map<std::string, std::string>& datos) {
std::string usuario_id = datos.at("usuario_id");
double monto = std::stod(datos.at("monto"));
std::cout << "💳 Procesando pago de $" << monto << " para usuario " << usuario_id << std::endl;
// Simular fallo aleatorio
static std::random_device rd;
static std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0.0, 1.0);
if (dis(gen) < 0.3) {
throw std::runtime_error("Error al procesar pago");
}
static int contador_transaccion = 0;
std::string id_transaccion = "txn_" + std::to_string(++contador_transaccion);
return {
{"pago_procesado", "true"},
{"id_transaccion", id_transaccion}
};
}
void compensar_pago(const std::map<std::string, std::string>& datos) {
std::string id_transaccion = datos.at("id_transaccion");
std::cout << "↩️ Reembolsando pago " << id_transaccion << std::endl;
}
};
class ServicioEnvio {
public:
std::map<std::string, std::string> programar_envio(const std::map<std::string, std::string>& datos) {
std::string usuario_id = datos.at("usuario_id");
std::string direccion = datos.at("direccion");
std::cout << "🚚 Programando envío para usuario " << usuario_id << " a " << direccion << std::endl;
static int contador_guia = 0;
std::string numero_guia = "guia_" + std::to_string(++contador_guia);
return {
{"envio_programado", "true"},
{"numero_guia", numero_guia}
};
}
void cancelar_envio(const std::map<std::string, std::string>& datos) {
std::string numero_guia = datos.at("numero_guia");
std::cout << "↩️ Cancelando envío " << numero_guia << std::endl;
}
};
// Demo Saga Pattern
void demo_saga_pattern() {
// Configurar servicios
ServicioInventario inventario;
ServicioPagos pagos;
ServicioEnvio envio;
// Configurar saga manager
SagaManager manager;
// Registrar pasos
manager.registrar_paso(
"reservar_stock",
[&inventario](const auto& datos) { return inventario.reservar_stock(datos); },
[&inventario](const auto& datos) { inventario.compensar_reserva(datos); }
);
manager.registrar_paso(
"procesar_pago",
[&pagos](const auto& datos) { return pagos.procesar_pago(datos); },
[&pagos](const auto& datos) { pagos.compensar_pago(datos); }
);
manager.registrar_paso(
"programar_envio",
[&envio](const auto& datos) { return envio.programar_envio(datos); },
[&envio](const auto& datos) { envio.cancelar_envio(datos); }
);
// Datos iniciales
std::map<std::string, std::string> datos_pedido = {
{"usuario_id", "user_123"},
{"producto_id", "prod_456"},
{"cantidad", "2"},
{"monto", "99.99"},
{"direccion", "Calle Principal 123"}
};
// Crear y configurar saga
auto saga = manager.iniciar_saga("procesar_pedido", datos_pedido);
manager.agregar_paso(saga->id, "reservar_stock");
manager.agregar_paso(saga->id, "procesar_pago");
manager.agregar_paso(saga->id, "programar_envio");
// Ejecutar saga
std::cout << "=== Iniciando Saga de Procesamiento de Pedido ===" << std::endl;
auto resultado = manager.ejecutar_saga(saga->id);
std::cout << "\nEstado final: ";
switch (resultado->estado) {
case EstadoSaga::COMPLETADA: std::cout << "COMPLETADA"; break;
case EstadoSaga::FALLIDA: std::cout << "FALLIDA"; break;
case EstadoSaga::COMPENSADA: std::cout << "COMPENSADA"; break;
default: std::cout << "DESCONOCIDO"; break;
}
std::cout << std::endl;
std::cout << "Datos finales:" << std::endl;
for (const auto& [clave, valor] : resultado->datos) {
std::cout << " " << clave << ": " << valor << std::endl;
}
}
int main() {
demo_saga_pattern();
return 0;
}
Paso 8: Caso de estudio - Sistema de e-commerce
Vamos a crear un sistema completo de e-commerce usando múltiples patrones avanzados.
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <chrono>
#include <stdexcept>
#include <algorithm>
// Domain Models
struct Producto {
std::string id;
std::string nombre;
double precio;
int stock;
std::string categoria;
Producto(const std::string& id, const std::string& nombre, double precio,
const std::string& categoria, int stock = 0)
: id(id), nombre(nombre), precio(precio), stock(stock), categoria(categoria) {}
};
struct Usuario {
std::string id;
std::string nombre;
std::string email;
std::string direccion;
Usuario(const std::string& id, const std::string& nombre, const std::string& email,
const std::string& direccion)
: id(id), nombre(nombre), email(email), direccion(direccion) {}
};
struct Pedido {
std::string id;
std::string usuario_id;
std::vector<std::map<std::string, std::string>> items;
double total;
std::string estado;
std::chrono::steady_clock::time_point fecha_creacion;
Pedido(const std::string& id, const std::string& usuario_id)
: id(id), usuario_id(usuario_id), total(0.0), estado("creado"),
fecha_creacion(std::chrono::steady_clock::now()) {}
};
// Repositories
class RepositorioProductos {
public:
RepositorioProductos() = default;
std::shared_ptr<Producto> guardar(const Producto& producto) {
productos_[producto.id] = std::make_shared<Producto>(producto);
return productos_[producto.id];
}
std::shared_ptr<Producto> obtener_por_id(const std::string& id) {
auto it = productos_.find(id);
if (it != productos_.end()) {
return it->second;
}
return nullptr;
}
std::vector<std::shared_ptr<Producto>> obtener_todos() {
std::vector<std::shared_ptr<Producto>> resultado;
for (const auto& [id, producto] : productos_) {
resultado.push_back(producto);
}
return resultado;
}
bool actualizar_stock(const std::string& id, int cantidad) {
auto producto = obtener_por_id(id);
if (producto) {
producto->stock += cantidad;
return true;
}
return false;
}
private:
std::map<std::string, std::shared_ptr<Producto>> productos_;
};
class RepositorioUsuarios {
public:
RepositorioUsuarios() = default;
std::shared_ptr<Usuario> guardar(const Usuario& usuario) {
usuarios_[usuario.id] = std::make_shared<Usuario>(usuario);
return usuarios_[usuario.id];
}
std::shared_ptr<Usuario> obtener_por_id(const std::string& id) {
auto it = usuarios_.find(id);
if (it != usuarios_.end()) {
return it->second;
}
return nullptr;
}
std::shared_ptr<Usuario> obtener_por_email(const std::string& email) {
for (const auto& [id, usuario] : usuarios_) {
if (usuario->email == email) {
return usuario;
}
}
return nullptr;
}
private:
std::map<std::string, std::shared_ptr<Usuario>> usuarios_;
};
class RepositorioPedidos {
public:
RepositorioPedidos() = default;
std::shared_ptr<Pedido> guardar(const Pedido& pedido) {
pedidos_[pedido.id] = std::make_shared<Pedido>(pedido);
return pedidos_[pedido.id];
}
std::shared_ptr<Pedido> obtener_por_id(const std::string& id) {
auto it = pedidos_.find(id);
if (it != pedidos_.end()) {
return it->second;
}
return nullptr;
}
std::vector<std::shared_ptr<Pedido>> obtener_por_usuario(const std::string& usuario_id) {
std::vector<std::shared_ptr<Pedido>> resultado;
for (const auto& [id, pedido] : pedidos_) {
if (pedido->usuario_id == usuario_id) {
resultado.push_back(pedido);
}
}
return resultado;
}
private:
std::map<std::string, std::shared_ptr<Pedido>> pedidos_;
};
// Services
class ServicioCatalogo {
public:
ServicioCatalogo(std::shared_ptr<RepositorioProductos> repositorio)
: repositorio_(repositorio) {}
std::shared_ptr<Producto> crear_producto(const std::string& nombre, double precio,
const std::string& categoria, int stock = 0) {
std::string producto_id = generar_id();
Producto producto(producto_id, nombre, precio, categoria, stock);
return repositorio_->guardar(producto);
}
std::vector<std::shared_ptr<Producto>> listar_productos(const std::string& categoria = "") {
auto productos = repositorio_->obtener_todos();
if (!categoria.empty()) {
productos.erase(
std::remove_if(productos.begin(), productos.end(),
[categoria](const std::shared_ptr<Producto>& p) {
return p->categoria != categoria;
}),
productos.end()
);
}
return productos;
}
std::shared_ptr<Producto> obtener_producto(const std::string& id) {
return repositorio_->obtener_por_id(id);
}
private:
std::shared_ptr<RepositorioProductos> repositorio_;
std::string generar_id() {
static int contador = 0;
return "prod_" + std::to_string(++contador);
}
};
class ServicioUsuarios {
public:
ServicioUsuarios(std::shared_ptr<RepositorioUsuarios> repositorio)
: repositorio_(repositorio) {}
std::shared_ptr<Usuario> registrar_usuario(const std::string& nombre, const std::string& email,
const std::string& direccion) {
// Verificar si ya existe
if (repositorio_->obtener_por_email(email)) {
throw std::runtime_error("Email ya registrado");
}
std::string usuario_id = generar_id();
Usuario usuario(usuario_id, nombre, email, direccion);
return repositorio_->guardar(usuario);
}
std::shared_ptr<Usuario> obtener_usuario(const std::string& id) {
return repositorio_->obtener_por_id(id);
}
private:
std::shared_ptr<RepositorioUsuarios> repositorio_;
std::string generar_id() {
static int contador = 0;
return "user_" + std::to_string(++contador);
}
};
class ServicioPedidos {
public:
ServicioPedidos(std::shared_ptr<RepositorioPedidos> repositorio_pedidos,
std::shared_ptr<RepositorioProductos> repositorio_productos,
std::shared_ptr<RepositorioUsuarios> repositorio_usuarios)
: repositorio_pedidos_(repositorio_pedidos),
repositorio_productos_(repositorio_productos),
repositorio_usuarios_(repositorio_usuarios) {}
std::shared_ptr<Pedido> crear_pedido(const std::string& usuario_id,
const std::vector<std::map<std::string, std::string>>& items) {
// Verificar usuario
auto usuario = repositorio_usuarios_->obtener_por_id(usuario_id);
if (!usuario) {
throw std::runtime_error("Usuario no encontrado");
}
// Verificar productos y calcular total
double total = 0.0;
for (const auto& item : items) {
std::string producto_id = item.at("producto_id");
int cantidad = std::stoi(item.at("cantidad"));
auto producto = repositorio_productos_->obtener_por_id(producto_id);
if (!producto) {
throw std::runtime_error("Producto " + producto_id + " no encontrado");
}
if (producto->stock < cantidad) {
throw std::runtime_error("Stock insuficiente para " + producto->nombre);
}
total += producto->precio * cantidad;
}
// Crear pedido
std::string pedido_id = generar_id();
Pedido pedido(pedido_id, usuario_id);
pedido.items = items;
pedido.total = total;
// Actualizar stock
for (const auto& item : items) {
std::string producto_id = item.at("producto_id");
int cantidad = std::stoi(item.at("cantidad"));
repositorio_productos_->actualizar_stock(producto_id, -cantidad);
}
return repositorio_pedidos_->guardar(pedido);
}
bool cancelar_pedido(const std::string& pedido_id) {
auto pedido = repositorio_pedidos_->obtener_por_id(pedido_id);
if (!pedido || pedido->estado == "cancelado") {
return false;
}
// Revertir stock
for (const auto& item : pedido->items) {
std::string producto_id = item.at("producto_id");
int cantidad = std::stoi(item.at("cantidad"));
repositorio_productos_->actualizar_stock(producto_id, cantidad);
}
pedido->estado = "cancelado";
return true;
}
private:
std::shared_ptr<RepositorioPedidos> repositorio_pedidos_;
std::shared_ptr<RepositorioProductos> repositorio_productos_;
std::shared_ptr<RepositorioUsuarios> repositorio_usuarios_;
std::string generar_id() {
static int contador = 0;
return "ped_" + std::to_string(++contador);
}
};
// API Gateway
class ECommerceGateway {
public:
ECommerceGateway(std::shared_ptr<ServicioCatalogo> servicio_catalogo,
std::shared_ptr<ServicioUsuarios> servicio_usuarios,
std::shared_ptr<ServicioPedidos> servicio_pedidos)
: servicio_catalogo_(servicio_catalogo),
servicio_usuarios_(servicio_usuarios),
servicio_pedidos_(servicio_pedidos) {}
std::map<std::string, std::string> manejar_request(const std::string& metodo,
const std::string& ruta,
const std::map<std::string, std::string>& datos) {
try {
if (metodo == "GET" && ruta == "/productos") {
std::string categoria = datos.at("categoria");
auto productos = servicio_catalogo_->listar_productos(categoria.empty() ? "" : categoria);
std::vector<std::map<std::string, std::string>> productos_data;
for (const auto& producto : productos) {
productos_data.push_back({
{"id", producto->id},
{"nombre", producto->nombre},
{"precio", std::to_string(producto->precio)},
{"stock", std::to_string(producto->stock)},
{"categoria", producto->categoria}
});
}
return {
{"status", "200"},
{"data", "productos_data"} // En un caso real, serializar a JSON
};
} else if (metodo == "POST" && ruta == "/usuarios") {
auto usuario = servicio_usuarios_->registrar_usuario(
datos.at("nombre"), datos.at("email"), datos.at("direccion")
);
return {
{"status", "201"},
{"data", usuario->id}
};
} else if (metodo == "POST" && ruta == "/pedidos") {
std::string usuario_id = datos.at("usuario_id");
// Convertir items string a vector de maps (simplificado)
std::vector<std::map<std::string, std::string>> items;
auto pedido = servicio_pedidos_->crear_pedido(usuario_id, items);
return {
{"status", "201"},
{"data", pedido->id}
};
} else if (metodo == "POST" && ruta == "/pedidos/cancelar") {
std::string pedido_id = datos.at("pedido_id");
bool exito = servicio_pedidos_->cancelar_pedido(pedido_id);
return {
{"status", "200"},
{"data", exito ? "true" : "false"}
};
} else {
return {
{"status", "404"},
{"error", "Ruta no encontrada"}
};
}
} catch (const std::exception& e) {
return {
{"status", "400"},
{"error", e.what()}
};
}
}
private:
std::shared_ptr<ServicioCatalogo> servicio_catalogo_;
std::shared_ptr<ServicioUsuarios> servicio_usuarios_;
std::shared_ptr<ServicioPedidos> servicio_pedidos_;
};
// Demo del sistema completo
void demo_ecommerce() {
// Configurar repositorios
auto repo_productos = std::make_shared<RepositorioProductos>();
auto repo_usuarios = std::make_shared<RepositorioUsuarios>();
auto repo_pedidos = std::make_shared<RepositorioPedidos>();
// Configurar servicios
auto servicio_catalogo = std::make_shared<ServicioCatalogo>(repo_productos);
auto servicio_usuarios = std::make_shared<ServicioUsuarios>(repo_usuarios);
auto servicio_pedidos = std::make_shared<ServicioPedidos>(repo_pedidos, repo_productos, repo_usuarios);
// Configurar gateway
ECommerceGateway gateway(servicio_catalogo, servicio_usuarios, servicio_pedidos);
// Crear productos
auto producto1 = servicio_catalogo->crear_producto("Laptop Gaming", 1200.00, "tecnologia", 10);
auto producto2 = servicio_catalogo->crear_producto("Mouse Inalámbrico", 45.99, "tecnologia", 20);
auto producto3 = servicio_catalogo->crear_producto("Silla Ergonómica", 299.99, "muebles", 5);
std::cout << "🛍️ Productos creados:" << std::endl;
for (const auto& p : {producto1, producto2, producto3}) {
std::cout << " - " << p->nombre << ": $" << p->precio << " (Stock: " << p->stock << ")" << std::endl;
}
// Registrar usuario
auto usuario = servicio_usuarios->registrar_usuario(
"Ana García", "[email protected]", "Calle Principal 123"
);
std::cout << "\n👤 Usuario registrado: " << usuario->nombre << " (" << usuario->email << ")" << std::endl;
// Crear pedido a través del gateway
std::map<std::string, std::string> datos_pedido = {
{"usuario_id", usuario->id},
{"items", "[]"} // Simplificado
};
auto response = gateway.manejar_request("POST", "/pedidos", datos_pedido);
if (response.at("status") == "201") {
std::cout << "\n📦 Pedido creado exitosamente:" << std::endl;
std::cout << " ID: " << response.at("data") << std::endl;
std::cout << " Estado: creado" << std::endl;
} else {
std::cout << "\n❌ Error al crear pedido: " << response.at("error") << std::endl;
}
// Mostrar estado actual
auto productos_lista = servicio_catalogo->listar_productos();
auto usuarios_lista = repo_usuarios->obtener_por_id(usuario->id);
auto pedidos_lista = repo_pedidos->obtener_por_usuario(usuario->id);
std::cout << "\n📊 Estado actual:" << std::endl;
std::cout << " Productos en stock: " << productos_lista.size() << std::endl;
std::cout << " Usuarios registrados: " << (usuarios_lista ? 1 : 0) << std::endl;
std::cout << " Pedidos realizados: " << pedidos_lista.size() << std::endl;
// Mostrar stock actualizado
std::cout << "\n📦 Stock actualizado:" << std::endl;
for (const auto& producto : productos_lista) {
std::cout << " " << producto->nombre << ": " << producto->stock << " unidades" << std::endl;
}
}
int main() {
demo_ecommerce();
return 0;
}
Paso 9: Consideraciones de rendimiento y escalabilidad
Patrones para alta escalabilidad
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <chrono>
#include <thread>
#include <functional>
#include <queue>
#include <mutex>
#include <atomic>
// Caching con Redis/Memcached (simulado)
class Cache {
public:
Cache() = default;
std::string get(const std::string& key, const std::string& default_value = "") {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(key);
if (it != cache_.end()) {
return it->second;
}
return default_value;
}
void set(const std::string& key, const std::string& value, int ttl_segundos = 300) {
std::lock_guard<std::mutex> lock(mutex_);
cache_[key] = value;
// En un caso real, aquí se manejaría TTL
}
void del(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
cache_.erase(key);
}
private:
std::map<std::string, std::string> cache_;
std::mutex mutex_;
};
// Load Balancer
class LoadBalancer {
public:
LoadBalancer(const std::vector<std::string>& servidores)
: servidores_(servidores), indice_(0) {}
std::string siguiente_servidor() {
std::string servidor = servidores_[indice_];
indice_ = (indice_ + 1) % servidores_.size();
return servidor;
}
private:
std::vector<std::string> servidores_;
std::atomic<int> indice_;
};
// Database Sharding
class ShardManager {
public:
ShardManager(const std::vector<std::string>& shards) : shards_(shards) {}
std::string obtener_shard(const std::string& clave) {
// Hash simple para determinar shard
size_t hash_val = std::hash<std::string>{}(clave) % shards_.size();
return shards_[hash_val];
}
private:
std::vector<std::string> shards_;
};
// Message Queue para procesamiento asíncrono
class MessageQueue {
public:
MessageQueue() = default;
void publicar(const std::string& topico, const std::string& mensaje) {
std::lock_guard<std::mutex> lock(mutex_);
colas_[topico].push(mensaje);
}
std::string consumir(const std::string& topico) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = colas_.find(topico);
if (it != colas_.end() && !it->second.empty()) {
std::string mensaje = it->second.front();
it->second.pop();
return mensaje;
}
return "";
}
private:
std::map<std::string, std::queue<std::string>> colas_;
std::mutex mutex_;
};
// Ejemplo de sistema escalable
class SistemaEscalable {
public:
SistemaEscalable()
: cache_(std::make_shared<Cache>()),
load_balancer_(std::make_shared<LoadBalancer>(std::vector<std::string>{"server1", "server2", "server3"})),
message_queue_(std::make_shared<MessageQueue>()),
shard_manager_(std::make_shared<ShardManager>(std::vector<std::string>{"shard1", "shard2", "shard3"})) {}
std::string procesar_solicitud(const std::string& usuario_id, const std::string& accion, const std::string& datos) {
// Verificar cache primero
std::string cache_key = usuario_id + ":" + accion;
std::string cached = cache_->get(cache_key);
if (!cached.empty()) {
return cached;
}
// Balancear carga
std::string servidor = load_balancer_->siguiente_servidor();
// Determinar shard
std::string shard = shard_manager_->obtener_shard(usuario_id);
// Procesar (simulado)
std::string resultado = "Procesado en " + servidor + " usando " + shard;
// Cachear resultado
cache_->set(cache_key, resultado);
// Publicar en cola para procesamiento asíncrono
message_queue_->publicar("logs", usuario_id + " - " + accion);
return resultado;
}
std::string consumir_log() {
return message_queue_->consumir("logs");
}
private:
std::shared_ptr<Cache> cache_;
std::shared_ptr<LoadBalancer> load_balancer_;
std::shared_ptr<MessageQueue> message_queue_;
std::shared_ptr<ShardManager> shard_manager_;
};
// Demo de escalabilidad
void demo_escalabilidad() {
SistemaEscalable sistema;
// Simular múltiples solicitudes
std::vector<std::string> usuarios = {"user0", "user1", "user2", "user3", "user4",
"user5", "user6", "user7", "user8", "user9"};
std::vector<std::string> acciones = {"read", "write", "update", "delete"};
for (int i = 0; i < 20; ++i) {
std::string usuario = usuarios[i % usuarios.size()];
std::string accion = acciones[i % acciones.size()];
std::string resultado = sistema.procesar_solicitud(usuario, accion, "data" + std::to_string(i));
std::cout << "Solicitud " << (i+1) << ": " << resultado << std::endl;
// Simular procesamiento asíncrono
std::string log = sistema.consumir_log();
if (!log.empty()) {
std::cout << " 📋 Log procesado: " << log << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
int main() {
demo_escalabilidad();
return 0;
}
Conclusión
¡Has dominado los patrones avanzados de diseño en C++! Estos patrones te permiten construir sistemas de alto rendimiento, escalables y mantenibles que pueden manejar la complejidad de aplicaciones empresariales modernas.
C++ es especialmente adecuado para estos patrones avanzados porque:
- Performance: Control preciso sobre memoria y recursos
- Type Safety: Compilación estricta que previene errores en tiempo de ejecución
- Control de Recursos: RAII y smart pointers para gestión automática de memoria
- Multi-threading: Soporte nativo para concurrencia de alto rendimiento
- Flexibilidad: Templates y metaprogramación para patrones genéricos
Recuerda que cada patrón tiene su lugar y propósito. La clave está en entender cuándo y cómo aplicar cada uno según las necesidades específicas de tu proyecto.
Para más tutoriales sobre arquitectura avanzada y patrones de diseño en C++, visita nuestra sección de tutoriales.
¡Sigue practicando y aplicando estos patrones en proyectos reales!
💡 Tip Importante
📝 Mejores Prácticas para Patrones Avanzados en C++
- Empieza simple: No implementes patrones complejos prematuramente
- Mide el impacto: Evalúa si un patrón realmente resuelve un problema
- Considera el costo: Algunos patrones añaden complejidad significativa
- Documenta decisiones: Explica por qué elegiste ciertos patrones
- Mantén la coherencia: Usa patrones consistentemente en todo el sistema
- Prueba exhaustivamente: Los sistemas distribuidos requieren testing riguroso
- Monitorea en producción: Supervisa el comportamiento real de los patrones
- Aprende de fallos: Los patrones de resiliencia se prueban mejor con fallos reales
- Evoluciona iterativamente: Refactoriza y mejora la arquitectura con el tiempo
- Mantén la simplicidad: El mejor patrón es a menudo el más simple que funciona
- Gestiona memoria: Usa smart pointers y RAII para evitar leaks
- Thread safety: Considera concurrencia en patrones distribuidos
- Compilación: Aprovecha el type checking de C++ para detectar errores tempranamente
📚 Recursos Recomendados para C++:
- Effective C++ - Scott Meyers
- More Effective C++ - Scott Meyers
- Effective Modern C++ - Scott Meyers
- C++ Concurrency in Action - Anthony Williams
- Designing Data-Intensive Applications - Martin Kleppmann
- Building Microservices - Sam Newman
- Domain-Driven Design - Eric Evans
- Patterns of Enterprise Application Architecture - Martin Fowler
- Release It! - Michael T. Nygard
¡Estos recursos te ayudarán a profundizar en arquitecturas empresariales avanzadas con C++!
No hay comentarios aún
Sé el primero en comentar este tutorial.