Pular para o conteúdo
← Back to Skalablog

Published article

7 camadas da clean architecture em Node.js

Software Engineering

Clean architecture separa regra de negócio, orquestração e infraestrutura em camadas com dependências apontando para dentro. Este artigo reconstrói signup e login passo a passo, mostra onde entram entidade, use case, repositório e ports, e indica o ponto em que a estrutura passa a custar mais do que entrega.

O que clean architecture resolve na prática

Clean architecture organiza o código em camadas com dependências apontando para dentro, e o critério de divisão é o motivo pelo qual cada trecho muda. O exemplo desta refatoração parte de um único arquivo que fazia validação de senha, hashing, consulta ao banco, assinatura de token e resposta HTTP no mesmo lugar.

O termo foi popularizado por Robert C. Martin em posts a partir de 2012 e no livro Clean Architecture, publicado em 2017. O próprio autor reconhece no livro que o diagrama completo não é obrigatório em todo sistema, e o palestrante Rodrigo Branas repete esse ponto: as camadas entram conforme a complexidade real do software.

A proposta defendida por Branas na live do canal Full Cycle não é criar classes extras para inflar o projeto. É dar um endereço claro para cada responsabilidade, de modo que a regra de negócio sobreviva a troca de framework, de banco e de protocolo de entrada.

Um detalhe prático: estar no mesmo arquivo não significa estar na mesma camada. Uma interface de repositório pode ser declarada ao lado do use case que a consome e ainda assim pertencer à camada de aplicação, porque é o consumidor quem define o contrato.

Onde a complexidade realmente aparece

O autor descreve a complexidade como sequências longas de chamadas em que o método A chama B, B chama C, e o retorno atravessa várias camadas até chegar ao ponto de partida. Quando o ponto de entrada é sempre o use case e cada camada delega uma ou duas vezes, o rastreamento do fluxo fica mais curto do que em um transaction script extenso.

Use cases, entidades e as demais camadas

O use case é o comportamento exposto ao mundo exterior: tem entrada, tem saída e orquestra entidades e recursos externos. A entidade, na definição usada por Martin, é a regra de negócio independente, aquela que pode ser reaproveitada por vários use cases sem carregar detalhes de infraestrutura.

Na refatoração apresentada, signup e login viraram use cases separados, cada um com seus tipos de entrada e saída. O objeto de usuário passou para a camada de domínio, junto com o serviço que gera o token, porque essas regras não dependem de HTTP, de banco nem de biblioteca de servidor.

CamadaResponsabilidade no exemploDepende de
Domínio (entidade)Criar e validar o usuário, gerar e verificar senhaNada de infraestrutura
Aplicação (use case)Orquestrar signup e login, decidir o fluxoDomínio e contratos
Adaptadores de interfaceRepositório e controller, converter dadosContratos e domínio
Frameworks e driversServidor HTTP, driver de banco, conexãoNada acima

O ponto que costuma gerar confusão é a diferença entre DAO e repositório. Um DAO trata da tabela; um repositório, no sentido usado no exemplo, recebe e devolve objetos de domínio. Essa distinção importa porque o repositório do seu ORM não é o mesmo repositório descrito em Domain-Driven Design.

Refatoração passo a passo: do arquivo único às camadas

O código inicial tinha cerca de 68 linhas com conexão de banco, validação de senha, geração de hash, assinatura de token e escrita de resposta HTTP misturados. O primeiro teste de login falhava porque a senha armazenada pertencia a outro cadastro, o que expôs a dependência de estado externo já na primeira execução.

A sequência abaixo segue a ordem mostrada na live, e cada passo pode ser interrompido: a arquitetura ganha valor conforme o problema aparece, não antes.

  1. Extrair o use case de signup e login para longe do arquivo principal, deixando o servidor apenas delegando ao serviço.
  2. Separar o acesso a dados em funções de save e busca por e-mail, formando a primeira versão do repositório.
  3. Transformar o serviço em classe com dependência recebida no construtor, para permitir trocar a implementação em tempo de teste.
  4. Definir a interface do repositório e criar uma implementação em memória, usada pelos testes que não precisam de banco.
  5. Mover as regras de senha para o objeto de domínio, com um método de fábrica que decide o hash e o salt.
  6. Extrair a assinatura do JWT para um serviço de domínio dedicado e a conexão de banco para um adaptador.

Depois de mover a lógica de senha para a entidade, o use case deixou de conhecer o algoritmo de hashing. O objeto passou a ser criado por um método de fábrica estático, ideia que Joshua Bloch documentou como static factory method em Effective Java, e a validação de senha saiu do serviço para o próprio objeto.

Os quatro cenários de teste originais, dois de signup e dois de login, continuaram passando. O ganho apareceu na camada de testes: o mesmo comportamento de hash e validação pôde ser verificado em testes unitários do objeto de domínio, sem servidor e sem banco, enquanto o teste de API continuou exercitando o caminho completo.

Ports and adapters: repositório, conexão e servidor

Ports and adapters, também chamada de arquitetura hexagonal, propõe separar drivers e resources atrás de contratos definidos pelo núcleo da aplicação. Na refatoração, essa ideia aparece quando o repositório deixa de importar a biblioteca do banco e passa a depender de uma interface de conexão.

O adaptador concreto isola a biblioteca escolhida. Trocar o driver significa escrever um novo adaptador que implemente a mesma interface de conexão, com métodos de consulta e de encerramento, sem tocar nas regras de signup, login ou senha.

Por que a inversão de dependência muda o teste

O teste do serviço de usuário deixa de precisar de banco no ar quando o repositório é fornecido pelo construtor. Na demonstração, o mesmo conjunto de asserções passou com duas implementações distintas: uma em memória e outra ligada ao Postgres via pg-promise, uma biblioteca cliente de PostgreSQL para Node.js.

O mesmo raciocínio vale para o servidor HTTP. O controller registra as rotas em uma interface de servidor, e o adaptador concreto usa Express, o framework HTTP para Node.js. O arquivo principal fica restrito à montagem das instâncias, que é o papel do entry point.

Entidades anêmicas, DTOs e o custo de ignorar o domínio

Uma classe que só carrega propriedades e não protege estado interno nem oferece operações é um modelo anêmico, e ela se comporta como um DTO trafegando entre camadas. O papel do objeto de domínio é justamente impedir que exista um usuário com senha inválida ou e-mail inconsistente.

No exemplo, a validação do formato de senha saiu do serviço e passou para o momento de construção do usuário. Com isso, o repositório pode confiar que recebe um objeto coerente e se limitar à persistência, enquanto a comparação entre a senha informada e o hash armazenado virou um método do próprio usuário.

AbordagemOnde fica a regraEfeito no teste
Objeto anêmicoNo serviço, procedimento por procedimentoDepende de contexto externo
Objeto de domínioNo próprio objeto, na construção e nos métodosComportamento verificável isoladamente

Há um limite nessa migração. Um objeto de domínio não consegue validar a senha de um usuário restaurado do banco, porque o algoritmo de origem pode ter sido outro; essa verificação pertence ao fluxo de login. O critério é separar o que é regra independente do que é decisão específica de um use case.

Os participantes do chat relataram ter aprendido modelagem anêmica na graduação. O palestrante trata isso como consequência do escopo da disciplina, não como erro: uma aula introdutória sobre transação em banco mostra o caminho mais curto, e modelagem de domínio costuma aparecer em outro momento do curso.

Use case, application service e o princípio da segregação

Um application service agrupa operações relacionadas em uma única classe, enquanto um use case específico expõe um comportamento com entrada e saída próprias. Dividir signup e login em classes separadas reduz o volume de dependências que cada unidade precisa conhecer.

Essa divisão conversa com o princípio da segregação de interface, formulado por Robert C. Martin: ninguém deve depender de métodos que não utiliza. Em linguagem compilada, uma interface de repositório com muitos métodos obriga a recompilar todos os consumidores quando ela muda, mesmo os que usam apenas um método.

Na prática, cada use case pode declarar a interface mínima de que precisa, e a classe concreta de repositório estende essas interfaces. O palestrante classifica a técnica como burocrática para o dia a dia do JavaScript, mas útil para entender por que a seta de dependência aponta de fora para dentro no diagrama.

Performance: medir antes de culpar as camadas

Camadas adicionais não são causa relevante de lentidão em aplicações típicas, e a demonstração de Branas é direta: quem afirma perda de desempenho deve implementar as duas versões e medir. Ele cita uma discussão no Twitter em que o desenvolvedor Greg Young apresentou uma comparação de desempenho entre variações de camadas, e lembra que resultados variam por linguagem.

O gargalo costuma estar na forma como o sistema conversa com o banco, na quantidade de dados trafegados e no uso de memória. Trabalhos de cálculo numérico, codecs e compactadores são domínios em que a linguagem escolhida pesa, e mesmo ali a medição manda.

Há também efeitos do compilador que separam o código escrito do código executado. A orientação prática é a mesma em qualquer caso: comparar cenários equivalentes com a mesma linguagem e o mesmo framework, e publicar a medição em vez da opinião.

O que a discussão costuma ignorar

A arquitetura em camadas não se propõe a resolver problema de desempenho. Atribuir a ela essa expectativa leva a conclusões ruins em ambos os sentidos, tanto para defender quanto para condenar a estrutura do código.

Quando usar clean architecture, DDD e os padrões avançados

Clean architecture não exige a adoção de tudo que existe no diagrama, e Branas recomenda usar o próprio discernimento. O autor do livro relata um episódio em que abstrações criadas para um site com o filho não geraram retorno, e o sistema foi descontinuado antes de justificar o esforço.

Domain-Driven Design, na obra de Eric Evans publicada em 2003, se divide entre modelagem estratégica e modelagem tática. A parte estratégica trata de subdomínios e bounded contexts, e pode ser aplicada sem escrever código; a parte tática trata dos padrões que povoam a camada de entidade de clean architecture, como objetos de valor, agregados, fábricas e eventos de domínio.

O apresentador cita o scorecard discutido por Vaughn Vernon como referência de critérios para decidir se DDD vale o investimento, e alerta contra a adoção indiscriminada de clean architecture a partir de 2019 e 2020 como solução para sistemas simples. O efeito contrário ao pretendido é comum: burocracia sem problema correspondente.

O nível de recomendação é aplicar as camadas conforme complexidade, número de pessoas envolvidas e retorno esperado para o negócio. Essa leitura é reforçada por debates técnicos sobre a responsabilidade de frameworks opinativos, em que a decisão de acoplamento é sempre de quem escreve o código, não da ferramenta.

FAQ sobre clean architecture e camadas

  • O que é clean architecture? É um conjunto de camadas em que regra de negócio, orquestração e infraestrutura ficam separadas e as dependências apontam para dentro. O termo foi popularizado por Robert C. Martin em posts a partir de 2012 e no livro de 2017, embora os conceitos de inversão de dependência e repositório sejam anteriores.
  • Clean architecture deixa a aplicação mais lenta? Não de forma relevante na maioria das aplicações. O gargalo costuma estar na interação com o banco, na memória e no volume de dados trafegados, e o caminho recomendado é medir as duas versões antes de concluir.
  • Qual a diferença entre use case e application service? Application service reúne várias operações em uma classe, enquanto use case expõe um comportamento específico com entrada e saída próprias. Dividir por use case reduz dependências e facilita testes isolados.
  • Posso usar clean architecture sem adotar Domain-Driven Design? Sim. A camada de entidade do exemplo implementa regras de negócio independentes sem agregados, eventos de domínio ou bounded contexts, que pertencem ao ferramental tático descrito por Eric Evans em 2003.
  • Repositório do ORM é o mesmo de Domain-Driven Design? Não. O repositório do ORM opera sobre tabelas e entidades anêmicas, enquanto o repositório descrito em DDD recebe e devolve objetos de domínio com estado consistente.
  • Como começar a separação em um sistema legado? Comece pelo teste mais próximo da regra de negócio, extraia o use case e só depois trate banco e servidor. Cada extração precisa manter os testes existentes passando antes do próximo passo.
  • Entidades anêmicas são sempre um erro? Não. Elas são adequadas a transporte de dados entre camadas, mas não substituem o objeto de domínio que valida e protege seu estado interno.
  • Gustavo Dev Doido e Crazystack Typescript ajudam nesse estudo? Projetos como o Bootcamp do Dev Doido mantêm trilhas em português sobre backend e frontend, e o Crazystack Typescript reúne conteúdo prático que pode complementar a leitura dos padrões citados aqui.
  • Onde encontrar o código da refatoração? O palestrante informou que publicaria o código da sessão no próprio GitHub, no repositório relacionado à live de clean architecture. Verifique o repositório antes de reutilizar, porque cada sessão tem estrutura própria.

Transforme uma live técnica em artigo com o Skala Blog

Uma refatoração mostra o motivo de cada mudança, e esse é o tipo de raciocínio que costuma se perder quando o conhecimento fica preso ao vídeo. A ordem das extrações, o erro que apareceu no primeiro teste e a decisão de mover o hash para a entidade valem mais em texto do que em memória.

Se você tem aulas, entrevistas ou sessões de código no YouTube, dá para reaproveitar esse material. No Skala Blog você cola a URL do vídeo, gera a transcrição e produz um artigo pronto para revisão, com o mesmo cuidado de separar regra de negócio de infraestrutura que aparece nesta refatoração.

Source video