StackPractices
advanced Por Mathias Paulenko

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

CriterioPesoOpción AOpción BOpción C
Experiencia del equipoAlto534
Soporte de comunidadMedio543
PerformanceMedio354
Costo operacionalAlto425
Score Ponderado4.23.34.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ónCaso de uso
Read replicasCargas de lectura intensiva
ShardingEscritura intensiva, datasets grandes
Connection poolingMuchas 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.