Arquitetura de Software

O Papel do Arquiteto de Software

ARQUITETURA NÃO É:
- Desenhar diagramas bonitos e nunca implementar
- Escolher a tecnologia mais hype do momento
- Tomar decisões sozinho em uma torre de marfim

ARQUITETURA É:
- Tomar decisões estruturais que são CARAS de mudar depois
- Analisar trade-offs e comunicá-los claramente para stakeholders
- Definir restrições que guiam decisões de design
- Criar FITNESS FUNCTIONS que validam automaticamente as decisões

"The architecture of a software system is the set of structures
needed to reason about the system." — SEI/CMU

LEIS FUNDAMENTAIS:
1. Lei de Conway: "A estrutura do software reflete a estrutura organizacional"
   Times separados → serviços separados → APIs entre eles
   Solução: alinhar times com domínios de negócio (Inverse Conway Maneuver)

2. Lei de Gall: "Um sistema complexo que funciona invariavelmente
   evoluiu de um sistema simples que funcionava."
   Implicação: comece simples, evolua incrementalmente

3. Última Decisão Responsável: adie decisões arquiteturais até o
   último momento responsável — quando tiver mais informação

Características Arquiteturais (os “-ilities”)

CARACTERÍSTICAS OPERACIONAIS:
┌─────────────────┬────────────────────────────────────────────────┐
│ Disponibilidade │ % de tempo que o sistema está acessível        │
│                 │ 99.9% = 8.76h/ano de downtime                 │
│                 │ 99.99% = 52.6min/ano                          │
├─────────────────┼────────────────────────────────────────────────┤
│ Escalabilidade  │ Capacidade de lidar com aumento de carga       │
│                 │ Vertical: máquina maior                        │
│                 │ Horizontal: mais máquinas                      │
├─────────────────┼────────────────────────────────────────────────┤
│ Performance     │ Latência e throughput                           │
│                 │ p99 < 200ms? 10k requests/s?                  │
├─────────────────┼────────────────────────────────────────────────┤
│ Confiabilidade  │ Probabilidade de funcionar corretamente        │
│                 │ Tolerância a falhas, graceful degradation      │
├─────────────────┼────────────────────────────────────────────────┤
│ Resiliência     │ Capacidade de se recuperar de falhas            │
│                 │ Circuit breakers, retries, fallbacks           │
└─────────────────┴────────────────────────────────────────────────┘

CARACTERÍSTICAS ESTRUTURAIS:
┌─────────────────┬────────────────────────────────────────────────┐
│ Manutenibilidade│ Facilidade de modificar e corrigir             │
│                 │ Modularidade, baixo acoplamento                │
├─────────────────┼────────────────────────────────────────────────┤
│ Testabilidade   │ Facilidade de testar componentes               │
│                 │ Injeção de dependência, contratos claros       │
├─────────────────┼────────────────────────────────────────────────┤
│ Deployability   │ Facilidade e frequência de deploy              │
│                 │ CI/CD, feature flags, blue-green               │
├─────────────────┼────────────────────────────────────────────────┤
│ Extensibilidade │ Facilidade de adicionar funcionalidades        │
│                 │ Plugins, hooks, event-driven                   │
└─────────────────┴────────────────────────────────────────────────┘

TRADE-OFF FUNDAMENTAL:
Nem todas as características podem ser maximizadas simultaneamente.
Escolher 3-5 características prioritárias para o contexto do sistema.

Exemplo: Sistema de pagamentos
  Prioridades: Confiabilidade > Segurança > Performance > Disponibilidade
  Aceita: menor Deployability, menor Extensibilidade

Exemplo: Rede social
  Prioridades: Escalabilidade > Disponibilidade > Performance
  Aceita: consistência eventual (CAP theorem)

Estilos Arquiteturais

1. LAYERED ARCHITECTURE (monolítica em camadas):

┌────────────────────────────────────┐
│        Presentation Layer          │  Controllers, Views
├────────────────────────────────────┤
│         Business Layer             │  Services, Domain Logic
├────────────────────────────────────┤
│        Persistence Layer           │  Repositories, DAOs
├────────────────────────────────────┤
│         Database Layer             │  Database Engine
└────────────────────────────────────┘

Regra: cada camada só acessa a camada imediatamente abaixo.
Vantagens: simplicidade, separação de concerns, familiar.
Desvantagens: acoplamento vertical, difícil escalar parcialmente.
Quando usar: aplicações CRUD, times pequenos, MVPs.

2. MODULAR MONOLITH (evolução da layered):

┌─────────────────────────────────────────────┐
│                  API Gateway                 │
├──────────┬──────────┬──────────┬────────────┤
│  Auth    │  Orders  │  Users   │  Products  │  Módulos independentes
│  Module  │  Module  │  Module  │  Module    │
│  ┌────┐  │  ┌────┐  │  ┌────┐  │  ┌────┐    │
│  │Svc │  │  │Svc │  │  │Svc │  │  │Svc │    │  Cada módulo tem
│  │Repo│  │  │Repo│  │  │Repo│  │  │Repo│    │  suas próprias camadas
│  │DB  │  │  │DB  │  │  │DB  │  │  │DB  │    │
│  └────┘  │  └────┘  │  └────┘  │  └────┘    │
└──────────┴──────────┴──────────┴────────────┘
              Shared Database (ou schemas separados)

Comunicação entre módulos: interfaces públicas (não acesso direto ao DB)
Vantagens: escalabilidade organizacional sem complexidade distribuída.
Precursor natural para microsserviços (extrair módulo = extrair serviço).

3. MICROKERNEL (plugin architecture):

┌────────────────────────────────────────┐
│              Core System               │
│  (funcionalidade mínima e estável)     │
├────────┬────────┬────────┬─────────────┤
│Plugin A│Plugin B│Plugin C│  Plugin D   │
│Tax BR  │Tax US  │Export  │  Custom     │
└────────┴────────┴────────┴─────────────┘

Quando usar: sistemas com regras que variam por cliente/região.
Exemplos: IDEs, browsers, ERP com módulos por país.

4. EVENT-DRIVEN ARCHITECTURE:

┌──────────┐    ┌──────────────────┐    ┌──────────────┐
│ Producer │ →  │  Event Broker    │ →  │  Consumer A  │
│ (Orders) │    │ (Kafka/RabbitMQ) │    │  (Email)     │
└──────────┘    │                  │ →  ├──────────────┤
                │                  │    │  Consumer B  │
                └──────────────────┘    │  (Inventory) │
                                        └──────────────┘

Dois padrões:
- Event Notification: "algo aconteceu" (event contém mínimo de dados)
  Consumers buscam dados adicionais quando necessário.
- Event-Carried State Transfer: event contém todos os dados necessários
  Consumers são autossuficientes (eliminam chamadas síncronas).

Topologias:
- Broker: eventos publicados em tópicos, consumers se inscrevem
  Mais desacoplado, mais difícil garantir ordem de processamento.
- Mediator: orquestrador central coordena workflow
  Mais controle, ponto único de falha.
// Event-Driven na prática:

// ACOPLAMENTO FORTE (chamada síncrona):
async function createOrder(data) {
  const order = await saveOrder(data);
  await emailService.sendConfirmation(order);   // Se falhar, tudo falha
  await inventoryService.reduceStock(order);     // Acoplado!
  await analyticsService.trackPurchase(order);   // 3 dependências síncronas
  return order;
  // Latência total = soma de todas as chamadas
  // Disponibilidade = produto das disponibilidades (99.9%^3 = 99.7%)
}

// ACOPLAMENTO FRACO (event-driven):
async function createOrder(data) {
  const order = await saveOrder(data);

  // Transactional Outbox Pattern: salvar evento na mesma transação
  await db.transaction(async (tx) => {
    await tx.insert('orders', order);
    await tx.insert('outbox_events', {
      aggregate_type: 'Order',
      aggregate_id: order.id,
      event_type: 'OrderCreated',
      payload: JSON.stringify(order),
    });
  });
  // Processo separado (Debezium, polling) publica eventos do outbox

  return order;
  // Latência = apenas saveOrder
  // Email, estoque, analytics processam assincronamente
  // Se falharem: retry com backoff, dead letter queue
}

// Consumers independentes:
eventBus.subscribe('OrderCreated', async (event) => {
  await sendConfirmationEmail(event.payload);
});

eventBus.subscribe('OrderCreated', async (event) => {
  await reduceInventory(event.payload);
});

// Adicionar novo consumer NÃO requer mudança no producer
// Cada consumer tem retry e dead letter queue independente

Hexagonal Architecture (Ports and Adapters)

A arquitetura hexagonal, proposta por Alistair Cockburn em 2005, inverte a dependência clássica: o domínio fica no centro e não depende de nada externo. Toda comunicação com o mundo exterior passa por ports (interfaces) e adapters (implementações).

                     ┌─────────────────────────────┐
                     │       DRIVING ADAPTERS       │
                     │  (quem INICIA a interação)   │
                     │                               │
                     │  REST Controller              │
                     │  GraphQL Resolver             │
                     │  CLI Command                  │
                     │  Event Consumer               │
                     └──────────┬──────────────────┘

                     ┌──────────▼──────────────────┐
                     │       DRIVING PORTS          │
                     │  (interfaces de entrada)      │
                     │                               │
                     │  CreateOrderUseCase           │
                     │  CancelOrderUseCase           │
                     └──────────┬──────────────────┘

                     ┌──────────▼──────────────────┐
                     │        DOMAIN CORE           │
                     │  (regras de negócio puras)    │
                     │                               │
                     │  Order, Product, Payment      │
                     │  Domain Services              │
                     │  Domain Events                │
                     │  Value Objects                 │
                     └──────────┬──────────────────┘

                     ┌──────────▼──────────────────┐
                     │       DRIVEN PORTS           │
                     │  (interfaces de saída)        │
                     │                               │
                     │  OrderRepository (interface)  │
                     │  PaymentGateway (interface)   │
                     │  NotificationService (iface)  │
                     └──────────┬──────────────────┘

                     ┌──────────▼──────────────────┐
                     │       DRIVEN ADAPTERS        │
                     │  (quem RECEBE a interação)    │
                     │                               │
                     │  PostgresOrderRepository      │
                     │  StripePaymentGateway         │
                     │  SendGridNotificationService  │
                     └─────────────────────────────┘

REGRA DE DEPENDÊNCIA:
→ Adapters dependem de Ports
→ Ports dependem do Domain
→ Domain NÃO depende de NADA externo
// === DOMAIN CORE (zero dependências externas) ===

// Value Object
class Money {
  constructor(
    readonly amount: number,
    readonly currency: string
  ) {
    if (amount < 0) throw new Error('Amount cannot be negative');
  }

  add(other: Money): Money {
    if (this.currency !== other.currency) throw new Error('Currency mismatch');
    return new Money(this.amount + other.amount, this.currency);
  }
}

// Entity
class Order {
  private constructor(
    readonly id: string,
    readonly items: OrderItem[],
    private _status: OrderStatus
  ) {}

  static create(items: OrderItem[]): Order {
    if (items.length === 0) throw new Error('Order must have items');
    return new Order(crypto.randomUUID(), items, 'pending');
  }

  get total(): Money {
    return this.items.reduce(
      (sum, item) => sum.add(item.subtotal),
      new Money(0, 'BRL')
    );
  }

  confirm(): void {
    if (this._status !== 'pending') throw new Error('Only pending orders can be confirmed');
    this._status = 'confirmed';
  }
}

// === DRIVEN PORT (interface — contrato de saída) ===
interface OrderRepository {
  save(order: Order): Promise<void>;
  findById(id: string): Promise<Order | null>;
}

interface PaymentGateway {
  charge(amount: Money, method: PaymentMethod): Promise<PaymentResult>;
}

// === DRIVING PORT (use case — contrato de entrada) ===
class CreateOrderUseCase {
  constructor(
    private readonly orders: OrderRepository,
    private readonly payments: PaymentGateway
  ) {}

  async execute(input: CreateOrderInput): Promise<Order> {
    const order = Order.create(input.items);
    const payment = await this.payments.charge(order.total, input.paymentMethod);

    if (payment.status === 'approved') {
      order.confirm();
    }

    await this.orders.save(order);
    return order;
  }
}

// === DRIVEN ADAPTER (implementação concreta) ===
class PostgresOrderRepository implements OrderRepository {
  constructor(private readonly pool: Pool) {}

  async save(order: Order): Promise<void> {
    await this.pool.query(
      'INSERT INTO orders (id, items, status) VALUES ($1, $2, $3)',
      [order.id, JSON.stringify(order.items), order.status]
    );
  }

  async findById(id: string): Promise<Order | null> {
    const result = await this.pool.query('SELECT * FROM orders WHERE id = $1', [id]);
    return result.rows[0] ? this.mapToDomain(result.rows[0]) : null;
  }
}

// === DRIVING ADAPTER (controller HTTP) ===
class OrderController {
  constructor(private readonly createOrder: CreateOrderUseCase) {}

  async handlePost(req: Request, res: Response): Promise<void> {
    const order = await this.createOrder.execute(req.body);
    res.status(201).json({ orderId: order.id });
  }
}
QUANDO USAR HEXAGONAL:
✅ Domínio complexo com muitas regras de negócio
✅ Múltiplos adapters (REST + GraphQL + CLI, Postgres + MongoDB)
✅ Testes de domínio sem infraestrutura (puro unit test)
✅ Times grandes onde isolamento de camadas reduz conflitos

QUANDO NÃO USAR:
❌ CRUDs simples (overhead de abstrações sem benefício)
❌ Scripts e lambdas (vida curta, poucas dependências)
❌ Protótipos (velocidade > arquitetura)

Microsserviços e Service Mesh

MICROSSERVIÇOS — quando faz sentido:

PRÉ-REQUISITOS (sem eles, microsserviços vão falhar):
□ CI/CD maduro (deploy automatizado e confiável)
□ Observabilidade robusta (métricas, logs, traces distribuídos)
□ Equipe experiente com sistemas distribuídos
□ Domínio de negócio bem entendido (bounded contexts claros)
□ Necessidade real de escalar times independentemente
□ Container orchestration (Kubernetes ou similar)

COMPLEXIDADES INTRODUZIDAS:
- Rede: latência, falhas parciais, retry storms
- Consistência: transações distribuídas (Saga pattern)
- Debugging: trace distribuído, correlação de logs
- Deploy: orquestração de deploys interdependentes
- Teste: testes de contrato, testes de integração entre serviços
- Operacional: mais serviços = mais coisas para monitorar/manter

PADRÕES DE RESILIÊNCIA:
// Circuit Breaker — evitar cascade failure:
// Estado: CLOSED → OPEN → HALF-OPEN → CLOSED
const CircuitBreaker = require('opossum');

const breaker = new CircuitBreaker(callPaymentService, {
  timeout: 3000,          // Timeout por chamada: 3s
  errorThresholdPercentage: 50,  // Abre se > 50% de falhas
  resetTimeout: 30000,    // Tenta fechar após 30s
  volumeThreshold: 10,    // Mínimo de chamadas antes de avaliar
});

breaker.on('open', () => {
  logger.warn('Circuit breaker ABERTO para payment service');
  metrics.inc('circuit_breaker_open_total');
});

breaker.on('halfOpen', () => {
  logger.info('Circuit breaker HALF-OPEN — testando payment service');
});

breaker.fallback(() => {
  // Fallback quando circuito está aberto:
  return { status: 'pending', message: 'Pagamento será processado em breve' };
});

// Uso:
const result = await breaker.fire(orderData);

// Retry com exponential backoff:
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries) throw error;
      const delay = Math.min(1000 * Math.pow(2, attempt) + Math.random() * 1000, 30000);
      // Jitter aleatório evita thundering herd
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Bulkhead — isolar falhas por dependência:
// Pool de conexões separado para cada serviço externo
// Se payment service travar, não consome conexões do email service
const paymentPool = new ConnectionPool({ max: 10 });
const emailPool = new ConnectionPool({ max: 5 });
SERVICE MESH (Istio, Linkerd):

Sidecar proxy intercepta todo tráfego de rede do serviço.
Sem mudar o código da aplicação, adiciona:

┌─────────────────────────────────┐
│ Pod                             │
│  ┌──────────┐  ┌─────────────┐ │
│  │  App     │  │  Envoy      │ │  ← Sidecar proxy
│  │ Container│←→│  Proxy      │←──→ Rede
│  └──────────┘  └─────────────┘ │
└─────────────────────────────────┘

FUNCIONALIDADES DO SERVICE MESH:
- mTLS automático entre serviços (zero-trust networking)
- Retry, timeout, circuit breaker (configuráveis via YAML)
- Canary deployment com traffic splitting (90/10)
- Rate limiting por serviço
- Observabilidade (métricas, traces) sem instrumentação
- Fault injection para chaos engineering

QUANDO USAR:
- Muitos microsserviços (>10) com comunicação intensa
- Requisitos fortes de segurança (mTLS obrigatório)
- Necessidade de traffic management avançado
- NÃO usar para poucos serviços — overhead de operação alto

Architecture Decision Records (ADRs)

# ADR 001: Usar PostgreSQL como banco de dados primário

## Status
Aceito

## Contexto
Precisamos escolher um banco de dados relacional para a aplicação principal.
O time tem experiência com PostgreSQL e MySQL. A aplicação tem requisitos de:
- JSONB para dados semi-estruturados
- Full-text search básico
- Transações complexas com múltiplas tabelas
- Estimativa de crescimento: 50M linhas em 2 anos

## Decisão
Usar PostgreSQL 16 como banco de dados primário, hospedado no Amazon RDS.

## Alternativas Consideradas

### MySQL 8
- Prós: familiar para parte do time, boa performance para reads simples
- Contras: JSONB inferior, sem CTEs recursivas otimizadas, window functions
  menos maduras, partitioning mais limitado

### DynamoDB
- Prós: serverless, auto-scaling, baixa latência consistente
- Contras: modelo relacional necessário, queries ad-hoc difíceis,
  custo imprevisível para workloads de scan

## Consequências
- Time precisa se atualizar em features específicas do PostgreSQL 16
- Adotar PgBouncer para connection pooling
- Monitorar com pg_stat_statements desde o início
- Backups configurados com pgBackRest + WAL archiving para S3
- Custo estimado de RDS: ~$150/mês (db.r6g.large, Multi-AZ)

## Data
2024-01-15
ESTRUTURA DE ADRs NO REPOSITÓRIO:
docs/
├── adr/
│   ├── 0001-usar-postgresql.md
│   ├── 0002-adotar-event-driven.md
│   ├── 0003-migrar-para-typescript.md
│   ├── 0004-estrategia-de-cache.md
│   └── template.md

DICAS:
- Numerar sequencialmente (imutável)
- Status: Proposto → Aceito → Deprecado → Substituído por ADR-XXX
- Incluir alternativas consideradas (mostra que houve análise)
- Incluir consequências (positivas E negativas)
- Curto e direto (1-2 páginas)
- Revisado pelo time (PR no repositório)
- Ferramenta: adr-tools (CLI para gerenciar ADRs)

Trade-offs: CAP e PACELC

TEOREMA CAP (Brewer):
Em um sistema distribuído, durante uma partição de rede (P),
você só pode ter Consistência (C) ou Disponibilidade (A), não ambos.

┌─────────────────────────────────────┐
│              CAP                     │
│                                      │
│    C ────────── A                    │
│     \          /                     │
│      \        /                      │
│       \      /                       │
│        \    /                        │
│         \  /                         │
│          P                           │
│                                      │
│  CP: PostgreSQL, MongoDB (default)   │
│      Prefere consistência, rejeita   │
│      requests durante partição       │
│                                      │
│  AP: Cassandra, DynamoDB             │
│      Prefere disponibilidade,        │
│      aceita dados stale              │
│                                      │
│  CA: não existe em sistemas          │
│      distribuídos reais              │
└─────────────────────────────────────┘

PACELC (extensão do CAP):
Se há Partição (P): escolher entre Availability e Consistency (AC)
Else (E): escolher entre Latency e Consistency (LC)

Exemplos:
- PostgreSQL (primary-replica): PA/EC — durante partição perde A,
  normalmente prioriza C (consistência forte)
- DynamoDB: PA/EL — durante partição mantém A (eventual consistency),
  normalmente prioriza L (baixa latência)
- Cassandra: PA/EL — configurable consistency (QUORUM, ONE, ALL)
- CockroachDB: PC/EC — sempre consistente, aceita maior latência

NA PRÁTICA:
A maioria das aplicações web funciona bem com:
- Writes: consistência forte (transações ACID no primário)
- Reads: consistência eventual aceitável (ler de réplicas)
- Padrão: read-your-own-writes (ler do primário logo após escrever)

Documentação: C4 Model

C4 MODEL (Simon Brown) — 4 níveis de zoom:

NÍVEL 1 — SYSTEM CONTEXT:
"Quais sistemas existem e como se relacionam?"

┌─────────┐     ┌──────────────────┐     ┌──────────┐
│ Usuário │ ──→ │ E-commerce       │ ──→ │ Stripe   │
│ (pessoa)│     │ System           │     │ (externo)│
└─────────┘     │                  │ ──→ ├──────────┤
                │                  │     │ SendGrid │
                └──────────────────┘     │ (externo)│
                        │                └──────────┘

                ┌──────────────────┐
                │ Analytics System │
                │ (outro sistema)  │
                └──────────────────┘

NÍVEL 2 — CONTAINER (não Docker, mas componentes deployáveis):
"Dentro do sistema, quais são os containers?"

┌─────────────────────────────────────────────┐
│              E-commerce System               │
│                                              │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐ │
│  │ SPA      │  │ API      │  │ Worker    │ │
│  │ (React)  │→ │ (Node.js)│→ │ (Node.js) │ │
│  └──────────┘  └────┬─────┘  └─────┬─────┘ │
│                     │              │        │
│               ┌─────┴──────┐ ┌─────┴─────┐ │
│               │ PostgreSQL │ │  RabbitMQ  │ │
│               │ (Database) │ │  (Broker)  │ │
│               └────────────┘ └───────────┘ │
└─────────────────────────────────────────────┘

NÍVEL 3 — COMPONENT:
"Dentro de um container, quais são os componentes?"

┌─────────────────────────────────────────┐
│              API (Node.js)               │
│                                          │
│  ┌──────────────┐  ┌─────────────────┐  │
│  │ Auth         │  │ Order           │  │
│  │ Controller   │  │ Controller      │  │
│  └──────┬───────┘  └──────┬──────────┘  │
│         │                 │             │
│  ┌──────┴───────┐  ┌──────┴──────────┐  │
│  │ Auth         │  │ Order           │  │
│  │ Service      │  │ Service         │  │
│  └──────┬───────┘  └──────┬──────────┘  │
│         │                 │             │
│  ┌──────┴───────┐  ┌──────┴──────────┐  │
│  │ User         │  │ Order           │  │
│  │ Repository   │  │ Repository      │  │
│  └──────────────┘  └────────────────┘  │
└─────────────────────────────────────────┘

NÍVEL 4 — CODE (opcional, geralmente desnecessário):
Diagramas UML de classes. Gerado automaticamente do código.

FERRAMENTAS:
- Structurizr (do Simon Brown): DSL para C4, gera diagramas
- Mermaid: diagramas em Markdown (integra com GitHub)
- PlantUML: linguagem textual para diagramas
- draw.io/diagrams.net: ferramenta visual gratuita
- arc42: template de documentação de arquitetura

Fitness Functions e Arquitetura Evolutiva

FITNESS FUNCTIONS — testes automatizados para decisões arquiteturais:

"Se a arquitetura é importante, por que não testamos ela automaticamente?"

Exemplos de fitness functions:
// 1. ACOPLAMENTO — módulos não devem depender de internals de outros:
// Teste: verificar que módulo "orders" não importa de "users/internal"
test('Orders module não depende de internals de Users', () => {
  const orderFiles = glob.sync('src/modules/orders/**/*.ts');
  for (const file of orderFiles) {
    const content = fs.readFileSync(file, 'utf-8');
    expect(content).not.toMatch(/from ['"].*\/users\/internal/);
    expect(content).not.toMatch(/from ['"].*\/users\/repositories/);
  }
});

// 2. PERFORMANCE — latência p99 não deve regredir:
test('API p99 latency < 200ms', async () => {
  const results = await runLoadTest({
    url: 'https://staging.example.com/api/products',
    duration: '60s',
    rate: 100,
  });
  expect(results.latency.p99).toBeLessThan(200);
});

// 3. DEPENDÊNCIAS — limitar dependências transitivas:
test('Bundle size < 500KB', () => {
  const stats = JSON.parse(fs.readFileSync('dist/stats.json', 'utf-8'));
  const totalSize = stats.assets.reduce((sum, a) => sum + a.size, 0);
  expect(totalSize).toBeLessThan(500 * 1024);
});

// 4. CAMADAS — controller não acessa repository diretamente:
test('Controllers não importam repositories diretamente', () => {
  const controllerFiles = glob.sync('src/**/controllers/**/*.ts');
  for (const file of controllerFiles) {
    const content = fs.readFileSync(file, 'utf-8');
    expect(content).not.toMatch(/Repository/);
  }
});

// 5. COBERTURA — código crítico deve ter cobertura mínima:
// jest.config.js:
// coverageThreshold: {
//   './src/modules/payments/': { branches: 90, functions: 95, lines: 95 },
//   './src/modules/auth/':     { branches: 85, functions: 90, lines: 90 },
// }
ARQUITETURA EVOLUTIVA (Neal Ford, Rebecca Parsons):

Princípios:
1. Mudanças incrementais: pequenas mudanças frequentes > grandes reescritas
2. Fitness functions: validar decisões automaticamente
3. Deployment pipelines: automatizar validação de cada mudança
4. Acoplamento adequado: módulos com interfaces claras e estáveis

Exemplo de evolução:
Monólito → Modular Monolith → Microsserviços (quando necessário)

Fase 1: Monólito (time de 3-5)
  Simples, um deploy, transações ACID, sem overhead de rede.

Fase 2: Modular Monolith (time de 5-15)
  Módulos com interfaces públicas, schemas separados, testes de contrato.
  Fitness function: nenhum módulo acessa tabelas de outro módulo.

Fase 3: Extrair primeiro microsserviço (gargalo identificado)
  O módulo que precisa escalar independentemente vira serviço.
  Comunicação via eventos (event-driven) ou API.
  Manter monólito para o resto — NÃO migrar tudo de uma vez.

Gestão de Dívida Técnica

DÍVIDA TÉCNICA (Ward Cunningham):
"Dívida técnica é como dívida financeira: acelera no curto prazo
mas paga juros enquanto não for quitada."

QUADRANTE DE DÍVIDA (Martin Fowler):
┌─────────────────┬─────────────────────────┐
│                 │   Deliberada            │
│   Prudente      │   "Sabemos que isso     │
│                 │    não é ideal, mas      │
│                 │    precisamos entregar"  │
│                 │   → Tech debt planejada  │
├─────────────────┼─────────────────────────┤
│                 │   Deliberada            │
│   Imprudente    │   "Não temos tempo      │
│                 │    para design"          │
│                 │   → Preguiça/negligência │
├─────────────────┼─────────────────────────┤
│                 │   Inadvertida           │
│   Prudente      │   "Agora que terminamos,│
│                 │    sabemos como deveria  │
│                 │    ter sido feito"       │
│                 │   → Aprendizado natural  │
├─────────────────┼─────────────────────────┤
│                 │   Inadvertida           │
│   Imprudente    │   "O que é layered      │
│                 │    architecture?"        │
│                 │   → Falta de competência │
└─────────────────┴─────────────────────────┘

GESTÃO PRÁTICA:
1. IDENTIFICAR: code review, métricas (complexidade ciclomática,
   cobertura, frequência de mudança, churn)
2. QUANTIFICAR: estimar custo de manter vs custo de refatorar
3. PRIORIZAR: usar Tech Debt Ratio = custo_remediar / custo_desenvolver
4. ALOCAR: dedicar 15-20% de cada sprint para reduzir dívida
5. PREVENIR: fitness functions, standards, code review

FERRAMENTAS DE ANÁLISE:
- SonarQube: análise estática, debt estimation
- CodeScene: análise comportamental (hotspots, churn)
- Code Climate: qualidade e manutenibilidade
- Métricas de código: complexidade ciclomática, fan-in/fan-out, instabilidade

Governança de Arquitetura

COMO GARANTIR QUE DECISÕES ARQUITETURAIS SÃO SEGUIDAS:

1. ADRs — documentar decisões e razões
   Cada decisão significativa tem um ADR no repositório
   Revisado pelo time em PR (como qualquer código)

2. FITNESS FUNCTIONS — validar automaticamente
   Rodam no CI/CD como parte do pipeline
   Se a fitness function falhar, o build falha

3. TECH RADAR — comunicar tecnologias adotadas/em avaliação
   Adopt | Trial | Assess | Hold
   Atualizado trimestralmente com o time

4. RFCS (Request for Comments) — propostas de mudança significativa
   Documento público que o time inteiro pode comentar
   Deadline para comentários, depois decisão documentada em ADR

5. ARCHITECTURAL KATAS — prática deliberada
   Time resolve problemas de arquitetura fictícios
   Desenvolve músculo de trade-off analysis

6. CODE REVIEW COM FOCO ARQUITETURAL:
   Checklist:
   □ Segue os padrões definidos nos ADRs?
   □ Módulo acessa apenas interfaces públicas de outros módulos?
   □ Dependências externas adicionadas estão no Tech Radar (Adopt/Trial)?
   □ Testes de contrato atualizados para mudanças de API?
   □ Performance: query N+1? Chamada síncrona desnecessária?

7. PLATFORM TEAM — time dedicado a:
   - Manter templates de projetos (scaffolding)
   - Prover ferramentas de observabilidade
   - Gerenciar shared libraries e SDKs internos
   - Definir e manter golden paths (caminho recomendado)

Referências e Fontes

  • “Clean Architecture” — Robert C. Martin — princípios de arquitetura e a regra de dependência
  • “Fundamentals of Software Architecture” — Mark Richards & Neal Ford — características arquiteturais, estilos e trade-offs
  • “Building Evolutionary Architectures” — Ford, Parsons, Kua — fitness functions e arquitetura evolutiva
  • “Software Architecture: The Hard Parts” — Ford, Richards et al. — decisões difíceis em arquiteturas distribuídas
  • “Hexagonal Architecture” — Alistair Cockburn, 2005 — https://alistair.cockburn.us/hexagonal-architecture/
  • C4 Model — Simon Brown — https://c4model.com/
  • ADR GitHubhttps://adr.github.io/ — templates e ferramentas para Architecture Decision Records
  • ThoughtWorks Technology Radarhttps://www.thoughtworks.com/radar