Backend
Backend - Arquitectura
Conceptos fundamentales de arquitectura backend: Node puro vs frameworks, Clean Architecture, monolitos vs microservicios, comunicación entre servicios y API Gateway
ConceptosBest Practices
Introducción
La arquitectura es fundamental para construir aplicaciones backend escalables y mantenibles. Estos conceptos te ayudarán a tomar decisiones arquitectónicas informadas.
Node Puro vs NestJS
1 preguntas
1
Node puro vs NestJS: ¿Cuándo usar cada uno?
Comparación rápida:
| Aspecto | Node.js Puro | NestJS |
|---|---|---|
Complejidad | ✅ Simple | ❌ Más complejo |
Estructura | ❌ Tú decides | ✅ Estructura definida |
TypeScript | ⚠️ Opcional | ✅ Integrado |
Dependency Injection | ❌ Manual | ✅ Built-in |
Decoradores | ❌ No | ✅ Sí |
Testing | ⚠️ Manual setup | ✅ Integrado |
Curva de aprendizaje | ✅ Baja | ❌ Media-Alta |
Flexibilidad | ✅ Total | ⚠️ Limitada por framework |
Node.js Puro - Usa cuando:
✓Proyectos pequeños/medianos: APIs simples, scripts
✓Máxima flexibilidad: Necesitas control total
✓Equipo pequeño: Sin necesidad de estructura compleja
✓Prototipado rápido: Desarrollo rápido sin overhead
✓Microservicios simples: Servicios pequeños y específicos
Ejemplo Node puro:
javascript
const express = require('express');
const app = express();
app.get('/users', async (req, res) => {
const users = await db.getUsers();
res.json(users);
});
app.listen(3000);NestJS - Usa cuando:
✓Proyectos grandes: Aplicaciones enterprise
✓Equipo grande: Necesitas estructura consistente
✓TypeScript: Quieres type safety
✓Arquitectura definida: Clean Architecture, SOLID
✓Testing: Necesitas testing robusto
✓Escalabilidad: Proyecto que crecerá
Ejemplo NestJS:
typescript
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Get()
findAll(): Promise<User[]> {
return this.usersService.findAll();
}
}Recomendación:
•Node puro: Para proyectos pequeños o cuando necesitas flexibilidad
•NestJS: Para proyectos enterprise o cuando trabajas en equipo grande
Clean Architecture
1 preguntas
1
¿Qué es Clean Architecture?
Definición: Arquitectura que separa el código en capas independientes, donde las capas internas no dependen de las externas.
Principio clave: Dependencias apuntan hacia adentro (hacia el dominio).
Capas (de afuera hacia adentro):
1.Frameworks & Drivers (Externa):
• Web framework (Express, NestJS)
• Base de datos
• APIs externas
• UI
2.Interface Adapters:
• Controllers
• Presenters
• Gateways
• DTOs
3.Use Cases (Aplicación):
• Lógica de negocio específica
• Casos de uso
• Orquestación
4.Entities (Dominio):
• Entidades de negocio
• Reglas de dominio
• Lógica core
Ejemplo de estructura:
src/
domain/ # Entidades puras
user.ts
use-cases/ # Lógica de aplicación
create-user.ts
adapters/ # Interfaces
controllers/
repositories/
infrastructure/ # Frameworks
database/
http/Ventajas:
✓Testabilidad: Fácil testear lógica de negocio
✓Independencia: Cambiar frameworks sin afectar dominio
✓Mantenibilidad: Código organizado y claro
✓Escalabilidad: Fácil agregar nuevas features
Desventajas:
✗ Más código inicial
✗ Curva de aprendizaje
✗ Puede ser overkill para proyectos pequeños
Regla de dependencia:
•Capas externas pueden usar internas
•Capas internas NUNCA usan externas
•Comunicación entre capas vía interfaces
Monolito vs Microservicios
4 preguntas
1
Monolito vs Microservicios: ¿Cuándo usar cada uno?
Comparación:
| Aspecto | Monolito | Microservicios |
|---|---|---|
Complejidad | ✅ Baja | ❌ Alta |
Deployment | ✅ Simple (uno) | ❌ Complejo (múltiples) |
Escalabilidad | ⚠️ Vertical | ✅ Horizontal |
Desarrollo | ✅ Rápido inicial | ❌ Más lento |
Testing | ✅ Más simple | ❌ Más complejo |
Debugging | ✅ Más fácil | ❌ Más difícil |
Tecnología | ⚠️ Una stack | ✅ Múltiples stacks |
Falla aislada | ❌ Todo cae | ✅ Solo un servicio |
Monolito - Usa cuando:
✓Proyecto pequeño/mediano: Equipo pequeño
✓Desarrollo inicial: MVP, prototipo
✓Lógica acoplada: Dominio muy relacionado
✓Equipo pequeño: < 10 desarrolladores
✓Deployment simple: Una aplicación
Ventajas del Monolito:
•Desarrollo más rápido
•Testing más simple
•Debugging más fácil
•Menos overhead operacional
•Transacciones ACID más fáciles
Microservicios - Usa cuando:
✓Proyecto grande: Equipo grande (> 20 devs)
✓Escalabilidad: Necesitas escalar partes específicas
✓Dominios separados: Bounded contexts claros
✓Tecnologías diferentes: Necesitas diferentes stacks
✓Equipos independientes: Equipos por servicio
Ventajas de Microservicios:
•Escalabilidad independiente
•Tecnología por servicio
•Despliegue independiente
•Falla aislada
•Equipos autónomos
Desventajas de Microservicios:
✗ Complejidad operacional
✗ Latencia de red
✗ Transacciones distribuidas complejas
✗ Testing más difícil
✗ Debugging distribuido
Recomendación:
•Empezar con monolito
•Migrar a microservicios cuando sea necesario
•No usar microservicios "porque sí"
2
Microservicios: pros y contras
Ventajas (Pros):
✓Escalabilidad independiente:
• Escalar solo servicios que necesitan
• Optimizar recursos por servicio
✓Tecnología flexible:
• Cada servicio puede usar diferente stack
• Elegir mejor herramienta para cada caso
✓Despliegue independiente:
• Deployar un servicio sin afectar otros
• Rollback granular
✓Equipos autónomos:
• Equipos pueden trabajar independientemente
• Ownership claro por servicio
✓Falla aislada:
• Si un servicio falla, otros siguen funcionando
• Mejor resiliencia
✓Desarrollo paralelo:
• Múltiples equipos trabajando simultáneamente
• Menos conflictos de código
Desventajas (Contras):
✗ Complejidad operacional:
• Múltiples servicios para monitorear
• Más infraestructura
• DevOps más complejo
✗ Latencia de red:
• Comunicación entre servicios añade latencia
• Múltiples llamadas de red
✗ Transacciones distribuidas:
• ACID difícil de mantener
• Necesitas Saga pattern o eventual consistency
✗ Testing más complejo:
• Testing de integración más difícil
• Necesitas mocks/stubs
• Testing end-to-end complejo
✗ Debugging distribuido:
• Trazar requests entre servicios
• Logs distribuidos
• Necesitas distributed tracing
✗ Overhead de desarrollo:
• Más código boilerplate
• Configuración por servicio
• Más tiempo de setup
✗ Consistencia de datos:
• Eventual consistency
• Sincronización compleja
Cuándo vale la pena:
✓Equipo grande (> 20 personas)
✓Dominios claramente separados
✓Necesitas escalar partes específicas
✓Equipos independientes
✓Infraestructura madura
3
¿Cómo manejar comunicación entre servicios?
Patrones de comunicación:
1. Síncrona (Request/Response):
✓HTTP/REST:
javascript
// Servicio A llama a Servicio B
const response = await fetch('http://service-b/api/users');
const users = await response.json();✓gRPC:
javascript
// Más eficiente que REST
const client = new UserServiceClient('service-b:50051');
const users = await client.getUsers({});Ventajas:
•Simple de implementar
•Respuesta inmediata
•Fácil de debuggear
Desventajas:
•Acoplamiento temporal
•Si servicio B cae, A también falla
•Latencia acumulada
2. Asíncrona (Event-Driven):
✓Message Queue (RabbitMQ, Kafka):
javascript
// Servicio A publica evento
await channel.publish('user.created', { userId: 123 });
// Servicio B consume evento
channel.consume('user.created', (msg) => {
// Procesar evento
});✓Event Bus (Redis Pub/Sub):
javascript
// Publicar
redis.publish('events', JSON.stringify(event));
// Suscribir
redis.subscribe('events', (channel, message) => {
// Procesar evento
});Ventajas:
•Desacoplamiento
•Resiliencia (si B cae, eventos se acumulan)
•Escalabilidad
•No bloquea
Desventajas:
•Más complejo
•Eventual consistency
•Debugging más difícil
3. Híbrida:
✓Síncrona para datos críticos:
• Operaciones que necesitan respuesta inmediata
• Validaciones
✓Asíncrona para procesos:
• Notificaciones
• Procesamiento en background
• Eventos de dominio
Mejores prácticas:
✓Circuit Breaker:
javascript
// Prevenir cascading failures
if (circuitBreaker.isOpen()) {
return fallbackResponse();
}✓Retry con backoff:
javascript
// Reintentar con delay exponencial
await retry(apiCall, { retries: 3, backoff: 'exponential' });✓Timeout:
javascript
// No esperar indefinidamente
const response = await fetch(url, { timeout: 5000 });✓Idempotencia:
javascript
// Requests pueden duplicarse
if (alreadyProcessed(requestId)) {
return cachedResponse;
}Cuándo usar cada una:
| Escenario | Patrón | Razón |
|---|---|---|
Datos críticos | Síncrono | Necesitas respuesta inmediata |
Notificaciones | Asíncrono | No bloquea, eventual consistency OK |
Procesamiento pesado | Asíncrono | No bloquea otros servicios |
Consulta simple | Síncrono | Simple y directo |
Eventos de dominio | Asíncrono | Desacoplamiento |
4
¿Qué es un API Gateway?
Definición: Punto de entrada único que actúa como intermediario entre clientes y microservicios.
Funciones principales:
✓Routing:
Cliente → API Gateway → Microservicio A
→ Microservicio B
→ Microservicio C✓Autenticación/Autorización:
javascript
// Gateway valida token antes de routing
if (!isValidToken(req.headers.authorization)) {
return res.status(401).json({ error: 'Unauthorized' });
}✓Rate Limiting:
javascript
// Limitar requests por cliente
if (rateLimitExceeded(clientId)) {
return res.status(429).json({ error: 'Too many requests' });
}✓Load Balancing:
javascript
// Distribuir carga entre instancias
const service = loadBalancer.selectInstance('user-service');✓Transformación de requests:
javascript
// Adaptar formato de request
const adaptedRequest = transformRequest(req);✓Caching:
javascript
// Cachear respuestas frecuentes
if (cached = cache.get(key)) {
return cached;
}✓Logging y Monitoreo:
javascript
// Centralizar logs
logger.log({ service: 'user-service', duration: 150ms });Arquitectura:
[Clientes]
↓
[API Gateway]
↓
[Microservicios]Ventajas:
✓Punto único de entrada: Cliente solo conoce gateway
✓Seguridad centralizada: Auth en un lugar
✓Simplifica cliente: No necesita conocer múltiples endpoints
✓Cross-cutting concerns: Rate limiting, logging, etc.
✓Versionado: Manejar versiones de API
Desventajas:
✗ Single point of failure: Si gateway cae, todo cae
✗ Latencia adicional: Una capa más
✗ Complejidad: Otro componente para mantener
Implementaciones comunes:
•Kong: Open source, plugin-based
•AWS API Gateway: Managed service
•Nginx: Reverse proxy + gateway
•Zuul: Netflix (Java)
•Express Gateway: Node.js
Ejemplo básico:
javascript
// API Gateway
app.use('/api/users', proxy('http://user-service:3001'));
app.use('/api/orders', proxy('http://order-service:3002'));
app.use('/api/payments', proxy('http://payment-service:3003'));
// Cliente solo conoce
GET /api/users/123
// Gateway routea a user-service internamenteCuándo usar:
✓Múltiples microservicios
✓Necesitas centralizar auth/rate limiting
✓Quieres simplificar cliente
✓Necesitas transformación de requests
✓Múltiples protocolos (HTTP, gRPC, WebSocket)