Arquitetura Moderna: trade-offs, ADRs, C4 e evolução
- ⬜🛡️ Segurança de Software de Verdade: threat model ao SBOM(Engenharia de Software Moderna)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
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
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 arquiteturais
index.ts), 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 |
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)
docs/adr/NNNN-titulo.md no repo, numerado. Use adr-tools se quiser CLI (adr new "titulo"). 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")
@endumlStructurizr DSL (oficial, texto), PlantUML com C4 macros (em código), diagrams.net (drag-and-drop),likec4 (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 — custo operacional mata a startup antes do PMF.
📋 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 — lore de fracasso. Evite a todo custo.
📋 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?
Quiz rápido
4 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito