Guía de Arquitectura de Software
Una guía para diseñar arquitectura de software: monolitos vs microservicios, arquitectura en capas, flujo de datos y criterios de selección de tecnología.
Overview
La arquitectura de software define la estructura de un sistema, las relaciones entre componentes y los principios que guían el diseño y la evolución. Una buena arquitectura permite a los equipos moverse rápido sin romper cosas.
When to Apply
- Inicias un proyecto nuevo o una reescritura mayor
- Escalas un sistema que está alcanzando límites de performance
- Organizas un equipo grande alrededor de ownership de código
- Migras de infraestructura legacy a moderna
Estilos Arquitectónicos
Arquitectura Monolítica
Estructura: Unidad desplegable única que contiene toda la funcionalidad.
Cuándo Elegir
- Equipo pequeño (< 10 desarrolladores)
- Dominio simple con baja complejidad
- Fase de prototipado rápido
- Requerimientos de latencia estrictos entre componentes
Pros: Despliegue simple, testing fácil, bajo overhead operacional. Contras: Alto acoplamiento, más difícil escalar componentes individuales, riesgo de fallas en cascada.
Arquitectura de Microservicios
Estructura: Servicios independientes que se comunican por red.
Cuándo Elegir
- Equipo grande (> 20 desarrolladores)
- Dominio complejo con contextos acotados claros
- Necesidad de escalar y desplegar independientemente
- Múltiples stacks tecnológicos requeridos
Pros: Despliegue independiente, autonomía de equipo, persistencia políglota. Contras: Latencia de red, complejidad operacional, dificultad de debugging distribuido.
Monolito Modular
Estructura: Unidad desplegable única con módulos internos bien definidos.
Cuándo Elegir
- Equipo mediano (10–30 desarrolladores)
- Quieres postergar la complejidad de microservicios
- Límites de dominio claros pero infraestructura compartida
Pros: Operaciones más simples que microservicios, mejor estructura que big-ball-of-mud. Contras: Requiere disciplina para mantener límites de módulos.
Arquitectura en Capas
Modelo Clásico de 3 Capas
┌──────────────────────────────┐
│ Capa de Presentación │
│ - Controllers, Views, DTOs │
├──────────────────────────────┤
│ Capa de Lógica de Negocio │
│ - Services, Domain Models │
├──────────────────────────────┤
│ Capa de Acceso a Datos │
│ - Repositories, ORM, Queries │
└──────────────────────────────┘
Regla de Dependencia: Las capas internas no deben depender de las externas. Consulta principios SOLID.
Patrones de Flujo de Datos
CQRS (Command Query Responsibility Segregation)
Separa los modelos de lectura y escritura.
Cuándo Usar
- Las cargas de lectura y escritura difieren considerablemente
- Los modelos de lectura requieren datos denormalizados/optimizados
- Event sourcing ya está en uso
Trade-off: Agrega complejidad; úsalo solo cuando lecturas y escrituras escalan independientemente.
Arquitectura Event-Driven
Los componentes se comunican mediante eventos asíncronos.
Cuándo Usar
- Se requiere desacoplamiento entre servicios
- Las acciones pueden procesarse asíncronamente
- La trazabilidad de cambios de estado es valiosa
Opciones de Event Bus: Apache Kafka, RabbitMQ, AWS SNS/SQS, NATS.
Framework de Selección de Tecnología
Matriz de Criterios
| Criterio | Peso | Opción A | Opción B | Opción C |
|---|---|---|---|---|
| Experiencia del equipo | Alto | 5 | 3 | 4 |
| Soporte de comunidad | Medio | 5 | 4 | 3 |
| Performance | Medio | 3 | 5 | 4 |
| Costo operacional | Alto | 4 | 2 | 5 |
| Score Ponderado | 4.2 | 3.3 | 4.1 |
Registro de Decisiones
Documenta cada elección tecnológica mayor con contexto, alternativas y consecuencias. Usa la Plantilla de ADR.
Patrones de Escalabilidad
Escalado Horizontal
Agrega más instancias detrás de un load balancer.
Client -> Load Balancer -> [Instance 1, Instance 2, Instance 3]
Requerimiento: El estado debe externalizarse (base de datos, cache, object storage).
Escalado de Base de Datos
| Patrón | Caso de uso |
|---|---|
| Read replicas | Cargas de lectura intensiva |
| Sharding | Escritura intensiva, datasets grandes |
| Connection pooling | Muchas instancias de aplicación |
| Caching (Redis) | Datos calientes, storage de sesiones |
Comunicación entre Componentes
Síncrona (REST / gRPC)
- Pros: Modelo mental simple, feedback inmediato.
- Contras: Acoplamiento fuerte, posibles fallas en cascada.
- Usar para: Operaciones orientadas al usuario que requieren respuesta inmediata.
Asíncrona (Events / Message Queues)
- Pros: Desacoplada, resiliente, escalable.
- Contras: Consistencia eventual, más difícil de debuggear.
- Usar para: Procesamiento en background, notificaciones, analytics.
Anti-Patrones
- Big Ball of Mud: Sin arquitectura, todo acoplado
- Microservicios prematuros: Dividir antes de entender los límites
- Golden Hammer: Usar la tecnología favorita para todo
- Not Invented Here: Reconstruir en vez de comprar/adoptar
- Over-Engineering: Resolver problemas que todavía no tienes
Lo que funciona
- Empieza simple: Comienza con un monolito modular; extrae servicios cuando sea necesario
- Define contextos acotados: Usa Domain-Driven Design para encontrar límites naturales
- Diseña para observabilidad: Cada componente debe exponer métricas, logs, traces
- Automatiza todo: CI/CD, infraestructura, testing, escaneo de seguridad
- Documenta decisiones: ADRs para cada elección arquitectónica mayor
Troubleshooting
- High latency between services: trace the request path. Look for synchronous chains, missing caching, and oversized payloads that cross network boundaries.
- Single point of failure: identify components without redundancy. Add replicas, failover, or circuit breakers before scaling traffic.
- Unexpected coupling between services: review shared databases, libraries, and schemas. Bound contexts should own their data and expose stable interfaces.
- Cost spikes after scaling: Reserved capacity or spot instances can reduce steady-state spend.
- Difficult to reason about the system: maintain architecture decision records and service dependency maps.
Temas Avanzados
Escenario Detallado: Seleccion de Arquitectura para E-commerce
Proyecto: Plataforma e-commerce (Python + Django)
Equipo: 8 desarrolladores (creciendo a 15 en 12 meses)
Volumen: 50k usuarios activos, 10k pedidos/dia
Dominio: Catalogo, Orders, Payments, Users, Notifications
Fase 1: Monolito modular (mes 0-6)
- Django con modulos separados por bounded context
- Esquema PostgreSQL por modulo (sin FKs entre modulos)
- Comunicacion via servicios internos (no acceso directo a DB)
- Deploy: 1 binario, CI/CD con GitHub Actions
Estructura:
shop/
modules/
catalog/
domain/ # Product, Category, SKU
application/ # CreateProductService, SearchService
infrastructure/ # ProductRepository (Django ORM)
api/ # CatalogApi (interfaz publica)
views/ # HTTP views
orders/
domain/ # Order, OrderLine, OrderStatus
application/ # PlaceOrderService, CancelOrderService
infrastructure/ # OrderRepository
api/ # OrdersApi
views/
payments/
domain/ # Payment, Transaction
application/ # ProcessPaymentService
infrastructure/ # StripeGateway, PaymentRepository
api/
views/
shared/
kernel/ # BaseEntity, Money, DomainEvent
Reglas de boundary:
- catalog NO importa de orders ni payments
- orders importa CatalogApi (interfaz), no implementacion
- payments importa OrdersApi (interfaz)
- Verificado con pylint-import-checker en CI
Testeo:
- Unitarios por modulo: < 10ms (sin DB)
- Integracion por modulo: < 200ms (SQLite en memoria)
- Cross-module: fakes en memoria de otros modulos
- E2E: Django test client, < 2s por test
Fase 2: Extraccion de notificaciones (mes 6-9)
- Notificaciones es el modulo con menor acoplamiento
- Extraer a microservicio independiente (Go + RabbitMQ)
- Reemplazar NotificationApi in-process por cliente HTTP
- Migrar datos con CDC (Debezium -> Kafka -> nueva DB)
- Traffic shift gradual: 5% -> 25% -> 50% -> 100%
Fase 3: Extraccion de catalogo (mes 12-18)
- Catalogo necesita escalado independiente (busquedas intensivas)
- Extraer a microservicio (Python + Elasticsearch)
- Migrar de PostgreSQL a Elasticsearch para busquedas
- Mantener PostgreSQL para escritura (CQRS)
Decision matrix para extraccion:
| Modulo | Riesgo | Valor | Esfuerzo | Prioridad |
|--------|--------|-------|----------|-----------|
| Notifications | Bajo | Medio | 4 sem | 1 |
| Catalog | Medio | Alto | 8 sem | 2 |
| Payments | Alto | Alto | 12 sem | 3 |
| Orders | Alto | Critico | 16 sem | 4 |
| Users | Medio | Alto | 8 sem | 5 |
Lecciones aprendidas:
- El monolito modular permitio extraccion mecanica (no arquitectonica)
- Los tests cross-module con fakes detectaron breaking changes
- El traffic shift gradual dio confianza al negocio
- CDC evito dual-write y posibles inconsistencias
Como documento decisiones arquitectonicas?
Usa ADRs (Architecture Decision Records). Cada ADR documenta: contexto, decision, alternativas consideradas, consecuencias. Guarda los ADRs en el repositorio junto al codigo (carpeta docs/adr/). Usa numeracion secuencial (ADR-001, ADR-002). Un ADR no se borra ni se edita; si la decision cambia, crea un nuevo ADR que lo suprime. Esto crea un historial auditable de decisiones y su razonamiento.
Notas de Producción
- Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
- Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.
Puntos Clave
- Aplica guía de arquitectura de software cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
Errores Comunes en Producción
- Tratar la guía como un checklist para completar una vez en lugar de una práctica por evolucionar.
- Adoptar cada recomendación de golpe en lugar de comenzar con un cambio medido.
- Saltar la evaluación de madurez e imponer prácticas avanzadas a un equipo no preparado.
- No actualizar runbooks y expectativas de guardia al introducir nuevas prácticas.
- Ignorar datos reales de incidentes al priorizar qué partes de la guía aplicar primero.
- No asignar un responsable que revise decisiones trimestralmente.
- Copiar ejemplos sin adaptarlos a las herramientas y restricciones reales del equipo.
- Olvidar medir resultados antes de agregar la siguiente mejora.
Preguntas frecuentes
¿Cómo empiezo con esto en un proyecto existente?
Empieza con una parte pequeña y aislada de tu codebase. Aplica los conceptos de esta guía a un módulo o servicio. Mide el impacto, luego expande a otras áreas.
¿Qué herramientas necesito?
Las herramientas mencionadas throughout esta guía se listan en cada sección. La mayoría son open-source y ampliamente adoptadas. Consulta los recursos relacionados para instrucciones de setup.
¿Cómo mido el éxito después de implementar esto?
Define métricas claras antes de empezar: benchmarks de rendimiento, tasas de error o indicadores de mantenibilidad. Compara antes y después. Itera basándote en datos, no en suposiciones.
Recursos Relacionados
Patrón MVC
Separa la aplicación en componentes Modelo, Vista y Controlador. Patrón de diseño arquitectural para código organizado y mantenible.
PatternPatrón Repository
Abstrae la lógica de acceso a datos detrás de una interfaz limpia. Patrón de diseño arquitectural para capas de datos testeables y mantenibles.
GuideGuía de Diseño de APIs REST
Una Referencia Detallada para diseñar APIs REST limpias, escalables y mantenibles.
GuideGuía de Pipelines CI/CD
Una guía práctica para construir pipelines CI/CD con GitHub Actions, testing, estrategias de deployment y procedimientos de rollback.
RecipePatrones de Comunicación entre Microservicios
Elige entre patrones de comunicación síncronos y asíncronos para arquitecturas de microservicios resilientes.
RecipeDescubrimiento de servicios
Implementa service discovery con health checks, resolución DNS-based y service registries para ambientes en vivo de microservicios.