Tu guía completa desde cero
para construir aplicaciones modernas y escalables
Por Pedro Lopez
Head Teams Wirbi
Simplificando la arquitectura cloud-native
Usa las flechas → ← para navegar • F para pantalla completa
Entendiendo el concepto desde cero
Imagina que tu aplicación es como un restaurante grande. En lugar de tener una sola cocina gigante donde se prepara todo (monolito), los microservicios son como tener múltiples estaciones especializadas: una para pizzas, otra para ensaladas, otra para postres. Cada una funciona independientemente pero trabajan juntas para servir al cliente.
Aplicación Monolítica = Como vivir en una casa donde todo está en un solo cuarto grande
Microservicios = Como vivir en una casa con habitaciones separadas (cocina, dormitorio, baño)
Si se daña el baño, no afecta la cocina. Si quieres renovar el dormitorio, no necesitas tocar el resto de la casa.
¿Cuándo usar cada uno? Guía práctica
Aspecto | 🏗️ Monolito | 🧩 Microservicios |
---|---|---|
Complejidad Inicial | ✅ Simple de empezar | ❌ Más complejo al inicio |
Deployment | ✅ Un solo paquete | ❌ Múltiples servicios |
Escalabilidad | ❌ Escala todo junto | ✅ Escala solo lo necesario |
Equipos | ❌ Todos trabajan en lo mismo | ✅ Equipos independientes |
Tecnología | ❌ Una sola tecnología | ✅ Múltiples tecnologías |
Fallos | ❌ Un error afecta todo | ✅ Fallos aislados |
Creemos algo simple paso a paso
Un servicio simple de "Lista de Tareas" (TODO List). Es el "Hola Mundo" de los microservicios. Simple pero completo.
// 1. Creamos la aplicación web
var builder = WebApplication.CreateBuilder(args);
// 2. Agregamos servicios (como ingredientes de una receta)
builder.Services.AddEndpointsApiExplorer(); // Para documentación
builder.Services.AddSwaggerGen(); // Para probar nuestra API
var app = builder.Build();
// 3. Configuramos Swagger (interfaz para probar)
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// 4. Lista en memoria para guardar tareas (por ahora)
var tareas = new List<Tarea>();
// 5. ENDPOINTS - Las URLs de nuestro servicio
// GET: Obtener todas las tareas
app.MapGet("/api/tareas", () =>
{
return Results.Ok(tareas);
})
.WithName("ObtenerTareas")
.WithOpenApi();
// GET: Obtener una tarea por ID
app.MapGet("/api/tareas/{id}", (int id) =>
{
var tarea = tareas.FirstOrDefault(t => t.Id == id);
return tarea is null
? Results.NotFound($"Tarea {id} no encontrada")
: Results.Ok(tarea);
})
.WithName("ObtenerTareaPorId")
.WithOpenApi();
// POST: Crear nueva tarea
app.MapPost("/api/tareas", (Tarea nuevaTarea) =>
{
nuevaTarea.Id = tareas.Count + 1;
nuevaTarea.FechaCreacion = DateTime.Now;
tareas.Add(nuevaTarea);
return Results.Created($"/api/tareas/{nuevaTarea.Id}", nuevaTarea);
})
.WithName("CrearTarea")
.WithOpenApi();
// PUT: Actualizar tarea
app.MapPut("/api/tareas/{id}", (int id, Tarea tareaActualizada) =>
{
var tarea = tareas.FirstOrDefault(t => t.Id == id);
if (tarea is null)
return Results.NotFound();
tarea.Titulo = tareaActualizada.Titulo;
tarea.Completada = tareaActualizada.Completada;
return Results.Ok(tarea);
})
.WithName("ActualizarTarea")
.WithOpenApi();
// DELETE: Eliminar tarea
app.MapDelete("/api/tareas/{id}", (int id) =>
{
var tarea = tareas.FirstOrDefault(t => t.Id == id);
if (tarea is null)
return Results.NotFound();
tareas.Remove(tarea);
return Results.Ok($"Tarea {id} eliminada");
})
.WithName("EliminarTarea")
.WithOpenApi();
app.Run();
// 6. Nuestra clase modelo (¿qué es una tarea?)
public class Tarea
{
public int Id { get; set; }
public string Titulo { get; set; } = "";
public bool Completada { get; set; }
public DateTime FechaCreacion { get; set; }
}
mkdir MiPrimerMicroservicio
cd MiPrimerMicroservicio
dotnet new webapi -minimal
dotnet run
http://localhost:5000/swagger
Los componentes esenciales explicados
↓ Solicitudes HTTP ↓
↓ Distribuye a servicios ↓
↓ Cada uno con su base de datos ↓
Cada microservicio tiene su propia base de datos. Esto se llama "Database per Service". Es como si cada departamento de una empresa tuviera su propio archivo. No pueden acceder directamente al archivo de otro departamento, deben pedirlo a través de los canales oficiales (APIs).
Las 3 formas principales explicadas
Como una llamada telefónica. Haces una pregunta y esperas la respuesta inmediata.
GET /api/usuarios/123
← Respuesta inmediata
✅ Cuándo usar: Necesitas respuesta inmediata
❌ Evitar si: La operación toma mucho tiempo
Como enviar un email o WhatsApp. Envías el mensaje y no esperas respuesta inmediata.
Publicar: "Nuevo pedido"
← Sin esperar respuesta
✅ Cuándo usar: Procesos largos, notificaciones
❌ Evitar si: Necesitas confirmación inmediata
Como un grupo de WhatsApp. Publicas algo y todos los interesados lo reciben.
Evento: "Usuario Creado"
← Múltiples servicios escuchan
✅ Cuándo usar: Múltiples servicios necesitan saber
❌ Evitar si: Solo un servicio necesita la info
// Servicio A llama a Servicio B
public class ProductoService
{
private readonly HttpClient _httpClient;
public async Task<decimal> ObtenerPrecioConDescuento(int productoId, int usuarioId)
{
// 1. Obtener información del producto (local)
var producto = await _dbContext.Productos.FindAsync(productoId);
// 2. Llamar al servicio de usuarios para obtener descuento
var response = await _httpClient.GetAsync(
$"http://usuario-service/api/usuarios/{usuarioId}/descuento"
);
var descuento = await response.Content.ReadFromJsonAsync<decimal>();
// 3. Calcular precio final
return producto.Precio * (1 - descuento);
}
}
Empieza simple con REST (síncrono). Es más fácil de entender y debuggear.
Cuando tengas problemas de performance o necesites desacoplar servicios, entonces considera mensajería asíncrona.
Por qué cada servicio tiene su propia base de datos
Imagina que cada servicio es como un departamento en una empresa. Cada departamento tiene su propio archivo/armario con sus documentos. Si Ventas necesita información de RRHH, no puede ir a abrir su archivo directamente, debe pedirlo formalmente (a través de la API).
Servicio | Tipo de BD | ¿Por qué esta elección? |
---|---|---|
👤 Usuarios | PostgreSQL (Relacional) | Datos estructurados, relaciones entre tablas, transacciones ACID |
📦 Catálogo | MongoDB (NoSQL) | Productos con atributos variables, búsquedas flexibles |
🛒 Carrito | Redis (Cache) | Datos temporales, super rápido, expira automáticamente |
📊 Analytics | ClickHouse (Columnar) | Grandes volúmenes de datos, consultas analíticas |
Problema: ¿Qué pasa cuando necesitas actualizar datos en múltiples servicios?
Por ejemplo: Al crear un pedido, necesitas:
Solución: Patrón SAGA - Como una cadena de dominós controlada.
// Flujo de creación de pedido con SAGA
public class CrearPedidoSaga
{
// Paso 1: Reservar inventario
public async Task<bool> ReservarInventario(Pedido pedido)
{
try
{
await _inventarioService.ReservarProductos(pedido.Productos);
return true;
}
catch
{
return false; // Si falla, se detiene aquí
}
}
// Paso 2: Procesar pago
public async Task<bool> ProcesarPago(Pedido pedido)
{
try
{
await _pagoService.Cobrar(pedido.Total, pedido.ClienteId);
return true;
}
catch
{
// Si falla, compensamos: liberamos el inventario
await _inventarioService.LiberarProductos(pedido.Productos);
return false;
}
}
// Paso 3: Crear pedido
public async Task<bool> CrearPedido(Pedido pedido)
{
try
{
await _pedidoService.Crear(pedido);
return true;
}
catch
{
// Si falla, compensamos todo
await _pagoService.Reembolsar(pedido.Total, pedido.ClienteId);
await _inventarioService.LiberarProductos(pedido.Productos);
return false;
}
}
}
Qué hacer cuando las cosas fallan (y siempre fallan)
Es la capacidad de tu sistema para recuperarse de los errores. Como un edificio antisísmico: no evita el terremoto, pero no se cae cuando ocurre. En microservicios, asumimos que algo siempre puede fallar.
Si algo falla, inténtalo de nuevo. Como cuando el WiFi falla y recargas la página.
Como un fusible eléctrico. Si algo falla mucho, deja de intentarlo por un tiempo.
No esperes para siempre. Como cuando pides pizza: si no llega en 30 min, cancelas.
// 1. Instalar paquete: dotnet add package Polly
// 2. Configurar políticas de resiliencia
var retryPolicy = Policy
.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync(
3, // Reintentar 3 veces
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 2, 4, 8 segundos
onRetry: (outcome, timespan, retryCount, context) =>
{
Console.WriteLine($"Reintento {retryCount} después de {timespan} segundos");
});
// 3. Circuit Breaker: después de 3 fallas, espera 30 segundos
var circuitBreakerPolicy = Policy
.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.CircuitBreakerAsync(
3, // Después de 3 fallas consecutivas
TimeSpan.FromSeconds(30), // Espera 30 segundos antes de intentar de nuevo
onBreak: (result, timespan) =>
{
Console.WriteLine($"Circuito abierto por {timespan}");
},
onReset: () =>
{
Console.WriteLine("Circuito cerrado, funcionando normal");
});
// 4. Combinar políticas
var resilientPolicy = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);
// 5. Usar en tu código
public async Task<Producto> ObtenerProducto(int id)
{
return await resilientPolicy.ExecuteAsync(async () =>
{
var response = await _httpClient.GetAsync($"/api/productos/{id}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Producto>();
});
}
Empaquetando tu microservicio
Docker es como un contenedor de envío para tu aplicación.
Así como un contenedor puede llevar cualquier cosa y funcionar en cualquier barco,
Docker empaqueta tu aplicación para que funcione en cualquier servidor.
Sin Docker: "En mi máquina funciona" 😅
Con Docker: "Funciona en todas partes" 🎉
# 1. Imagen base - Como elegir el sistema operativo
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
# 2. Compilar la aplicación
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MiServicio.csproj", "."]
RUN dotnet restore "MiServicio.csproj"
COPY . .
RUN dotnet build "MiServicio.csproj" -c Release -o /app/build
# 3. Publicar la aplicación
FROM build AS publish
RUN dotnet publish "MiServicio.csproj" -c Release -o /app/publish
# 4. Imagen final - Solo lo necesario para ejecutar
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MiServicio.dll"]
# Construir imagen
docker build -t mi-servicio .
# Ejecutar contenedor
docker run -p 8080:80 mi-servicio
# Ver contenedores corriendo
docker ps
# Detener contenedor
docker stop [container-id]
version: '3.8'
services:
# Servicio de API
api:
build: ./ServicioAPI
ports:
- "8080:80"
environment:
- ConnectionStrings__DefaultConnection=Server=db;Database=MiDB;
depends_on:
- db
# Base de datos
db:
image: postgres:15
environment:
- POSTGRES_PASSWORD=MiPassword123
- POSTGRES_DB=MiDB
volumes:
- db-data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
db-data:
Cómo asegurarte de que todo funciona
Imagina una pirámide: muchos tests pequeños y rápidos en la base, pocos tests grandes y lentos en la punta.
5% - Tests completos del sistema
30% - Tests de componentes juntos
65% - Tests de funciones individuales
using Xunit;
public class CalculadoraTests
{
[Fact]
public void Sumar_DosNumeros_RetornaResultadoCorrecto()
{
// Arrange (Preparar)
var calculadora = new Calculadora();
// Act (Actuar)
var resultado = calculadora.Sumar(5, 3);
// Assert (Verificar)
Assert.Equal(8, resultado);
}
[Theory]
[InlineData(1, 1, 2)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
public void Sumar_VariosEscenarios_RetornaResultadoCorrecto(int a, int b, int esperado)
{
var calculadora = new Calculadora();
var resultado = calculadora.Sumar(a, b);
Assert.Equal(esperado, resultado);
}
}
public class ProductosApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public ProductosApiTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task ObtenerProductos_RetornaListaDeProductos()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/api/productos");
// Assert
response.EnsureSuccessStatusCode();
var productos = await response.Content.ReadFromJsonAsync<List<Producto>>();
Assert.NotNull(productos);
Assert.NotEmpty(productos);
}
}
Cómo saber qué está pasando en tu sistema
Como un doctor que examina a un paciente, necesitas diferentes herramientas para diagnosticar problemas:
[INFO] Usuario 123 login exitoso
[ERROR] Fallo conexión BD
CPU: 45%
Requests/sec: 1000
Errores: 0.1%
Gateway → Auth → Orders → DB
Total: 250ms
// 1. Instalar: dotnet add package Serilog.AspNetCore
// 2. Configurar en Program.cs
using Serilog;
// Configurar Serilog
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console() // Logs en consola
.WriteTo.File("logs/miapp-.txt", rollingInterval: RollingInterval.Day) // Logs en archivo
.CreateLogger();
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog(); // Usar Serilog
// 3. Usar en tu código
app.MapPost("/api/pedidos", (Pedido pedido, ILogger<Program> logger) =>
{
logger.LogInformation("Creando pedido para cliente {ClienteId}", pedido.ClienteId);
try
{
// Procesar pedido
var resultado = ProcesarPedido(pedido);
logger.LogInformation("Pedido {PedidoId} creado exitosamente", resultado.Id);
return Results.Ok(resultado);
}
catch (Exception ex)
{
logger.LogError(ex, "Error creando pedido para cliente {ClienteId}", pedido.ClienteId);
return Results.Problem("Error procesando pedido");
}
});
// Agregar health checks
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy())
.AddSqlServer(connectionString)
.AddRedis(redisConnection);
// Endpoint de salud
app.MapHealthChecks("/health"); // GET /health → 200 OK o 503 Unhealthy
No registres información sensible como contraseñas o números de tarjeta.
Usa niveles de log apropiados:
• DEBUG: Solo en desarrollo
• INFO: Eventos importantes normales
• WARNING: Algo raro pero manejable
• ERROR: Errores que necesitan atención
Estrategia paso a paso sin romper nada
Como una higuera que crece alrededor de un árbol viejo, los microservicios van reemplazando gradualmente partes del monolito hasta que eventualmente el monolito desaparece. No es necesario reescribir todo de una vez.
Mes 1
Encuentra las partes
independientes
(Usuarios, Pagos, etc.)
Mes 2-3
Extrae la parte
más simple
(ej: Notificaciones)
Mes 4
Punto de entrada
único que enruta
al lugar correcto
Mes 5+
Repite el proceso
con más servicios
gradualmente
// Facade que decide si usar monolito o microservicio
public interface IUserService
{
Task<User> GetUserAsync(int id);
}
public class UserServiceFacade : IUserService
{
private readonly IConfiguration _config;
private readonly HttpClient _httpClient;
private readonly MonolithUserRepository _monolithRepo;
public async Task<User> GetUserAsync(int id)
{
// Feature flag para migración gradual
var useMicroservice = _config.GetValue<bool>("Features:UseUserMicroservice");
if (useMicroservice)
{
// Llamar al nuevo microservicio
var response = await _httpClient.GetAsync($"/api/users/{id}");
return await response.Content.ReadFromJsonAsync<User>();
}
else
{
// Usar el código del monolito
return await _monolithRepo.GetUserAsync(id);
}
}
}
Aprende de los errores de otros
Problema: Un servicio para cada tabla de BD
Solución: Agrupa por dominio de negocio, no por tablas
Problema: Múltiples servicios usando la misma BD
Solución: Cada servicio su propia BD, comunicación por API
Problema: Asumir que todo siempre funciona
Solución: Implementar retry, circuit breaker, timeouts
Problema: No saber qué está pasando en producción
Solución: Logs, métricas y traces desde el día 1
Problema: Intentar hacer todo atómico entre servicios
Solución: Usar patrones como SAGA o eventual consistency
Problema: Kubernetes, Istio, etc desde el inicio
Solución: Empieza simple, evoluciona según necesites
Qué estudiar después
1-3 meses
• REST APIs
• Docker básico
• Unit testing
• Git & CI/CD
3-6 meses
• Mensajería (RabbitMQ)
• Patrones (SAGA, CQRS)
• Kubernetes básico
• Observabilidad
6-12 meses
• Service Mesh
• Event Sourcing
• gRPC
• Seguridad avanzada
1+ años
• Arquitectura
• Performance tuning
• Multi-cloud
• Domain-Driven Design
Construye un mini e-commerce con 3 servicios:
1. Servicio de Usuarios: Registro, login (JWT)
2. Servicio de Productos: CRUD de productos
3. Servicio de Órdenes: Crear pedidos
Esto te dará experiencia práctica con los conceptos principales.
Ahora es momento de practicar 💪
Recuerda: La mejor forma de aprender es construyendo
1️⃣ Crea tu primer microservicio simple
2️⃣ Dockerízalo y ejecuta localmente
3️⃣ Agrega un segundo servicio
4️⃣ Haz que se comuniquen
5️⃣ Comparte tu progreso en LinkedIn 🚀
Pedro Lopez
Head Teams Wirbi
teams@wirbi.com | ¿Preguntas? ¡Escríbeme!