🧠FFVAcademy
🏛️

Arquitetura Moderna: trade-offs, ADRs, C4 e evolução

20 min de leitura·+95 XP
Pré-requisitos (0/1)0%

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

1. TimeTamanho, expertise, localização. Microserviços exige maturidade operacional.
2. DomínioSimples (CRUD) ou complexo (regras de negócio fragmentadas). DDD ajuda no complexo; exagero no simples.
3. EscalaUsuários, tráfego, dados. Prematuramente otimizar pra escala fantasma é o maior erro.
4. Dinheiro/PrazoStartup com 6 meses de runway: simplicidade vence elegância. Corp com 5 anos: debt vale revisitar.
💡
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.

bash
# 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
⚠️
Regra do monolito modular. Módulos comunicam entre si SÓ pela API pública (index.ts), nunca por import direto de “internals”. Fitness function garante isso.

Microserviços: quando fazem sentido

SinalMonolito modularMicroserviços
Time1-50 engs50+ distribuídos em múltiplos squads
Deploy cadenceDiário/semanalVárias vezes por dia por serviço
Domínios independentesPoucoMuitos (payments, catalog, inventory...)
Ops capabilityBaixa-médiaAlta (SRE, observabilidade, gitops)
Acoplamento de mudançaAceitávelRuim — mesma feature toca 5 serviços
Stack heterogêneaNão precisaUma linguagem por serviço OK
Custo de infraBaixoAlto (rede, observabilidade, versioning)
Quando migrarQuando dor organizacional > custo de distribuirDepois 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

Ubiquitous languageMesmos termos entre negócio e código. Se o time diz "carrinho", código diz Cart, não ShoppingBasket.
Bounded contextFronteira de linguagem. "Customer" em Billing pode significar algo diferente de "Customer" em Catalog. Cada contexto é um módulo.
AggregateGrupo de entidades com invariante comum e uma raiz. Transações respeitam o aggregate.
Value objectObjeto sem identidade (Money, Email, Address). Imutável, comparação por valor.
Domain eventFato do passado ("OrderPlaced"). Outros contexts reagem.
RepositoryAbstrai persistência. Domain não sabe de SQL.
Onde NÃO aplicarCRUD simples sem regra de negócio. DDD tem overhead — só paga em domínio complexo.

ADR: Architecture Decision Record

markdown
# 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. 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

🌍C1 Contextquem usa o sistema
Diagrama de alto nível: sistema + atores humanos + sistemas externos. 1 por produto.
zoom in
📦C2 Containero que se deploya
Web app, API, banco, queue, worker. 1 por sistema. Mostra protocolos entre containers.
zoom in
🧩C3 Componentdentro do container
(Se útil) controllers, services, repositories de 1 container. 1 por container complexo.
zoom in
⌨️C4 Coderaro
Classes/funções. Raramente vale o esforço — IDE já mostra. Pule em 99% dos casos.
plantuml
@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")
@enduml
Ferramentas. Structurizr 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:

typescript
// 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)

Strangler Fig (Fowler)Nova impl cresce em volta da antiga. Reverse proxy roteia gradualmente. Desliga a velha só quando zero tráfego. Zero downtime.
Branch by AbstractionCria interface entre código antigo e novo. Implementações coexistem. Troca por feature flag.
Parallel RunRoda impl nova em paralelo com a antiga, compara resultados (em especial em migração de pricing, scoring, risk).
Event InterceptionCaptura eventos do sistema antigo, alimenta o novo. Útil pra CDC (change data capture) em migração de banco.
Anti-Corruption LayerCamada de tradução entre sistemas com modelos incompatíveis. Protege o domínio novo de legado sujo.
Big-rewriteQuase sempre fracasso. Só quando o sistema não tem como evoluir (tech morto, security insolúvel) e orçamento+time comprovadamente gigantes.

Cenários reais de decisão

📋 Startup série A, 8 engs, domínio CRM ainda fluido

Monolito modular + ADRs + C4

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çoscusto operacional mata a startup antes do PMF.

📋 Empresa com monolito de 8 anos em Rails; 80 devs; deploys lentos; features atrasam

Strangler Fig + Bounded Contexts

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-rewritelore de fracasso. Evite a todo custo.

📋 SaaS B2B multi-tenant crescendo, time querendo adicionar “IA” por todo lado

Arquitetura orientada a eventos + AI gateway

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?

Em time < 30 devs, não. Um ou dois sêniores conduzem as decisões via ADR + RFC. Em time maior, um Tech Lead/Staff por domínio + guilda de arquitetura faz mais sentido que papel top-down.

CQRS, Event Sourcing valem a pena?

Em domínio complexo com auditoria crítica (pagamentos, trading, healthcare) — sim. Em CRUD normal — overhead. Aplique onde paga.

Serverless é sempre mais barato?

Não. Spike curto: sim. Carga estável e alta: EC2/K8s ficam baratos. Faça cálculo (lambda pricing + requests/mês + duração) antes de adotar como padrão.

Como evito “architecture astronauts” no meu time?

Prazo curto + demonstrar valor rápido + fitness functions medindo simplicidade (linhas, profundidade de call, tempo de build). Arquiteto que não escreve código perde contato com dor real.

Agent pode propor arquitetura?

Pode (vimos architect subagent). Útil pra levantar opções, trade-offs, referências. Decisão final é humana e registrada em ADR. Quem responde pelo sistema é pessoa, não modelo.
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.
🧩

Quiz rápido

4 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito

Continue lendo