Apostila completa de Arquitetura de Software & System Design
Escrever a função certa é ofício; escolher onde ficam as fronteiras, como os serviços conversam, o que acontece quando um deles cai e como o sistema cresce 100× é arquitetura — e é isso que separa o desenvolvedor pleno do sénior. Esta apostila vai dos fundamentos (acoplamento, coesão, estilos) aos sistemas distribuídos (saga, CQRS, consistência eventual) e à entrevista de system design, com um framework replicável e exemplos resolvidos.
O que é arquitetura e por que ela importa
Objetivo: definir arquitetura de forma útil, entender atributos de qualidade e trade-offs, e o que muda no papel de quem "faz arquitetura".
1.1 Uma definição que serve
Há dezenas de definições acadêmicas. A mais prática, atribuída a Ralph Johnson e popularizada por Martin Fowler: "arquitetura é o conjunto de decisões que você gostaria de ter acertado cedo — as coisas que são difíceis e caras de mudar depois". Onde ficam as fronteiras entre módulos, se há um ou vinte bancos de dados, se a comunicação é síncrona ou por eventos, qual a estratégia de consistência — mudar qualquer uma dessas em um sistema grande custa meses.
Não há uma linha nítida, mas uma régua útil: arquitetura é o que afeta a estrutura, os atributos de qualidade e é caro reverter (estilo, fronteiras, comunicação, dados). Design é o que fica dentro de um componente e é barato refatorar (padrões de classe, nome de função, estrutura de um módulo). Um bom arquiteto empurra decisões para o lado "barato de mudar" sempre que possível — adiar é uma estratégia.
1.2 Atributos de qualidade (os "-ilities")
Requisitos funcionais dizem o que o sistema faz. Atributos de qualidade dizem quão bem — e é o que a arquitetura serve. Os principais:
| Atributo | Pergunta que responde |
|---|---|
| Escalabilidade | Aguenta 10×/100× a carga? A que custo? |
| Disponibilidade | Quanto tempo fora do ar por ano é tolerável? (99,9% = ~8,7h/ano) |
| Desempenho / latência | Responde rápido o suficiente no p95/p99? |
| Confiabilidade / resiliência | O que acontece quando uma dependência falha? |
| Manutenibilidade / evolutibilidade | Quão fácil é mudar sem quebrar? Quão rápido um time novo entende? |
| Segurança | Superfície de ataque, isolamento, dados protegidos |
| Observabilidade | Dá para saber o que está acontecendo em produção? |
| Custo | Cabe no orçamento hoje e quando escalar? |
1.3 Tudo é trade-off
A frase mais importante da apostila: "depende" é a resposta profissional. Não existe arquitetura "melhor" — existe a mais adequada às forças do seu contexto. Microservices compram escalabilidade e autonomia de time ao custo de complexidade operacional, latência de rede e consistência distribuída. Cache compra latência ao custo de invalidação e possível dado velho. Consistência forte compra correção ao custo de disponibilidade sob partição (ver apostila de NoSQL, teorema CAP).
Adotar microservices, Kafka, service mesh e multi-região com 3 desenvolvedores e 200 usuários é a receita clássica de matar um produto por over-engineering. A arquitetura certa para a maioria dos sistemas é a mais simples que atende os requisitos atuais e não impede os próximos passos previsíveis. Comece simples; adicione complexidade quando a dor for real.
1.4 O papel de quem faz arquitetura
- Identificar os atributos de qualidade que importam para este sistema e negociá-los com o negócio (não dá para maximizar todos).
- Decidir a estrutura e as fronteiras, e documentar o porquê (ADRs — Módulo 9).
- Manter a integridade conceitual: o sistema deve parecer projetado por uma mente só, mesmo feito por muitas.
- Reduzir risco cedo: provar as decisões arriscadas com spikes/protótipos antes de comprometer o time.
- Continuar codando o suficiente para não perder contato com a realidade ("arquiteto de PowerPoint" não funciona).
Domínio de arquitetura é o que destrava as faixas sénior / staff / principal e as vagas de "Solutions Architect". Nas entrevistas, a competência avaliada quase nunca é "você conhece o padrão X?", e sim "você raciocina sobre trade-offs e faz as perguntas certas antes de desenhar?". A resposta "depende — quais são os requisitos de consistência e escala?" vale mais que qualquer diagrama pronto.
✏️ Exercício 1 — Nomeie os atributos
Para cada sistema, quais 2–3 atributos de qualidade são dominantes (não "todos importam")? (a) sistema de trading de alta frequência; (b) app interno de RH de uma empresa de 500 pessoas; (c) plataforma de streaming de vídeo global; (d) prontuário eletrônico hospitalar.
Gabarito: (a) Latência (microssegundos) e determinismo; disponibilidade importa mas latência manda. (b) Manutenibilidade e custo baixo; escala é trivial. (c) Escalabilidade, disponibilidade e desempenho de entrega (throughput, CDN); custo por stream. (d) Confiabilidade, consistência/integridade dos dados, segurança/privacidade e auditabilidade; latência é secundária. O ponto: arquiteturas diferentes porque as forças dominantes são diferentes.
Fundamentos: acoplamento, coesão e fronteiras
Objetivo: dominar os conceitos que valem em qualquer estilo — acoplamento e coesão, inversão de dependência, a Lei de Conway e a diferença entre complexidade essencial e acidental.
2.1 Acoplamento e coesão
- Coesão (alta é bom): o quanto os elementos dentro de um módulo pertencem juntos. Um módulo coeso tem uma responsabilidade clara; muda por um motivo só.
- Acoplamento (baixo é bom): o quanto um módulo depende de outro. Baixo acoplamento = mudar A raramente obriga a mudar B.
Praticamente toda "boa arquitetura" é uma forma de buscar alta coesão dentro das fronteiras e baixo acoplamento entre elas. Tipos de acoplamento, do pior ao mais tolerável: de conteúdo (mexe nas entranhas do outro) → comum (estado global compartilhado) → de controle (passa flag que dita o comportamento) → de dados (troca só os dados necessários por uma interface).
2.2 Fronteiras e inversão de dependência
Uma fronteira é uma linha através da qual as dependências apontam em uma direção controlada. O princípio de inversão de dependência (o "D" de SOLID): módulos de alto nível (regra de negócio) não devem depender de módulos de baixo nível (banco, framework, API externa); ambos dependem de abstrações.
SEM inversão: [regra de negócio] ──depende──▶ [Postgres] [Stripe] [SendGrid]
(trocar de banco = reescrever a regra)
COM inversão: [regra de negócio] ──▶ [interface Repositório] ◀──implementa── [Postgres]
[regra de negócio] ──▶ [interface Pagamentos] ◀──implementa── [Stripe]
(a regra não conhece a tecnologia; adapters são plugáveis e testáveis)
É a base de arquitetura hexagonal / ports & adapters / clean architecture (Módulo 3): o núcleo de domínio no centro, sem dependência de infraestrutura; tudo que é I/O é um adaptador na borda.
2.3 Lei de Conway
"Organizações projetam sistemas que copiam a estrutura de comunicação da própria organização." (Melvin Conway, 1967.) Consequências práticas:
- Se você tem 4 times, vai acabar com ~4 componentes com interfaces onde os times se falam — queira você ou não.
- Inverse Conway Maneuver: organize os times do jeito que você quer que a arquitetura fique. Times autônomos por domínio de negócio ⇒ serviços autônomos por domínio.
- Uma fronteira arquitetural que corta no meio de um time gera atrito permanente; alinhe fronteiras de código com fronteiras de time.
2.4 Complexidade essencial × acidental
De "No Silver Bullet" (Fred Brooks): a complexidade essencial é a do problema em si (um sistema de logística é complexo). A acidental é a que você adiciona com ferramentas, camadas e abstrações mal escolhidas. Bom arquiteto ataca a acidental sem fingir que elimina a essencial. Toda camada, todo serviço, toda tecnologia nova é complexidade acidental que precisa se pagar.
Perguntas: "O que é acoplamento e coesão, com exemplo?", "Explique inversão de dependência e por que ela ajuda a testar", "O que é a Lei de Conway e como você a usa a favor?", "Como você decide onde colocar uma fronteira entre módulos?" (por domínio de negócio / capacidade, alinhado ao time, minimizando o acoplamento que sobra).
✏️ Exercício 2 — Encontre o acoplamento ruim
Um módulo de Relatórios importa diretamente o modelo ORM de Pedidos, lê colunas específicas, e quando o time de Pedidos renomeia uma coluna, os relatórios quebram em produção. Que tipo de acoplamento é esse e como reduzir?
Gabarito: Acoplamento de conteúdo/estrutura — Relatórios depende do schema interno de Pedidos. Reduzir: Pedidos expõe uma interface estável (uma API, uma view de leitura, um evento com contrato versionado, ou um DTO) e Relatórios consome só isso. A mudança de coluna interna passa a ser invisível para fora. Se relatórios precisam de dados de vários domínios, considerar um modelo de leitura próprio alimentado por eventos (CQRS — Módulo 5).
Estilos arquiteturais
Objetivo: conhecer os estilos mais usados — o que cada um otimiza, o que custa, e quando escolher — sem tratar nenhum como padrão-ouro universal.
3.1 Monólito (e o monólito modular)
Uma única unidade de deploy. Não é sinônimo de bagunça: um monólito modular tem fronteiras internas explícitas (módulos com interfaces claras, sem acesso ao banco um do outro), roda como um processo, e é a escolha certa para a esmagadora maioria dos sistemas no início e por muito tempo.
- Ganha: simplicidade operacional (um deploy, um banco, um repositório), transações locais (ACID de graça), refatoração fácil entre módulos, latência mínima, observabilidade trivial.
- Custa: escala como um bloco só; um bug de memória derruba tudo; deploy acopla os times; acima de certo tamanho, o build e o onboarding pesam.
3.2 Camadas (layered / n-tier)
Organização horizontal: apresentação → aplicação → domínio → persistência. Familiar e fácil de ensinar. Risco: virar um "monólito em camadas" onde toda feature atravessa todas as camadas e a de domínio depende da de persistência (acoplamento invertido). Combine com inversão de dependência.
3.3 Hexagonal / Ports & Adapters / Clean
Organização concêntrica: domínio puro no centro; "portas" (interfaces) ao redor; "adaptadores" (HTTP, banco, filas, APIs externas) na borda, dependendo para dentro. Ótimo para testabilidade (o domínio roda sem infraestrutura) e para trocar tecnologia sem tocar a regra. É um padrão interno — combina com monólito modular ou com cada microserviço.
3.4 Microservices
Vários serviços pequenos, cada um com seu deploy, seu time e (idealmente) seu banco, comunicando-se por rede.
| Ganha | Custa |
|---|---|
| Escala independente por serviço | Complexidade operacional (dezenas de deploys, service discovery, observabilidade distribuída) |
| Autonomia de time (deploy sem coordenar) | Consistência distribuída (sem transação ACID entre serviços — Módulo 5) |
| Isolamento de falha (um serviço cai, o resto segue) | Latência e falha de rede em toda chamada |
| Liberdade tecnológica por serviço | Testes de integração e debugging mais difíceis; custo de infra maior |
CI/CD maduro, automação de infra, observabilidade distribuída (tracing), cultura de on-call, e fronteiras de domínio bem entendidas. Sem isso, você troca a dor conhecida do monólito pela dor pior do monólito distribuído (serviços que precisam ser deployados juntos, chamadas síncronas em cadeia, um banco compartilhado escondido). Regra de ouro: comece monólito modular; extraia serviços quando a fronteira estiver óbvia e a dor for real.
3.5 Event-Driven Architecture (EDA)
Componentes reagem a eventos (fatos que aconteceram: PedidoPago, UsuárioCadastrado) publicados num broker (Kafka, RabbitMQ, SNS/SQS). O produtor não sabe (nem espera) quem consome.
- Ganha: desacoplamento forte (adicionar consumidor sem tocar o produtor), resiliência (o broker absorve picos e quedas), extensibilidade, auditoria natural (o log de eventos).
- Custa: fluxo difícil de seguir ("onde isso é tratado?"), consistência eventual, necessidade de idempotência e de lidar com ordem/duplicação, testes e debugging mais complexos.
- Sabores: notificação de evento (evento fino, consumidor busca o resto), event-carried state transfer (evento gordo, carrega os dados), event sourcing (o evento é a fonte de verdade — Módulo 5).
3.6 Serverless (FaaS)
Funções que sobem sob demanda, escalam a zero, você paga por invocação. Ótimo para cargas intermitentes, glue code, processamento orientado a evento, e times que não querem gerir servidor. Custa: cold start, limites de execução, lock-in, e custo alto em volume constante. Frequentemente combinado com EDA.
3.7 Um guia rápido de escolha
| Situação | Comece com |
|---|---|
| Produto novo, time pequeno, requisitos em descoberta | Monólito modular + hexagonal internamente |
| Monólito crescendo, times pisando no pé um do outro, partes com escala muito diferente | Extrair microserviços nas fronteiras óbvias (strangler — Módulo 9) |
| Muitos consumidores reagindo aos mesmos fatos; integrações que mudam sempre | Adicionar EDA para os fluxos assíncronos |
| Cargas esporádicas, picos imprevisíveis, glue entre serviços | Serverless para essas peças |
Perguntas: "Monólito ou microservices para uma startup em estágio inicial?" (monólito modular — a resposta oposta é red flag), "O que é um monólito distribuído e por que é o pior dos mundos?", "Trade-offs de EDA", "O que são ports and adapters?". Demonstrar que você não vai de microservices por reflexo é um sinal forte de senioridade.
✏️ Exercício 3 — Escolha e justifique
Uma fintech tem um monólito de 6 anos. O módulo de "cálculo de risco" precisa escalar 20× em picos e é mantido por um time dedicado; o resto do sistema tem carga estável. Qual sua recomendação?
Gabarito: Extrair apenas "cálculo de risco" como serviço independente (padrão strangler): a fronteira é clara (domínio coeso, time dedicado, exigência de escala distinta), e o ganho — escalar só essa peça, deploy independente — justifica o custo. Manter o resto como monólito modular. Comunicação: síncrona se o cálculo é no caminho da requisição (com timeout/circuit breaker), ou assíncrona se puder ser feito fora do fluxo. Evitar a tentação de "já que vamos, quebrar tudo em microserviços".
Comunicação entre serviços
Objetivo: escolher entre síncrono e assíncrono com consciência do acoplamento temporal, e conhecer os padrões de coordenação (orquestração vs coreografia) e de borda (gateway, BFF).
4.1 Síncrono × assíncrono
| Síncrono (REST, gRPC) | Assíncrono (mensagem/evento) | |
|---|---|---|
| Modelo | Chamo e espero a resposta | Publico e sigo em frente; alguém processa depois |
| Acoplamento temporal | Alto: o outro serviço precisa estar de pé agora | Baixo: o broker guarda a mensagem até dar |
| Latência do usuário | Soma a latência de toda a cadeia | Responde já; o trabalho acontece em background |
| Falha | Falha propaga na hora (precisa de timeout, retry, circuit breaker) | Broker absorve; reprocessa; mas exige idempotência e tratamento de ordem |
| Bom para | Preciso da resposta para continuar (consultar saldo, validar) | Notificar, propagar mudança, processar pesado, integrar N consumidores |
Se A chama B que chama C que chama D, todos síncronos, a disponibilidade de A é o produto das disponibilidades (4 serviços a 99,9% ⇒ ~99,6%), e a latência é a soma. Cada salto síncrono adicionado piora as duas coisas. Prefira assíncrono quando a resposta imediata não for necessária, e achate as cadeias.
4.2 REST × gRPC × GraphQL (resumo)
- REST/JSON: universal, cacheável, ótimo para APIs públicas e entre times heterogêneos.
- gRPC: binário (Protobuf), contrato forte, streaming, baixa latência — ótimo entre serviços internos de alta comunicação.
- GraphQL: o cliente pede exatamente os campos que quer; bom para agregar dados de várias fontes para front-ends variados; cuidado com N+1 e com cache. (Ver a apostila de API Design para o detalhe.)
4.3 Coordenação: orquestração × coreografia
Quando um processo de negócio cruza vários serviços (ex.: "fazer um pedido" toca estoque, pagamento, entrega):
| Orquestração | Coreografia |
|---|---|
| Um serviço "maestro" comanda: chama estoque, depois pagamento, depois entrega, e trata as falhas | Cada serviço reage a eventos: PedidoCriado → estoque reserva e emite EstoqueReservado → pagamento cobra e emite PagamentoAprovado → … |
| + Fluxo explícito e fácil de entender/monitorar; lógica centralizada | + Desacoplado; fácil adicionar um passo/consumidor novo |
| − O maestro vira ponto de acoplamento e de falha; pode virar "deus" | − Ninguém "vê" o fluxo inteiro; difícil depurar; risco de ciclos de evento |
Regra prática: processos de negócio complexos e com muitas regras de compensação ⇒ orquestração (com um motor de workflow tipo Temporal/Camunda se justificar). Propagação de fatos simples ⇒ coreografia.
4.4 Padrões de borda
- API Gateway: ponto único de entrada — roteamento, autenticação, rate limiting, agregação, TLS. Evita que cada cliente conheça a topologia interna.
- Backend for Frontend (BFF): um gateway/serviço por tipo de cliente (web, mobile, parceiro), que agrega e molda os dados sob medida — em vez de uma API genérica que serve mal a todos.
- Service Mesh (Istio, Linkerd): move retries, mTLS, observabilidade e roteamento para uma camada de infraestrutura (sidecar). Poderoso e complexo — só quando a quantidade de serviços justifica.
Perguntas: "Quando usar comunicação assíncrona em vez de síncrona?", "Por que cadeias de chamadas síncronas prejudicam a disponibilidade?", "Orquestração ou coreografia para um checkout?", "O que é um BFF e que problema resolve?". Falar em "acoplamento temporal" e no efeito multiplicativo na disponibilidade sinaliza profundidade.
✏️ Exercício 4 — Redesenhe o fluxo
No cadastro de usuário, o endpoint faz, de forma síncrona e em sequência: grava no banco, chama o serviço de e-mail de boas-vindas, chama o CRM, chama o serviço de analytics, chama o de antifraude. O p95 do cadastro é 4,2 s e cai quando o CRM está lento. Redesenhe.
Gabarito: Síncrono, só o essencial para responder ao usuário: gravar no banco e, se necessário, a checagem antifraude que bloqueia o cadastro. Tudo o mais (e-mail de boas-vindas, CRM, analytics) vira assíncrono: o endpoint publica um evento UsuárioCadastrado (via outbox para não ter dual-write — Módulo 5) e responde. Consumidores independentes tratam e-mail, CRM e analytics, com retry e idempotência; se o CRM cai, a fila segura e reprocessa, sem afetar o cadastro. p95 despenca para ~o tempo de um insert.
Dados em sistemas distribuídos
Objetivo: entender por que "banco por serviço" quebra as transações, e os padrões que resolvem — outbox, saga, CQRS, event sourcing — junto com consistência eventual e idempotência.
5.1 Database per service e o fim do JOIN
Em microservices, cada serviço é dono do seu banco e ninguém mais o acessa direto (senão o acoplamento volta pela porta dos fundos). Consequências: acabou o JOIN entre domínios, acabou a transação ACID que abrange dois serviços, e "montar uma tela com dados de 4 serviços" vira um problema de arquitetura.
5.2 O problema do dual-write
"Vou gravar no meu banco e publicar o evento no Kafka." Se a segunda operação falha (ou o processo morre entre as duas), os sistemas divergem — e não há transação entre banco e broker.
Na mesma transação do banco em que você grava o dado, você insere o evento numa tabela outbox. Um processo separado (poller ou CDC — ver apostila de Engenharia de Dados) lê a outbox e publica no broker, marcando como enviado. Se algo falhar, a transação inteira reverte ou o evento é reenviado. Entrega at-least-once garantida, sem dual-write. Consumidores precisam ser idempotentes.
5.3 Saga — transações que cruzam serviços
Uma saga é uma sequência de transações locais, cada uma num serviço, com uma transação de compensação para desfazer cada passo se um posterior falhar (não há rollback distribuído).
Pedido: reservar estoque ──▶ cobrar pagamento ──▶ agendar entrega
│ falhou!
▼
compensação: estornar pagamento ◀── liberar estoque (desfaz na ordem inversa)
- Saga coreografada: cada serviço reage a eventos e emite o próximo (ou o de compensação). Simples para poucos passos; vira sopa de eventos se crescer.
- Saga orquestrada: um orquestrador conduz os passos e as compensações. Melhor para fluxos com muitas regras; um motor de workflow ajuda.
- Exige pensar em semântica de compensação (nem tudo é reversível — e-mail enviado não "desenvia"; use compensação semântica: mandar um e-mail de correção).
5.4 CQRS
Command Query Responsibility Segregation: separar o modelo de escrita (comandos, normalizado, com regras de negócio) do modelo de leitura (queries, desnormalizado, otimizado para as telas). O modelo de leitura é atualizado a partir de eventos do modelo de escrita.
- Ganha: leitura e escrita escalam e evoluem separadas; telas complexas viram consultas triviais em um modelo feito sob medida; junta dados de vários serviços num read model.
- Custa: dois modelos para manter; consistência eventual entre eles (a leitura pode estar alguns ms atrás); mais infraestrutura.
- Não precisa de event sourcing — são independentes, embora combinem bem.
5.5 Event Sourcing avançado
Em vez de guardar o estado atual, guarde a sequência de eventos que levou a ele (ContaAberta, Depositado 100, Sacado 30 ⇒ saldo 70). O estado é uma projeção (fold) dos eventos.
- Ganha: auditoria perfeita, "viagem no tempo", capacidade de reconstruir novas projeções do zero, debugging por replay.
- Custa: complexidade alta, versionamento de eventos (o schema evolui e os eventos antigos são imutáveis), consultas exigem projeções, curva de aprendizado do time. Use com parcimônia, em domínios onde auditoria/histórico é requisito central (financeiro, jurídico).
5.6 Consistência eventual e idempotência
- Consistência eventual: em sistemas distribuídos, aceita-se que réplicas/projeções converjam "em breve", não instantaneamente. Projete a UX para isso (mostrar "processando", ler o próprio write localmente, evitar prometer o que ainda não confirmou).
- Idempotência: como a entrega é at-least-once, todo consumidor precisa processar a mesma mensagem duas vezes com o mesmo efeito de uma. Técnicas: chave de idempotência + tabela de "já processados"; upsert por id; operações naturalmente idempotentes.
- Ordem: não conte com ordem global. Use chave de particionamento para ordem por entidade, ou torne o processamento insensível à ordem.
Bloco muito cobrado em entrevista sénior: "Como manter consistência entre dois serviços sem transação distribuída?" (saga + outbox), "O que é o problema do dual-write?", "O que é CQRS e quando vale a pena?", "Diferença entre CQRS e event sourcing", "Como garantir idempotência num consumidor de fila?". Citar outbox sem que perguntem, ao falar de eventos, é sinal de quem já sofreu na prática.
✏️ Exercício 5 — Projete a consistência
Serviço de Pedidos e serviço de Estoque, bancos separados. Ao criar um pedido, o estoque precisa ser decrementado; se o pagamento falhar depois, o estoque volta. Descreva o desenho.
Gabarito: Saga (coreografada, poucos passos): (1) Pedidos grava o pedido como PENDENTE e, na mesma transação, insere PedidoCriado na outbox; um relay publica o evento. (2) Estoque consome PedidoCriado (idempotente, via chave = pedido_id), reserva/decrementa e emite EstoqueReservado (ou EstoqueInsuficiente). (3) Pagamento consome EstoqueReservado, cobra, emite PagamentoAprovado ou PagamentoRecusado. (4) Em PagamentoRecusado ou EstoqueInsuficiente, Pedidos marca o pedido como CANCELADO e Estoque consome o evento de cancelamento para compensar (liberar a reserva). Tudo idempotente; consistência eventual assumida na UX ("pedido em processamento").
Escalabilidade e performance
Objetivo: as ferramentas para o sistema aguentar mais carga — stateless + escala horizontal, balanceamento, caching, particionamento/sharding e controle de fluxo.
6.1 Vertical × horizontal
- Escala vertical: máquina maior. Simples, limitado por um teto físico e por preço exponencial; não ajuda em disponibilidade.
- Escala horizontal: mais máquinas. Sem teto prático, melhora disponibilidade, mas exige que o serviço seja stateless (nenhum estado de sessão na memória do processo) para qualquer instância atender qualquer requisição.
Tornar stateless: sessão em cookie assinado ou em store compartilhado (Redis); uploads e arquivos em object storage; nada de "afinidade de sessão" como muleta permanente.
6.2 Balanceamento de carga
- L4 (transporte) vs L7 (aplicação): L7 entende HTTP, roteia por path/header, faz TLS termination, health checks.
- Algoritmos: round-robin, least-connections, hash consistente (para afinidade de cache/estado), weighted.
- Health checks ativos removem instâncias doentes; graceful shutdown drena conexões antes de matar o processo no deploy.
6.3 Caching — a maior alavanca de performance
| Nível | Exemplo |
|---|---|
| Cliente / browser | Cache-Control, ETag |
| CDN / edge | Assets estáticos, respostas cacheáveis perto do usuário |
| Gateway / reverse proxy | Respostas de GET idempotentes |
| Aplicação (distribuído) | Redis/Memcached: resultado de query, objeto montado, sessão |
| Banco | Buffer pool, materialized views |
- Padrões: cache-aside (a app consulta o cache, no miss busca a fonte e popula), read-through/write-through, write-behind.
- Invalidação — "a segunda coisa mais difícil da computação": TTL (simples, aceita dado velho por um tempo), invalidação por evento (preciso, mais complexo), versionar a chave.
- Armadilhas: cache stampede (muitas chaves expiram juntas e todo mundo vai à fonte — mitigar com TTL + jitter e lock de recomputação), hot key, e cachear dado por usuário sem necessidade.
6.4 Particionamento e sharding
- Replicação (cópias do mesmo dado) escala leitura e dá disponibilidade; particionamento/sharding (fatias diferentes em nós diferentes) escala escrita e armazenamento.
- Shard key é a decisão crítica: alta cardinalidade, distribuição uniforme, e presente nas consultas frequentes. Chave ruim ⇒ hot shard ou toda query vira scatter-gather.
- Hash (distribui bem, mata range queries) vs range (bom para faixas, risco de hotspot) vs geográfico/tenant.
- Resharding é caro — planeje com folga (hash consistente, mais partições lógicas que físicas). Ver apostila de NoSQL para o detalhe.
6.5 Controle de fluxo: filas, back-pressure e limites
- Filas nivelam picos (load leveling): o produtor despeja rápido, o consumidor processa no seu ritmo.
- Back-pressure: quando o consumidor não dá conta, sinalizar para o produtor desacelerar (em vez de estourar memória). Bounded queues, rejeição explícita, load shedding (descartar o excedente de forma controlada).
- Rate limiting (token bucket, sliding window) protege o serviço de abuso e de vizinho barulhento.
- Noção de teoria de filas: conforme a utilização se aproxima de 100%, a latência de espera dispara (não é linear). Sistemas saudáveis operam com folga (~60–70% de utilização), não no talo.
Perguntas: "Como você escala um serviço web?" (stateless + horizontal + LB + cache + read replicas + shard quando a escrita apertar), "Estratégias e armadilhas de cache", "Como escolher uma shard key?", "O que é back-pressure?", "Por que um sistema a 95% de utilização fica lento?" (teoria de filas). Este módulo é o coração das perguntas de estimativa da entrevista de system design.
✏️ Exercício 6 — Ataque o gargalo
Uma API de catálogo faz 5.000 req/s, cada uma roda a mesma query pesada de "produtos em destaque" (muda 2×/dia). O banco está a 90% de CPU e o p99 subiu para 900 ms. Ordene as ações.
Gabarito: (1) Cache do resultado de "produtos em destaque" (Redis ou até em memória do processo), TTL de alguns minutos ou invalidação nos 2 momentos de mudança — resolve quase tudo na hora, pois é a mesma resposta para todos. (2) Se ainda houver pressão de leitura, read replicas e distribuir as queries. (3) CDN/edge cache para a resposta se ela for pública. (4) Revisar índices/plano da query. (5) Só então pensar em escalar o banco verticalmente ou shardar — provavelmente desnecessário depois do cache. Lição: identificar que 5.000 req/s pedem a mesma coisa muda tudo.
Confiabilidade e resiliência
Objetivo: projetar para a falha — porque em escala, algo está sempre quebrado. SPOF, redundância, os padrões de tolerância a falha, e recuperação de desastre.
7.1 A premissa: tudo falha
Discos morrem, redes particionam, dependências ficam lentas, deploys quebram, uma zona de disponibilidade cai. Arquitetura resiliente não tenta impedir toda falha — ela contém o raio de explosão e degrada com elegância em vez de cair por inteiro.
7.2 Elimine pontos únicos de falha (SPOF)
- Redundância em toda camada: múltiplas instâncias, múltiplas zonas de disponibilidade, réplicas de banco com failover.
- Cuidado com SPOFs escondidos: um único balanceador, um único NAT gateway, um serviço de config que todo mundo consulta na inicialização, o DNS, um segredo que só uma pessoa tem.
- Disponibilidade compõe: componentes em série multiplicam (pioram); redundância em paralelo aproxima de 100%.
7.3 Os padrões de tolerância a falha
| Padrão | Problema que resolve |
|---|---|
| Timeout | Nunca espere para sempre. Toda chamada de rede tem timeout agressivo e sensato. |
| Retry com backoff + jitter | Falha transitória se resolve tentando de novo — com espera crescente e aleatória para não criar "thundering herd". Só para operações idempotentes. |
| Circuit breaker | Se um serviço está falhando muito, pare de chamá-lo por um tempo (circuito "aberto"), retorne fallback na hora, e teste periodicamente se voltou. Evita esgotar threads esperando um serviço morto. |
| Bulkhead | Isolar recursos (pools de thread/conexão) por dependência, para que a lentidão de uma não afogue o serviço inteiro. |
| Fallback / degradação graciosa | Sem recomendações personalizadas? Mostre as populares. Sem o serviço de avaliações? Esconda a seção. O core continua de pé. |
| Load shedding | Sob sobrecarga, rejeitar parte das requisições rápido (com 429/503) para proteger a capacidade de atender o resto. |
| Idempotency key | Cliente manda uma chave única; o servidor deduplica retries de operações que mudam estado (pagamentos!). |
Retry sem backoff + sem jitter + sem circuit breaker, feito por milhares de clientes ao mesmo tempo, transforma um soluço de 2 segundos numa avalanche que mantém o serviço no chão (retry storm). E retry em operação não idempotente cobra o cartão duas vezes. Retry é uma faca de dois gumes — use com backoff exponencial, jitter, teto de tentativas e circuit breaker.
7.4 Recuperação de desastre
- RTO (Recovery Time Objective): quanto tempo até voltar. RPO (Recovery Point Objective): quanto dado você aceita perder (janela desde o último backup replicado).
- Estratégias por custo/agressividade: backup & restore → pilot light → warm standby → multi-site ativo-ativo.
- Backup que nunca foi restaurado não é backup. Teste a restauração periodicamente.
- Runbooks e automação para os cenários prováveis; game days e engenharia do caos (derrubar coisas de propósito) para validar que a resiliência projetada funciona de verdade.
Perguntas: "O que é um circuit breaker e quando ele 'abre'?", "Como fazer retry corretamente?" (idempotente + backoff exponencial + jitter + teto + circuit breaker), "O que é degradação graciosa, com exemplo?", "Diferença entre RTO e RPO", "Como você evita um retry storm?". Em system design, "e se este componente cair?" é pergunta garantida.
✏️ Exercício 7 — Blinde a dependência
Sua página de produto chama um serviço externo de recomendações. Ele às vezes fica com 3 s de latência e derruba o p99 da página inteira; quando ele cai de vez, a página trava esperando. Aplique os padrões.
Gabarito: (1) Timeout curto (ex.: 200–300 ms) na chamada de recomendações — a página não pode refém dela. (2) Fallback: no timeout ou erro, mostrar recomendações genéricas/populares (cacheadas) ou esconder a seção. (3) Circuit breaker: se a taxa de erro/timeout passar de um limiar, parar de chamar por 30–60 s e servir só o fallback, testando de volta aos poucos. (4) Bulkhead: pool de conexões dedicado para recomendações, para não consumir as threads do resto. (5) Tornar o carregamento das recomendações assíncrono no front (a página renderiza sem elas e preenche depois). Resultado: recomendações viram um "extra" que nunca compromete o core.
A entrevista de System Design
Objetivo: um framework replicável para conduzir uma entrevista de projeto de sistema, com estimativas de capacidade e um passo a passo aplicado a exemplos clássicos.
8.1 O que o entrevistador avalia
Não é a "resposta certa" (não existe). É: você esclarece requisitos antes de desenhar? Estrutura o raciocínio ou pula direto para a solução? Justifica escolhas com trade-offs? Conhece os números (o que cabe em RAM, latência de disco vs rede, QPS por máquina)? Identifica gargalos e sabe aprofundar quando pedem? Comunica com clareza?
8.2 O framework (≈45 min)
| Passo | Tempo | O que fazer |
|---|---|---|
| 1. Requisitos | ~5 min | Funcionais (features do MVP), não-funcionais (escala, latência, consistência, disponibilidade), e o que está fora do escopo. Confirme com o entrevistador. |
| 2. Estimativas | ~5 min | Usuários (DAU), QPS médio e de pico (leitura vs escrita), volume de dados/dia e em 5 anos, banda, memória de cache. Ordens de grandeza, não precisão. |
| 3. API | ~3 min | Os principais endpoints/contratos — define as fronteiras. |
| 4. Modelo de dados | ~5 min | Entidades, relacionamentos, escolha de datastore (SQL? KV? busca? blob?) justificada pelo padrão de acesso. |
| 5. Desenho de alto nível | ~10 min | Caixas e setas: clientes → LB → serviços → dados → cache → filas → workers. O caminho de leitura e o de escrita. |
| 6. Aprofundar | ~10 min | Onde o entrevistador apontar: o componente crítico, o gargalo de escala, a consistência, o caso de falha. |
| 7. Gargalos & evolução | ~5 min | SPOFs, hot spots, o que quebra primeiro em 10×, como monitorar, o que você faria diferente com mais tempo. |
8.3 Números que você precisa ter na cabeça
Latências de referência (ordem de grandeza): ler 1 MB da memória ........... ~0,25 ms SSD: leitura aleatória ........ ~0,1 ms round-trip na mesma região .... ~0,5 ms round-trip entre continentes .. ~150 ms Capacidade (folgada, "de guardanapo"): QPS por instância de app ...... ~1.000 (varia MUITO com a carga) linhas que cabem em RAM ....... milhões a dezenas de milhões 1 dia = 86.400 s → 1M eventos/dia ≈ ~12/s médio (pico pode ser 5–20× a média) Regra do 80/20 e leitura-pesada: a maioria dos sistemas de produto é 10:1 a 1000:1 leitura:escrita → cache e réplicas mandam
8.4 Padrões que reaparecem
- URL shortener / pastebin: geração de id (hash vs contador vs base62), KV store, cache, redirecionamento 301/302, analytics assíncrono.
- Feed / timeline: fan-out on write (empurra para o feed de cada seguidor — bom para quem tem poucos seguidores) vs fan-out on read (monta na hora — bom para celebridades); híbrido é a resposta.
- Chat / mensageria: conexões persistentes (WebSocket), presença, entrega e ordenação por conversa, armazenamento em wide-column, notificações push.
- Rate limiter: token bucket em Redis, sliding window, distribuído com alguma folga de precisão.
- Sistema de notificações: filas por canal, retry, deduplicação, preferências do usuário, provedores externos com fallback.
- Busca / typeahead: índice invertido (Elasticsearch), trie para autocomplete, sincronização via CDC.
Pular a fase de requisitos e começar a desenhar; não fazer estimativa nenhuma; jogar todo buzzword sem justificar; ir de microservices + Kafka + K8s para um problema que um monólito + Postgres resolveria; ficar em silêncio (é uma conversa, não uma prova escrita); não admitir trade-offs ("minha solução não tem desvantagem" é sempre falso).
A entrevista de system design é decisiva para vagas pleno-sênior+ em produto e Big Tech. Prepare-se resolvendo ~10–15 problemas clássicos em voz alta, cronometrado, seguindo sempre o mesmo framework. Estude os números da seção 8.3 até serem automáticos. E pratique a comunicação: desenhe enquanto fala, cheque premissas, conduza.
✏️ Exercício 8 — Esboço em 10 minutos: encurtador de URL
Faça o esqueleto: requisitos, estimativa, API, dados, alto nível, um gargalo.
Gabarito (esboço): Requisitos: encurtar URL, redirecionar, (opcional) URL custom, analytics de cliques; 100M links/mês, leitura:escrita ~100:1, redirecionamento < 50 ms, alta disponibilidade (link quebrado é péssimo). Fora: contas, edição. Estimativa: ~40 escritas/s média, ~4.000 leituras/s pico; 100M/mês × 5 anos ≈ 6B registros × ~500 B ≈ 3 TB. API: POST /urls {long_url} → {short}; GET /{short} → 302. Dados: KV store (short → long_url, created_at), sharded por hash do short; analytics em store separado, escrito assíncrono. Alto nível: cliente → LB → serviço de escrita (gera short via base62 de um contador distribuído ou hash+colisão) → KV; redirecionamento: LB → serviço de leitura → cache (Redis, hit rate altíssimo porque poucos links concentram tráfego) → KV no miss → 302; evento de clique → fila → worker → analytics. Gargalo: leitura — resolvido por cache + réplicas; a geração de id não pode colidir nem ser um SPOF (usar faixas de contador pré-alocadas por instância, ou hash com verificação).
Arquitetura na prática: documentar, avaliar e evoluir
Objetivo: as práticas que mantêm a arquitetura viva e saudável ao longo dos anos — ADRs, C4, fitness functions, avaliação, anti-padrões e migração incremental.
9.1 Documentar decisões: ADRs
Um Architecture Decision Record é um arquivo curto (uma página) no repositório, versionado com o código, que registra uma decisão: contexto, opções consideradas, decisão tomada, consequências (boas e ruins), e status (proposto/aceito/substituído). O valor não é o documento — é forçar o raciocínio e deixar rastro para quem chegar depois perguntar "por que isto é assim?".
# ADR-014: Usar outbox para publicação de eventos de domínio
## Status: Aceito (2026-03)
## Contexto
Precisamos publicar eventos quando um pedido muda de estado. Dual-write
(gravar no banco + publicar no broker) pode divergir em falha parcial.
## Decisão
Inserir o evento numa tabela `outbox` na mesma transação do banco; um relay
CDC publica no broker.
## Consequências
+ Sem perda/divergência de evento; entrega at-least-once.
+ Consumidores DEVEM ser idempotentes (aceito).
- Uma peça a mais (o relay) para operar e monitorar.
9.2 Comunicar a estrutura: o modelo C4
Quatro níveis de zoom, cada um um diagrama simples: Contexto (o sistema e quem interage com ele) → Contêineres (apps, serviços, bancos — as unidades de deploy) → Componentes (dentro de um contêiner) → Código (raramente necessário). Substitui o diagrama "UML gigante que ninguém entende" por uma hierarquia legível. Poucas caixas, legendas claras, uma história por diagrama.
9.3 Arquitetura evolutiva e fitness functions
- Premissa: a arquitetura vai mudar; projete para a mudança guiada, não para a perfeição inicial.
- Fitness function: um teste automatizado que verifica um atributo arquitetural — "nenhum módulo de domínio importa o pacote de infraestrutura" (ArchUnit), "o p99 do endpoint X fica abaixo de 200 ms" (teste de performance no CI), "nenhum serviço acessa o banco de outro". Torna as regras arquiteturais executáveis em vez de wiki que ninguém lê.
9.4 Avaliar uma arquitetura
ATAM (Architecture Tradeoff Analysis Method) é o método formal: a partir de cenários de atributos de qualidade ("o tráfego dobra na Black Friday", "um data center cai", "adicionamos um novo tipo de pagamento"), avalia-se como a arquitetura responde, quais os pontos de sensibilidade e os trade-offs. Mesmo informalmente, "rodar cenários" é a melhor forma de estressar um desenho antes de construí-lo.
9.5 Anti-padrões
| Anti-padrão | Sintoma |
|---|---|
| Big Ball of Mud | Sem fronteiras; tudo acessa tudo; mudar qualquer coisa quebra outra |
| Monólito distribuído | Serviços separados que precisam ser deployados juntos, chamam-se em cadeia síncrona e compartilham banco |
| Golden Hammer | "Resolvemos tudo com Kafka / com microserviços / com o padrão X" |
| Arquitetura de currículo | Tecnologia escolhida para o CV do arquiteto, não para o problema |
| Acoplamento por banco compartilhado | Vários serviços lendo/escrevendo as mesmas tabelas |
| Camada anêmica de abstração | Wrappers que só repassam chamadas, adicionando indireção sem valor |
9.6 Migração incremental: Strangler Fig
Nunca faça o "big bang rewrite". O padrão strangler fig (Fowler): coloque uma fachada (proxy/gateway) na frente do sistema legado; construa cada capacidade nova (ou reescrita) atrás dela; roteie o tráfego daquela função para o novo componente; repita até o legado "estrangulado" poder ser desligado. Combine com shadow traffic (o novo processa em paralelo sem servir) e feature flags para reduzir risco.
Perguntas: "Como você documenta decisões de arquitetura?" (ADRs no repo), "O que é o modelo C4?", "O que é uma fitness function arquitetural?", "Como migrar um monólito legado sem parar o negócio?" (strangler fig + shadow + flags), "Cite anti-padrões de arquitetura que você já viu". Ter uma história real de migração incremental bem conduzida é ouro em entrevista sénior.
✏️ Exercício 9 — Escreva o ADR
Seu time decidiu adotar CQRS só na área de "relatórios e dashboards", mantendo o resto CRUD normal. Escreva o ADR (contexto, decisão, consequências).
Gabarito (esboço): Contexto: as telas de relatório fazem JOINs pesados entre 5 tabelas de domínios diferentes, degradam o banco transacional e acoplam o time de relatórios ao schema dos outros. Decisão: introduzir um read model dedicado a relatórios, desnormalizado, atualizado por eventos de domínio (consistência eventual, atraso aceitável de minutos); o caminho de escrita e o CRUD das outras áreas permanecem inalterados. Consequências: + leitura de relatório fica trivial e não pesa no banco transacional; + relatórios deixam de depender do schema interno dos outros domínios. − mais uma base e um pipeline de projeção para operar; − dados de relatório podem estar alguns minutos atrás (aceito pelo negócio); − necessidade de reprocessar projeções em caso de bug. Status: Aceito.
Mercado de trabalho: roadmap, entrevistas e portfólio
Objetivo: converter o conteúdo dos módulos anteriores em aprovação — perfis, roadmap, banco de perguntas com respostas e como demonstrar competência de arquitetura.
10.1 Onde arquitetura pesa na carreira
| Nível / papel | O que se espera de arquitetura |
|---|---|
| Pleno → Sénior | Projetar um serviço/feature com boas fronteiras; passar na entrevista de system design; raciocinar sobre trade-offs |
| Staff / Principal Engineer | Arquitetura de vários sistemas, decisões que atravessam times, mentoria, ADRs, reduzir risco cedo |
| Solutions / Cloud Architect | Desenhar soluções sobre uma stack de nuvem, com foco em requisitos não-funcionais e custo |
| Engineering Manager (técnico) | Guiar decisões de arquitetura do time, alinhar com a Lei de Conway |
10.2 Roadmap de estudo (8–10 semanas)
| Semanas | Foco | Prática |
|---|---|---|
| 1–2 | Fundamentos: atributos de qualidade, acoplamento/coesão, SOLID, Conway (Módulos 1–2) | Refatorar um projeto seu introduzindo ports & adapters no núcleo |
| 3 | Estilos: monólito modular, hexagonal, microservices, EDA, serverless (Módulo 3) | Escrever um comparativo justificado para um sistema real que você conhece |
| 4 | Comunicação: sync/async, orquestração/coreografia, gateway/BFF (Módulo 4) | Redesenhar um fluxo síncrono do seu trabalho como assíncrono com outbox |
| 5 | Dados distribuídos: outbox, saga, CQRS, event sourcing, idempotência (Módulo 5) | Implementar uma saga simples (2–3 serviços) com compensação |
| 6 | Escala e performance: stateless, LB, cache, sharding, back-pressure (Módulo 6) | Adicionar cache-aside com invalidação a um endpoint e medir |
| 7 | Resiliência: circuit breaker, retry, bulkhead, degradação, RTO/RPO (Módulo 7) | Blindar uma dependência externa com timeout + fallback + breaker |
| 8–9 | System design: framework + 12 problemas clássicos cronometrados (Módulo 8) | Resolver em voz alta, gravar-se, revisar contra soluções de referência |
| 10 | Prática: ADRs, C4, fitness functions, strangler (Módulo 9); portfólio | Documentar um sistema seu com C4 + 5 ADRs; publicar |
10.3 Banco de perguntas de entrevista (com a resposta que aprova)
Pleno — "Monólito ou microservices para um produto novo?"
Monólito modular. Um produto novo tem requisitos em descoberta, time pequeno e sem a maturidade operacional (CI/CD, observabilidade distribuída, on-call) que microservices exigem. Monólito modular dá fronteiras internas claras, transações locais e refatoração barata; extrai-se um serviço quando a fronteira fica óbvia e a dor (escala divergente, times pisando no pé) for real. Ir de microservices cedo é o over-engineering clássico.
Pleno — "Como dois serviços mantêm consistência sem transação distribuída?"
Saga: sequência de transações locais, cada uma com uma compensação para desfazer se um passo posterior falhar. Os eventos que disparam os passos são publicados via transactional outbox (gravados na mesma transação do dado) para não haver dual-write. Consumidores idempotentes. Aceita-se consistência eventual e projeta-se a UX para ela.
Pleno/sénior — "Explique circuit breaker, retry e bulkhead"
Retry: repetir uma operação idempotente que falhou por causa transitória, com backoff exponencial + jitter e teto de tentativas. Circuit breaker: após muitas falhas de uma dependência, parar de chamá-la por um tempo e retornar fallback imediato, testando a volta aos poucos — evita esgotar threads esperando um serviço morto e dá tempo para ele se recuperar. Bulkhead: isolar pools de recursos por dependência para que a lentidão de uma não afogue o serviço todo. Os três juntos evitam que uma falha local vire falha total.
Sénior — "Como você escalaria um serviço de leitura pesada?"
Tornar stateless e escalar horizontalmente atrás de um LB; adicionar cache em vários níveis (CDN para o público, Redis para objetos/queries, com TTL+jitter e cuidado com stampede e hot key); read replicas do banco distribuindo as leituras; desnormalizar / read model (CQRS) para as telas caras; só shardar a escrita quando ela virar gargalo. Medir onde está o gargalo antes de agir — muitas vezes 5.000 req/s pedem a mesma resposta e um cache resolve.
Sénior — "O que é um monólito distribuído e como evitar?"
São "microserviços" que na prática têm as desvantagens do distribuído sem os benefícios: precisam ser deployados juntos, chamam-se em cadeias síncronas longas, compartilham banco, e uma mudança obriga a mexer em vários. Evita-se colocando fronteiras por domínio de negócio (não por camada técnica), dando a cada serviço seu próprio dado, preferindo comunicação assíncrona, e não extraindo serviços antes de a fronteira estar clara.
Sénior — "Como migrar um monólito legado crítico?"
Strangler fig: fachada na frente do legado; cada capacidade nova ou reescrita é construída atrás dela; roteia-se o tráfego daquela função para o novo componente; repete-se até desligar o legado. Reduz risco com shadow traffic (novo processa em paralelo sem servir), feature flags e rollout gradual. Nunca big-bang rewrite — a taxa de fracasso é altíssima e o negócio não pode parar.
Armadilha — "Qual a melhor arquitetura?"
Não existe — depende dos atributos de qualidade dominantes do sistema e do contexto (time, prazo, escala real, orçamento). A pergunta certa é "quais forças mandam aqui?" e a resposta é a arquitetura mais simples que atende os requisitos atuais sem impedir os próximos passos previsíveis. Toda escolha é trade-off; quem diz que sua solução não tem desvantagem está errado.
10.4 Como demonstrar competência de arquitetura
- Documentar um sistema real (seu, de trabalho anonimizado, ou open source) com diagramas C4 e uma série de ADRs explicando as decisões e os trade-offs. Publicar.
- Estudo de system design escrito: escolher um produto conhecido, aplicar o framework do Módulo 8 num artigo, com estimativas e alternativas.
- Projeto que mostra um padrão distribuído: uma saga com compensação, ou CQRS com read model alimentado por eventos, ou um serviço com circuit breaker + outbox — pequeno, mas correto e explicado.
- Refatoração documentada: pegar um projeto acoplado e mostrar o "antes/depois" ao introduzir fronteiras e inversão de dependência, com fitness functions garantindo as regras.
- Contribuir em discussões de design (RFCs) de projetos open source.
10.5 Leituras de referência
- Fundamentos: Fundamentals of Software Architecture (Richards & Ford); Software Architecture: The Hard Parts (mesmos autores) para trade-offs de sistemas distribuídos.
- Dados distribuídos: Designing Data-Intensive Applications (Martin Kleppmann) — o livro definitivo, base dos Módulos 5–7.
- Microservices: Building Microservices (Sam Newman); Monolith to Microservices (mesmo autor) para o strangler.
- Padrões: Enterprise Integration Patterns (Hohpe & Woolf); o catálogo microservices.io (Chris Richardson) para saga/outbox/CQRS.
- Evolução: Building Evolutionary Architectures (Ford, Parsons, Kua); Team Topologies (Skelton & Pais) para a Lei de Conway na prática.
- Entrevista: System Design Interview (Alex Xu, vol. 1 e 2); o "System Design Primer" no GitHub.
Quatro ideias sustentam arquitetura: (1) arquitetura é o conjunto de decisões caras de mudar — e adiar as reversíveis é estratégia; (2) tudo é trade-off, não existe "melhor", e "depende — quais os requisitos?" é a resposta profissional; (3) busque alta coesão dentro das fronteiras, baixo acoplamento entre elas, alinhadas aos times (Conway); (4) comece simples e adicione complexidade só quando a dor for real — o distribuído resolve problemas reais e cria outros. As tecnologias mudam a cada ciclo; esses princípios, não.