Arquitetura Boa é a que Sobrevive ao Produto Real

Publicado em

Boas práticas de engenharia e arquitetura servem para manter o software fácil de mudar quando o produto deixa de caber no desenho original. A arquitetura certa não é a mais elegante no diagrama, mas a que reduz acoplamento, torna falhas observáveis e permite entregar mudanças sem reescrever metade do sistema.

O que torna uma arquitetura realmente sustentável?

Uma arquitetura sustentável nasce de restrições claras. Antes de discutir microsserviços, filas, cache ou monolito modular, a equipe precisa saber quais propriedades importam: latência, custo, segurança, auditabilidade, velocidade de entrega, tolerância a falhas ou simplicidade operacional. Sem isso, arquitetura vira preferência pessoal.

Uma boa prática é registrar decisões arquiteturais em ADRs, documentos curtos de uma ou duas páginas. O formato básico resolve muito: contexto, decisão, alternativas consideradas e consequências. Isso evita que, seis meses depois, alguém remova uma fila, um índice ou uma separação de módulo sem entender o motivo original.

  • Defina limites de domínio: pedidos, pagamentos, catálogo e usuários não devem depender livremente uns dos outros.
  • Explicite contratos: APIs, eventos e schemas precisam ter dono, versão e regra de compatibilidade.
  • Meça antes de otimizar: p95, p99, taxa de erro e custo por requisição dizem mais que opinião.
  • Prefira reversibilidade: uma decisão boa permite voltar atrás com pouco dano quando o contexto muda.
Desenvolvedores revisando arquitetura e qualidade de código
Desenvolvedores revisando arquitetura e qualidade de código

Quando separar módulos, serviços e responsabilidades?

Separar cedo demais cria custo operacional; separar tarde demais cria medo de mexer. A regra prática é observar mudança, não organograma. Se duas partes do sistema mudam por motivos diferentes, têm regras de negócio diferentes e exigem ciclos de deploy diferentes, existe um sinal forte de separação.

Isso não significa começar com dez serviços. Em muitos produtos, um monolito modular com fronteiras bem definidas entrega mais valor que uma arquitetura distribuída mal observada. O ponto é impedir dependência circular e acesso direto a dados internos de outro módulo. Um módulo de faturamento não deveria consultar tabelas internas de carrinho porque isso torna qualquer alteração de carrinho uma possível quebra fiscal.

Um padrão simples é fazer a aplicação depender de portas, não de implementações. A regra de negócio fala com uma interface; banco, fila, provedor externo e framework ficam nas bordas. Em TypeScript, a ideia pode ser expressa assim:

type PaymentGateway = {
  charge(input: ChargeInput): Promise<ChargeResult>
}

export async function confirmOrder(order: Order, gateway: PaymentGateway) {
  if (order.totalInCents <= 0) throw new Error('invalid total')

  const payment = await gateway.charge({
    orderId: order.id,
    amountInCents: order.totalInCents
  })

  return { ...order, paymentId: payment.id, status: 'paid' }
}

O ganho não é acadêmico. Esse desenho permite testar a regra de confirmação sem subir banco, sem chamar API de pagamento e sem depender do framework HTTP. Em bases grandes, essa diferença reduz o tempo de teste de minutos para segundos e torna refatorações menos arriscadas.

Como evitar que boas práticas virem burocracia?

Boas práticas precisam pagar aluguel. Se uma convenção não reduz defeitos, tempo de entrega, custo de suporte ou risco operacional, ela merece ser questionada. O objetivo não é maximizar camadas, padrões ou documentos; é tornar o sistema previsível para quem desenvolve e para quem opera.

Um bom critério é o tamanho do impacto. Uma alteração simples, como trocar o texto de uma notificação, não deveria exigir deploy de cinco serviços. Uma mudança sensível, como alterar cálculo de imposto, deveria ter testes de regressão, revisão de regra, rastreabilidade e logs suficientes para auditoria. Engenharia madura aplica rigor proporcional ao risco.

  1. Use testes como contrato: cubra regras de negócio, integrações críticas e bugs que já chegaram em produção.
  2. Automatize o caminho feliz: lint, typecheck, testes e build devem rodar no CI a cada pull request.
  3. Controle mudanças de schema: migrations precisam ser compatíveis com rollback e deploy gradual.
  4. Observe produção: logs estruturados, métricas e traces devem responder o que falhou, onde e para quem.

Em sistemas web atuais, uma boa linha de base inclui CI bloqueando merge quando testes falham, revisão obrigatória para módulos críticos, feature flags para mudanças de alto risco e alertas baseados em sintomas reais, como aumento de erro 5xx ou latência p95 acima do SLO. Um SLO de 99,9% de disponibilidade, por exemplo, permite cerca de 43 minutos de indisponibilidade por mês. Esse número muda a conversa: não é perfeccionismo, é orçamento de falha.

Quais sinais mostram que a arquitetura está ficando cara?

O primeiro sinal é medo. Se a equipe evita tocar em uma parte do sistema porque ninguém sabe o que pode quebrar, a arquitetura já está cobrando juros. Outros sinais aparecem no fluxo: pull requests pequenos levando dias, testes instáveis, deploys manuais, bugs repetidos e incidentes difíceis de diagnosticar.

A correção deve ser incremental. Escolha um fluxo importante, escreva testes ao redor do comportamento atual, extraia a regra para uma unidade mais pura, defina contrato com as bordas e só depois reorganize pastas ou serviços. Refatoração sem rede de segurança vira aposta. Refatoração orientada por comportamento vira redução de risco.

No fim, boas práticas de engenharia não são sobre parecer sofisticado. São sobre criar um sistema em que uma pessoa nova consiga entender onde mexer, uma pessoa experiente consiga evoluir sem travar o produto e a operação consiga explicar falhas com dados. A arquitetura boa aparece menos no desenho inicial e mais na décima mudança feita sem drama.

Perguntas frequentes

O que são boas práticas de engenharia de software?

São decisões e hábitos que reduzem defeitos, acoplamento e custo de mudança. Incluem testes, revisão de código, contratos claros, observabilidade, automação de CI e separação coerente de responsabilidades.

Qual é a diferença entre arquitetura e design de código?

Arquitetura trata das decisões difíceis de mudar, como limites de módulos, comunicação, dados e operação. Design de código trata da organização interna, nomes, funções, classes e padrões usados para implementar essas decisões.

Monolito modular é melhor que microsserviços?

Depende do estágio e do problema. Para muitos produtos, monolito modular é melhor no início porque reduz custo operacional e ainda permite bons limites; microsserviços fazem sentido quando há necessidade real de escala, autonomia de deploy e isolamento.

Como melhorar a arquitetura de um sistema legado?

Comece por um fluxo crítico, adicione testes de comportamento, documente dependências e extraia regras de negócio para unidades mais isoladas. Evite reescrita total sem necessidade: mudanças incrementais costumam entregar menos risco e mais aprendizado.