Arquitetura boa é arquitetura que ainda muda

Publicado em

Boas práticas de engenharia e arquitetura servem para manter software fácil de mudar sem perder previsibilidade. O sinal de uma boa arquitetura não é parecer sofisticada no diagrama, mas permitir alterações frequentes com baixo risco, contratos claros e impacto mensurável.

Em produção, quase todo sistema relevante envelhece mais pelo acúmulo de decisões implícitas do que pela tecnologia escolhida no início. O problema raramente é usar monólito, microsserviços, filas, cache ou banco relacional. O problema é não saber por que aquilo existe, quem depende daquilo e como detectar quando uma mudança quebrou algo importante.

Por isso, boas práticas não devem virar uma lista de mandamentos abstratos. Elas precisam aparecer no desenho das interfaces, nos limites entre módulos, na cobertura de testes, nos logs e nas revisões de código. Arquitetura boa é menos sobre prever o futuro e mais sobre criar condições para corrigir rota.

O que torna uma decisão arquitetural boa?

Uma decisão arquitetural é boa quando melhora uma restrição real do sistema e deixa suas consequências visíveis. Separar um serviço pode ser ótimo se times precisam evoluir em ritmos diferentes, mas pode ser desperdício se o gargalo ainda é modelagem de dados. Adicionar uma fila pode absorver picos, mas também cria atraso, reprocessamento e novas falhas operacionais.

Antes de aprovar uma decisão, vale responder quatro perguntas:

  • Qual problema concreto estamos resolvendo? Exemplo: reduzir o checkout de 2,8 s para menos de 1 s no percentil 95.
  • Qual custo estamos aceitando? Pode ser latência eventual, duplicidade de dados, mais infraestrutura ou deploy mais complexo.
  • Como vamos medir se funcionou? p95, taxa de erro, tempo médio de recuperação e retrabalho contam mais do que opinião.
  • Como desfazemos ou ajustamos isso? Decisões reversíveis são melhores quando o domínio ainda muda muito.

Esse raciocínio evita arquitetura de vitrine. Um sistema de pedidos com 500 requisições por minuto, equipe pequena e regras mudando toda semana pode se beneficiar mais de um monólito modular do que de cinco serviços distribuídos. Já uma plataforma com 20 squads e deploys independentes pode justificar fronteiras físicas mais fortes.

Desenvolvedores revisando arquitetura e código em uma tela
Desenvolvedores revisando arquitetura e código em uma tela

Como desenhar limites que não viram armadilhas?

Limites bons reduzem acoplamento sem esconder responsabilidades. Em código, isso aparece em módulos com nomes de negócio, dependências apontando para dentro do domínio e contratos pequenos. Em sistemas distribuídos, aparece em APIs versionadas, eventos bem definidos e dados com dono claro.

Um erro comum é dividir apenas pela camada técnica: controllers, services, repositories, helpers e validators enormes, todos compartilhando o mesmo modelo mental global. Essa organização funciona em projetos pequenos, mas tende a espalhar regras de negócio. Quando o domínio cresce, é melhor aproximar código que muda pelo mesmo motivo.

Compare uma estrutura genérica com uma estrutura orientada a capacidade:

/src
  /orders
    create-order.ts
    calculate-total.ts
    order.repository.ts
    order-events.ts
  /payments
    authorize-payment.ts
    refund-payment.ts
    payment-provider.ts
  /shipping
    quote-shipping.ts
    shipment.repository.ts

Essa separação não exige microsserviços. Ela apenas torna explícito que pedido, pagamento e entrega têm responsabilidades diferentes. Se um dia fizer sentido extrair pagamentos para outro serviço, a migração começa de um limite já exercitado dentro do código.

Também vale cuidar de contratos. Uma API não deve vazar detalhes do banco, nomes de tabelas ou estados temporários. Em vez de expor tudo que existe, exponha o que o consumidor precisa para tomar decisão. Se o contrato muda, versionar ou manter compatibilidade por um período reduz incidentes.

Quais práticas sustentam qualidade em produção?

Qualidade de engenharia aparece em ciclos curtos de feedback. Revisão de código, testes automatizados, feature flags, observabilidade e deploys pequenos funcionam porque reduzem o tamanho do desconhecido.

  • Pull requests pequenos: mudanças com 200 a 400 linhas são mais fáceis de revisar do que pacotes de 2.000 linhas.
  • Testes no nível certo: regras de domínio pedem testes unitários; fluxos críticos pedem integração; jornadas principais pedem poucos testes end-to-end.
  • Logs estruturados: requestId, userId, orderId e durationMs permitem investigar produção sem depender de busca textual frágil.
  • Métricas por sintoma: latência, erros, saturação e tráfego ajudam mais do que métricas internas isoladas.
  • Deploy reversível: migrações compatíveis, flags e rollback testado reduzem pressão em incidentes.

O detalhe é que essas práticas precisam estar conectadas. Não adianta ter logs se não existe correlação entre serviços. Não adianta ter testes se eles validam apenas mocks irreais. Não adianta ter feature flag se a remoção da flag nunca entra no backlog. Débito técnico não é só código ruim; é qualquer decisão operacional que ficou sem dono.

Um exemplo simples: uma mudança de schema deveria ser feita em etapas. Primeiro adicionar a coluna nova sem remover a antiga. Depois escrever nos dois campos ou popular por backfill. Em seguida ler do campo novo. Só então remover o campo antigo, quando não houver consumidores. Esse processo parece mais lento, mas evita indisponibilidade durante deploy.

Como documentar sem burocratizar?

Documentação útil responde dúvidas que causariam retrabalho. Ela não precisa explicar cada linha de código, mas deve registrar decisões que não são óbvias olhando para o repositório. Um Architecture Decision Record, ou ADR, com 10 a 20 linhas já resolve muito.

# ADR-007: usar fila para geração de notas fiscais

Contexto: a API de notas pode levar até 8 segundos em horários de pico.
Decisão: processar emissão de nota fiscal de forma assíncrona após pagamento confirmado.
Alternativas: chamada síncrona no checkout; job batch a cada 15 minutos.
Consequências: checkout fica mais rápido, mas precisamos lidar com retentativas e status pendente.

Outra prática saudável é documentar contratos junto do código. OpenAPI para APIs REST, JSON Schema para eventos e exemplos reais de payload reduzem ambiguidade. Se o contrato é validado no CI, melhor ainda: documentação deixa de ser promessa e vira parte do sistema.

Perguntas frequentes

Quais são as principais boas práticas de engenharia de software?

As principais são modularidade, testes automatizados, revisão de código, observabilidade, documentação de decisões e deploys pequenos. Elas reduzem risco porque tornam mudanças mais previsíveis e fáceis de auditar.

O que é uma boa arquitetura de software?

É uma arquitetura que atende às restrições reais do produto e continua fácil de evoluir. Ela tem limites claros, contratos explícitos, baixo acoplamento e mecanismos para detectar falhas em produção.

Monólito modular é melhor que microsserviços?

Depende do contexto. Para equipes pequenas e domínios ainda instáveis, monólito modular costuma ser mais simples; microsserviços fazem sentido quando há necessidade real de autonomia, escala independente e maturidade operacional.

Como reduzir débito técnico em sistemas legados?

Comece por pontos de mudança frequente e fluxos críticos. Adicione testes de caracterização, melhore observabilidade, registre decisões importantes e faça refatorações pequenas ligadas a entregas reais.