Voltar
Node.jsTypeScriptArquiteturaBackendArtigo · 1 de jun. de 2026 · 14 min

jonathanjuliani

Arquitetura Node errada: as 4 que vejo em todo projeto

Arquitetura Node errada tem nome: Controller Severino, God Service, Hexagonal de Cinema e NASA. Código real e quando cada padrão quebra em produção.

Você abre um projeto Node depois de três meses sem mexer. Vai trocar uma regra de negócio numa rota. E leva quatro horas pra achar onde aquela regra mora.

Você não tá burro. O projeto que tá errado.

O tipo de erro que esse projeto tem tem nome — na verdade, tem quatro. Quatro arquiteturas Node erradas que eu vejo em praticamente todo codebase que pego. É quase garantido que você trabalha com uma delas agora, ou já trabalhou. Neste post eu mostro as quatro com código de verdade, por que cada uma quebra em produção, e os três princípios que eu uso pra decidir quando separar lógica — sem receita de livro e sem over-engineering.

O que este post argumenta:

  • Por que arquitetura ruim só dói depois que o projeto cresce
  • O gap entre o que o tutorial ensina e o que o checkout real exige
  • Os quatro anti-patterns com nome: Severino, God Service, Hexagonal de Cinema e NASA
  • Três princípios práticos pra sair disso hoje
  • Onde cada anti-pattern faz sentido — opinião sem nuance vira dogma

Quando arquitetura Node errada começa a custar caro

Arquitetura ruim não é problema enquanto o projeto é pequeno. Você sozinho, MVP, três rotas — tanto faz. Funciona.

O problema é quando:

  • O time cresce e mais gente mexe no código
  • A base passa de uns vinte arquivos
  • Você precisa voltar depois de semanas sem tocar
  • Algo quebra em produção e você tem cinco minutos pra achar

É aí que o que parecia "tá funcionando, deixa quieto" vira dia de incidente, três da manhã, cliente reclamando — e você não consegue isolar onde a lógica de pagamento mora.


Tutorial vs produção: onde nasce o Controller Severino

A maioria desses padrões erados não nasce de preguiça. Nasce de uma coisa simples: o tutorial te ensinou a fazer endpoint. Não te ensinou onde botar lógica.

Pega qualquer tutorial popular de Node — Express, Nest, Fastify, tanto faz. Ele te ensina a subir o servidor, criar rota, conectar no banco, retornar JSON. E termina. O dev sai sabendo fazer CRUD. Funciona.

Aí ele chega no mundo real e precisa fazer um checkout — validar carrinho, checar estoque, cobrar no Stripe, criar pedido, mandar email, atualizar ERP. Sete coisas, não uma.

E faz a única coisa que aprendeu: enfia tudo no controller. É aí que nasce o primeiro anti-pattern.


Controller Severino: o endpoint que faz sete coisas

Severino é o cara que faz tudo sozinho. Não delega. Coloca o pé na padaria, na lavanderia e no banco no mesmo dia. É exatamente isso que o teu controller virou.

código
@Post('/checkout')
async checkout(@Req() req) {
  const body = req.body
 
  if (!body.card) {
    throw new BadRequestException()
  }
 
  const customer = await this.prisma.customer.findUnique(...)
  const stock = await this.inventory.check(...)
  const payment = await this.stripe.charge(...)
 
  await this.prisma.order.create(...)
  await this.mail.send(...)
 
  return { success: true }
}

Esse controller faz, numa única função:

  1. Validação de entrada
  2. Busca no banco
  3. Checagem de estoque
  4. Cobrança no Stripe
  5. Criação de pedido
  6. Envio de email
  7. Resposta HTTP

Sete responsabilidades. Como você testa isso? Você não testa. Você reza.

Onde mora a regra "só cobra se o estoque estiver disponível"? No meio. Misturada com erro HTTP, integração de pagamento e email.

"Tá Jon, mas e se for um endpoint simples? Tipo um login? Aí vale fazer assim, não vale?"

Vale. O problema não é o código do Severino. É deixar todo controller virar um Severino. Você move lógica do controller quando tiver mais de duas regras de negócio. Antes disso, é over-engineering. Depois disso, é dívida.

Se você acha que mover tudo pro service resolve — espera. O próximo é pior.


God Service: cinco dependências num createUser

O God Service nasce de boa intenção. O dev leu que "controller só orquestra, lógica vai no service". Beleza. Aí move tudo pro mesmo service.

código
@Injectable()
export class UserService {
  constructor(
    private prisma: PrismaService,
    private mailService: MailService,
    private stripeService: StripeService,
    private analyticsService: AnalyticsService,
    private cacheService: CacheService,
  ) {}
 
  async createUser(dto: CreateUserDto) {
    if (!dto.email.includes('@')) {
      throw new Error('Invalid email');
    }
 
    const user = await this.prisma.user.create({ data: dto });
 
    await this.stripeService.createCustomer(user);
    await this.mailService.sendWelcomeEmail(user.email);
    await this.analyticsService.track('USER_CREATED');
    await this.cacheService.invalidate('users');
 
    return user;
  }
}

Olha o construtor desse carinha. Cinco dependências. createUser faz seis coisas: validação, banco, Stripe, email, analytics, cache.

Esse não é um UserService. É um CreateUserOrchestrationService — só que se chama UserService, então cabe nele qualquer coisa com "user" no nome. Em três meses: mil linhas, dezesseis dependências, ninguém entende mais.

Quando o Stripe ficar lento, o que acontece com o cadastro?

Trava. O usuário espera oito segundos porque o Stripe tá em manutenção — porque você colocou criação de customer dentro da criação de usuário. Coisas que deviam ser separadas viraram uma coisa só.

"A mas eu achava que separar service por entidade era o jeito certo. UserService, OrderService, ProductService — não é assim?"

É um jeito. Não é "o" jeito. Em fluxos que orquestram integrações externas — email, pagamento, analytics — service por entidade é o atalho mais rápido pro God Service. A regra que eu uso: se o service tem mais de quatro dependências, ele virou orquestrador, não service.

Os dois primeiros são problema de onde a lógica mora. Os dois próximos são pior: problema de mentir sobre onde ela mora.


Hexagonal de Cinema: pasta domain/ com Prisma dentro

Essa é a do dev que leu o livro no fim de semana e voltou pra segunda inspirado.

A estrutura de pastas é linda:

código
src/
  domain/
  application/
  infra/

Você abre, vê isso, pensa "esse projeto é sério". Vai em entrevista, mostra a pasta, ganha pontos.

Aí abre domain/user.ts:

código
import { PrismaClient } from '@prisma/client';
import axios from 'axios';
 
export class User {
  // ...
}

A pasta domain é, na teoria, a parte que não conhece banco, HTTP nem nada externo — regra de negócio pura. A ideia é trocar de banco ou framework e o domain continua igual.

Daí você importa Prisma na primeira linha do domínio. E axios também, só pra garantir.

"Tá Jon, mas a arquitetura hexagonal não é boa?"

É excelente. Quando você usa de verdade. O problema não é a hexagonal. É cosplay de hexagonal. Pasta organizada não é arquitetura — é pasta organizada.

Sintoma simples: se domain/ importa framework, ORM ou cliente HTTP — sua hexagonal é de cinema. Fachada de loja fechada.

Eu prefiro um monolito honesto com src/ plano do que hexagonal de mentira. No monolito honesto ninguém te promete desacoplamento. A hexagonal de cinema promete — e entrega o acoplamento mais escondido do mundo, porque você acha que tá protegido.


Arquitetura NASA: sete interfaces num MVP

Você abre um MVP. POC. Coisa que devia estar no ar há dois meses.

código
meu-projeto-teste/
  src/
    domain/
    application/
      commands/
      queries/
    core/
    shared/
    common/
    foundation/
    abstractions/
    contracts/
    platform/
      infrastructure/
        database/
          postgres/
            IUserRepository.ts
            IUserRepositoryAdapter.ts
            IUserRepositoryFactory.ts
            IUserRepositoryProvider.ts
            IUserRepositoryManager.ts
            IUserRepositoryRegistry.ts
            IUserRepositorySingleton.ts

Sete interfaces pra repositório de usuário num MVP com três endpoints.

Você levou trinta minutos pra achar onde salva usuário no banco. Dentro do IUserRepository? Três métodos: find, create, update. A complexidade está toda nas abstrações, e a abstração não protege nada.

"Tá Jon, mas isso não tá pronto pra escalar?"

Escalar pra quê. Você tem zero usuários. Você não tá pronto pra escalar — tá pronto pra falir, porque gastou meses em abstração em vez de validar produto.

A NASA acontece quando o dev senior — bem-intencionado — monta estrutura pra empresa de mil pessoas num projeto de duas. Toda abstração criada antes de precisar é dívida, não investimento. A realidade nunca chega no formato que você previu.

Já trabalhei em projeto NASA. Você gasta mais tempo navegando do que escrevendo. O time fica lento. O produto evolui devagar. O concorrente num monolito feio te ultrapassa.


Os 3 princípios: quando separar lógica sem virar over-engineer

Não tem arquitetura certa pra todo projeto. Tem arquitetura certa pra cada momento. Os três princípios que eu uso:

1. Mova quando doer, não quando achar bonito

Lógica nasce no controller. Tá tudo bem. Você move pro service quando o controller tem mais de duas regras de negócio. Cria service novo quando o atual tem mais de quatro dependências. Cria camada de domínio quando — e só quando — tem regra que vale teste isolado.

Doeu? Move. Não doeu? Deixa quieto. Code review existe pra perceber quando dói.

2. Honestidade é melhor que estrutura

Prefiro src/ plano com vinte arquivos do que hexagonal de cinema. Prefiro controller Severino do que God Service que finge ser três services. A estrutura deve dizer o que o código faz — não mentir.

CRUD com algumas regras é CRUD. Não precisa chamar de Clean Architecture.

3. Abstração é resposta, não pergunta

Você não cria interface "pra caso de um dia mudar de banco". Cria quando mudou. Não cria repository pattern "pra desacoplar". Cria quando o acoplamento doeu.

Toda interface antes da dor é aposta. Você perde a maioria.

PrincípioRegra prática
Mova quando doer>2 regras no controller → service; >4 deps no service → dividir
Honestidade > estruturaNomeie o que o projeto é, não o que você quer que seja
Abstração respondeInterface só depois da dor real

O contraponto que eu reconheço

Opinião sem nuance vira dogma. Cada anti-pattern acima tem terreno onde faz sentido:

Anti-patternOnde faz sentido
Controller SeverinoEndpoint com uma ou duas regras (login simples, health check)
God ServiceCRUD fino, poucas integrações, time pequeno e prazo curto
Hexagonal realRegras de negócio ricas, testáveis isoladas, múltiplos adapters de verdade
Abstrações "NASA"Depois que a dor apareceu — troca de banco, segundo provider, compliance

Isso não é hedging — é reconhecimento de limite. A tese continua: o erro mais comum não é escolher a arquitetura errada do catálogo. É aplicar arquitetura de escala enterprise no momento errado — ou fingir que já aplicou.

Na prática, quase ninguém tem só um anti-pattern. Hexagonal de Cinema convive com Severino. NASA convive com God Service. A pergunta útil não é "qual diagrama copiar", e sim quando começar a se importar — e quanto.


Próximos passos

Se o God Service te incomodou por causa de await em cadeia — Stripe, email, analytics no mesmo fluxo — vale revisar como JavaScript lida com assíncrono de verdade: Promises em JavaScript: do zero ao async/await.

E se a confusão é fronteira entre módulos, imports e o que fica exposto em cada arquivo, Node.js: require, exports e module.exports explicados ajuda a ver onde a separação começa antes de inventar dez pastas.


TL;DR

Anti-patternSintomaSinal de alertaSaída prática
Controller SeverinoUma rota faz validação, banco, integração e HTTPMais de 2 regras de negócio no controllerExtrair service por fluxo
God ServiceUserService com Stripe, mail, analytics e cacheMais de 4 dependências no construtorOrquestrador explícito ou fila/evento
Hexagonal de CinemaPastas lindas, domain/ importa Prisma/axiosFramework dentro de domain/Monolito honesto ou hexagonal de verdade
NASA7 interfaces pra 3 métodos de repositórioMVP com abstração de empresa grandeAbstrair só depois da dor

Qual dos quatro você reconheceu no projeto agora — Severino, God Service, Hexagonal de Cinema ou NASA? Comenta sem vergonha; eu já trabalhei com os quatro. Se tem um quinto anti-pattern que merecia entrar na lista, ou se viu alguma coisa errada aqui, fala nos comentários — a gente troca.

Se quiser o detalhe técnico que não coube no post, a newsletter tá rolando junto com o canal — um insight por semana, direto no email.

Inscreva-se no Ledger da Engenharia

Receba notas de arquitetura e performance na sua caixa de entrada. Mesma lista do convite—inscreva-se aqui quando quiser.

Sem spam. Sem APIs de terceiros. Apenas eu enviando updates.

O Ledger da Engenharia

Transmissoes quinzenais sobre arquitetura, performance e engenharia aplicada. Inscreva-se a partir de qualquer artigo—sem spam.