Arquitetura Moderna: trade-offs, ADRs, C4 e evolução
Arquitetura é a arte de organizar trade-offs. Não existe “melhor arquitetura” absoluta: existe a escolha mais honesta dadas as restrições atuais (time, domínio, escala, dinheiro) e as mudanças previsíveis. Em 2026, com agents gerando código e distribuindo stack por 10 vendors, o papel do arquiteto sênior não é desenhar UML bonito — é decidir bem, documentar com ADR, comunicar com C4 e defender a qualidade com fitness functions.
Quatro forças a balancear
Regra de Gall. “Todo sistema complexo que funciona evoluiu de um sistema simples que funcionava. Tentar projetar sistema complexo do zero nunca funciona.” Comece pequeno, evolua por sinal.
Monolito modular: o padrão negligenciado
Um monolito bem modularizado é muitas vezes a melhor arquitetura até ~100 devs. Um binário, um deploy, um banco. Por dentro, módulos com fronteira clara.
# Exemplo de monolito modular em Node (estrutura típica)
src/
├── modules/
│ ├── billing/
│ │ ├── domain/ # entities, value objects, business rules
│ │ ├── use-cases/ # application services
│ │ ├── adapters/ # in/out: HTTP controller, DB repo, events
│ │ └── index.ts # ONLY public API of this module
│ ├── catalog/
│ │ └── ... mesma estrutura
│ ├── identity/
│ │ └── ...
│ └── shared/
│ └── ... # kernel: errors, clock, logger, IDs
├── infrastructure/
│ ├── db/ # postgres client, migrations
│ ├── queue/ # sqs/rabbit/kafka client
│ └── http/ # fastify app setup
├── main.ts # composition root
└── fitness/ # testes arquiteturaisRegra do monolito modular. Módulos comunicam entre si SÓ pela API pública (), nunca por import direto de “internals”. Fitness function garante isso.
Microserviços: quando fazem sentido
| Sinal | Monolito modular | Microserviços |
|---|---|---|
| Time | 1-50 engs | 50+ distribuídos em múltiplos squads |
| Deploy cadence | Diário/semanal | Várias vezes por dia por serviço |
| Domínios independentes | Pouco | Muitos (payments, catalog, inventory...) |
| Ops capability | Baixa-média | Alta (SRE, observabilidade, gitops) |
| Acoplamento de mudança | Aceitável | Ruim — mesma feature toca 5 serviços |
| Stack heterogênea | Não precisa | Uma linguagem por serviço OK |
| Custo de infra | Baixo | Alto (rede, observabilidade, versioning) |
| Quando migrar | Quando dor organizacional > custo de distribuir | Depois do monolito comprovar o domínio |
Anti-padrão do hype. Começar projeto novo já em microserviços “porque é moderno” é receita pra fracasso. Você paga o custo de distribuição antes de entender o domínio — e o domínio ainda vai mudar 10 vezes.
DDD pragmático
ADR: Architecture Decision Record
# ADR 0007: Adotar monolito modular para o backend V1
**Status:** Accepted
**Data:** 2026-04-16
**Deciders:** @fernando, @maria, @joao
## Contexto
Vamos começar o backend do produto Y. Time atual: 4 engs. Domínio
claro (CRM para pequenas empresas) mas ainda imaturo. Prazo: MVP em
3 meses.
## Opções consideradas
1. Monolito tradicional (tudo misturado)
2. Monolito modular (módulos com fronteira, 1 deploy)
3. Microserviços desde o dia 1
4. Serverless first (Lambda + DynamoDB)
## Decisão
Opção 2: monolito modular. Node 20 + Fastify + Postgres + Redis.
Módulos: identity, billing, deals, contacts.
## Justificativa
- Time pequeno: operar >1 deploy seria overhead.
- Domínio imaturo: muita fronteira vai mudar; modular reduz custo de
reorganizar.
- Prazo curto: otimizar pra velocidade de aprender, não pra escala.
- Quando crescer (100k users ou 15+ devs): reavaliamos em novo ADR.
## Consequências
- + Simplicidade operacional.
- + Uma stack só.
- + Consistência transacional dentro do monolito.
- - Escalabilidade horizontal por módulo mais difícil.
- - Release acoplado entre módulos (ao menos para V1).
## Referências
- "Modular monolith" (Kamil Grzybek)
- Shopify case study
- Related ADRs: 0005 (stack), 0006 (Postgres)Onde armazenar. no repo, numerado. Use se quiser CLI (). Status muda via novo ADR que “supersede” o anterior — nunca reescreva history.
Modelo C4: comunicar em 4 zooms
@startuml
!include <C4/C4_Container>
Person(user, "Cliente", "Usuário final da loja")
System_Boundary(store, "E-commerce") {
Container(web, "Web app", "Next.js", "UI pública")
Container(api, "API", "Fastify/Node", "Regras de negócio")
ContainerDb(db, "Postgres", "RDS Aurora", "Pedidos, usuários, catálogo")
Container(queue, "Queue", "SQS", "Eventos de pagamento")
Container(worker, "Worker", "Node", "Processa pagamentos async")
}
System_Ext(stripe, "Stripe", "Gateway de pagamento")
Rel(user, web, "usa", "HTTPS")
Rel(web, api, "chama", "HTTPS/JSON")
Rel(api, db, "lê/escreve", "SQL")
Rel(api, queue, "publica", "AWS SDK")
Rel(worker, queue, "consome", "AWS SDK")
Rel(worker, stripe, "chama", "HTTPS")
@endumlFerramentas. (oficial, texto), com C4 macros (em código), (drag-and-drop), (typescript-based). Escolha o que seu time consegue manter — diagrama desatualizado mente.
Fitness Functions
Exemplo real em um monorepo TypeScript — teste arquitetural que roda no CI:
// fitness/no-cross-module-imports.test.ts
import { describe, it, expect } from 'vitest';
import fg from 'fast-glob';
import fs from 'node:fs/promises';
import path from 'node:path';
// Regra: módulo X só pode importar de shared/ ou do próprio X.
// Nunca de internals de módulo Y.
describe('fitness: module boundaries', () => {
const modules = ['billing', 'catalog', 'identity'];
for (const mod of modules) {
it(`${mod} does not import other modules' internals`, async () => {
const files = await fg(`src/modules/${mod}/**/*.ts`);
const violations: string[] = [];
for (const f of files) {
const src = await fs.readFile(f, 'utf8');
const m = src.match(/from ['"]@\/modules\/([a-z]+)\/(?!index['"])[^'"]+['"]/g);
if (m) violations.push(`${f}: ${m.join(', ')}`);
}
expect(violations).toEqual([]);
});
}
});
// fitness/build-time.test.ts
import { execSync } from 'node:child_process';
it('build completes in under 90 seconds', () => {
const t0 = Date.now();
execSync('npm run build', { stdio: 'ignore' });
const elapsed = Date.now() - t0;
expect(elapsed).toBeLessThan(90_000);
});Estratégias de evolução (sem big-rewrite)
Cenários reais de decisão
📋 Startup série A, 8 engs, domínio CRM ainda fluido
Otimize pra aprender. Monolito modular com módulos bem delimitados acomoda mudança de modelo. ADRs garantem que não repetem discussões. C4 comunica com novos devs e investidores.
Alt: Microserviços —
📋 Empresa com monolito de 8 anos em Rails; 80 devs; deploys lentos; features atrasam
Não reescreva tudo. Identifique os 2-3 bounded contexts com mais dor (ex.: billing, fulfillment), extraia para serviços separados via strangler. Reduza custo operacional desses serviços com platform interna.
Alt: Big-rewrite —
📋 SaaS B2B multi-tenant crescendo, time querendo adicionar “IA” por todo lado
Eventos de domínio ficam em um bus (Kafka/EventBridge). Features de IA consomem eventos e publicam resultados. AI Gateway (Kong AI, LiteLLM) centraliza chamadas a modelos, controla custo, auditoria e fallback. Evita IA colada com fita em 10 lugares.
Princípios pra durar
- • Simplicity beats cleverness. Cada camada de abstração cobra imposto eternamente.
- • Conway's Law. Arquitetura reflete comunicação do time. Mude org antes de mudar arquitetura.
- • Premature optimization is the root... ainda vale. Meça antes de otimizar.
- • Postel's Law. Seja liberal no que aceita, conservador no que envia.
- • YAGNI. You Ain't Gonna Need It. Feature flag, cache, retry só quando a dor surgir.
- • DRY com moderação. 3 repetições > 1 abstração errada. Abstração errada é mais cara que copiar.
- • Boundaries preservam opções. Repositório bem isolado permite trocar Postgres por Dynamo. Módulo bem fechado permite extrair pra serviço.
- • Arquitetura é viva. Revise ADRs principais a cada 6-12 meses. Decisões envelhecem.
Perguntas típicas
❓ Preciso de arquiteto dedicado?
❓ CQRS, Event Sourcing valem a pena?
❓ Serverless é sempre mais barato?
❓ Como evito “architecture astronauts” no meu time?
❓ Agent pode propor arquitetura?
Take-aways. (1) Arquitetura é trade-off consciente, não “melhor prática” absoluta. (2) Monolito modular ainda é o padrão default abaixo de ~50 engs. (3) Microserviços quando o custo organizacional do monolito passar o custo da distribuição. (4) ADR registra o “por quê”; C4 comunica o “o quê”; fitness function defende o “ainda vale”. (5) Evolua por strangler fig, nunca por big-rewrite. (6) Trilha fechada — você saiu do “coder” e virou engenheiro.
Próximos passos sugeridos
CAP e PACELC: o teorema que define toda arquitetura distribuída
Sistemas Distribuídos · 16 min · +80 XP
Observability: os 3 pilares (logs, métricas, traces) e por que não basta
Observabilidade & SRE · 15 min · +75 XP
Capstone: refactor grande respeitando trilha
Engenharia de Software Moderna · 20 min · +90 XP
Discussão
Carregando…