Arquitetura aparece nas conversas quando o sistema já dói. Uma alteração simples atravessa cinco módulos, ninguém sabe qual serviço é responsável por uma regra e o deploy exige uma sequência que vive apenas na memória de quem está há mais tempo no projeto.

O problema raramente é falta de tecnologia. Na maioria dos casos, o sistema perdeu limites claros.

Uma arquitetura útil não tenta prever todos os requisitos futuros. Ela organiza as decisões atuais para que as próximas mudanças custem menos e possam ser feitas com segurança.

Comece pela direção da dependência

Framework, banco de dados e fila são detalhes importantes, mas mudam com mais frequência do que as regras centrais do produto. Quando a regra de negócio depende diretamente desses detalhes, qualquer troca técnica invade o código inteiro.

Um limite simples já melhora muito:

export interface OrdersRepository {
  findById(id: string): Promise<Order | null>;
  save(order: Order): Promise<void>;
}

export class CancelOrder {
  constructor(private readonly orders: OrdersRepository) {}

  async execute(orderId: string) {
    const order = await this.orders.findById(orderId);
    if (!order) throw new OrderNotFoundError(orderId);

    order.cancel();
    await this.orders.save(order);
  }
}

O caso de uso conhece o contrato de persistência, mas não sabe se a implementação usa PostgreSQL, DynamoDB ou um armazenamento em memória para testes. A dependência aponta do detalhe para a regra.

Isso não exige criar uma interface para cada classe. O objetivo é proteger as decisões que carregam comportamento de negócio e variam em ritmos diferentes.

Modele limites que a equipe reconhece

Separar pastas por controllers, services e repositories organiza tipos técnicos. Separar por capacidades — pedidos, cobrança, catálogo, identidade — organiza o sistema de acordo com o produto.

Essa diferença fica evidente quando uma funcionalidade cresce. Em uma estrutura puramente técnica, a implementação de cobrança se espalha por várias pastas. Em uma estrutura orientada ao domínio, o código relacionado permanece próximo e o limite de responsabilidade fica visível.

Um bom módulo responde três perguntas sem depender de documentação paralela:

  • Que decisões pertencem a ele?
  • Quais operações oferece para o restante do sistema?
  • Quais dados pode alterar?

Se dois módulos precisam editar a mesma tabela e validar a mesma regra, o limite ainda não está resolvido.

Contratos também precisam evoluir

Toda integração cria um compromisso. Isso vale para uma API pública, um evento publicado em uma fila e uma função compartilhada entre pacotes.

Antes de alterar um contrato, vale classificar a mudança:

  • Aditiva: acrescenta um campo opcional ou uma nova operação.
  • Compatível: muda a implementação sem alterar o comportamento observado.
  • Quebrável: remove, renomeia ou muda a semântica de algo existente.

Mudanças aditivas permitem migração gradual. Mudanças quebráveis precisam de versão, período de convivência ou coordenação explícita entre produtores e consumidores.

Eventos merecem atenção especial. Depois de publicado, um evento pode ter consumidores que você não controla. Trate o schema como uma API: valide, versione e documente o significado, não apenas o formato.

Observabilidade faz parte do desenho

Um limite arquitetural que não aparece nos logs é difícil de operar. Para acompanhar uma operação do navegador ao banco, cada camada deve preservar contexto suficiente para responder:

  • qual requisição iniciou o trabalho;
  • qual usuário ou sistema executou a ação;
  • quanto tempo cada etapa consumiu;
  • em qual dependência ocorreu uma falha;
  • qual versão do serviço estava em produção.

Um identificador de correlação consistente, métricas por operação e logs estruturados resolvem mais incidentes do que painéis cheios de dados sem relação.

Observabilidade não é uma etapa aplicada depois que a arquitetura está pronta. Ela é o mecanismo que mostra se os limites funcionam sob carga real.

Escolha a menor arquitetura suficiente

Modularidade não significa distribuir tudo em serviços. Um monólito modular costuma ser a escolha mais segura quando a equipe ainda está descobrindo o domínio: uma unidade de deploy, transações simples e limites internos que podem amadurecer.

Extraia um serviço quando houver uma razão operacional concreta, como escala independente, isolamento de falhas, restrição de segurança ou autonomia real de uma equipe. Sem isso, a distribuição adiciona rede, consistência eventual, rastreamento e mais pontos de falha sem resolver o problema original.

O teste final é direto: uma mudança importante consegue permanecer dentro de um módulo, com contrato explícito e impacto observável? Se sim, a arquitetura está fazendo seu trabalho. Se não, o próximo passo não é necessariamente uma ferramenta nova. É descobrir qual decisão ainda não encontrou o lugar certo.