Arquitetura Limpa e Injeção de Dependência no Vue.js: Como estou aplicando conceitos de escalabilidade com JavaScript puro

Arthur Pedro Spolti

Arthur Pedro Spolti

2026-02-09T00:04:27Z

4 min read

Muito se fala sobre arquitetura limpa no Back-end, mas por que o Front-end ainda parece o 'Velho Oeste' quando o assunto é organização?

Recentemente iniciei um projeto de média escala, e minha meta era que fosse organizado, escalável e que seguisse os princípios de arquitetura. Mas infelizmente não encontrei uma comunidade tão ativa e madura, como a do Back-end.

Então após muita pesquisa e estudo, decidi compartilhar a abordagem que estou aplicando nos meus projetos em Vue.js e JavaScript. Não é uma verdade absoluta, mas é uma solução eficaz e clara - e eu adoraria ouvir o que você acha dela.


1.0 Estruturação de Pastas

Exemplo da estruturação de pastas
Nessa abordagem, cada contexto de negócio é isolado em um módulo autossuficiente, dentro de cada um, temos:

  • data/repository: O repository é a camada que lida com a origem dos dados (nesse caso utilizei uma API pública);
  • domain/usecase: A camada de domínio, é o coração do módulo e não deve depender de nenhuma camada;
  • di: Aqui é onde o diferencial entra. Utilizamos o provide do Vue para realizar a inversão de dependência, disponibilizando as instâncias dos Casos de Uso para que qualquer componente do módulo possa consumi-los sem precisar instanciá-las manualmente;
  • keys: Aqui definimos os símbolos, que servirão como chaves para a injeção de dependência;
  • controller: Onde serão injetados os casos de uso necessários. Ele coordena as chamadas de dados e gerencia o estado reativo que a tela irá consumir;
  • view: Camada de apresentação.

2.0 Implementação

2.1 data/repository/fetch_company_repository.js:

Exemplo de uso do repository
Criamos uma instância do axios, recebemos ela aqui no repository, mas sem importá-lo diretamente e retornamos a resposta.


2.2 domain/usecase/fetch_company_usecase.js:

Exemplo de uso do usecase
Criamos uma função que recebe o repository por parâmetro.


2.3 di/company_di.js:

Exemplo de uso da di

Afinal, o que há de tão diferente nessa implementação? O segredo está na composição das dependências antes mesmo delas chegarem à interface.

Nesta etapa, conectamos as camadas que criamos anteriormente:

  1. O repository recebe a instância global do Axios (configurada com a URL base). Em seguida, o usecase recebe esse repository. Mantendo o código desacoplado.
  2. Criamos uma função que recebe o app (instância do Vue). Isso permite injetarmos as dependências globalmente ou de forma modular.
  3. Usamos essas keys (símbolos) para evitar algum tipo de conflito com as strings. Ao executar app.provide(key, instancia), estamos dizendo ao Vue: "Sempre que alguém pedir por esta chave, entregue esta instância pronta do Caso de Uso".

2.4 keys/company_keys.js:

Exemplo de uso das keys

Nesse arquivo definimos as keys do nosso módulo. Embora o vue permita uma string diretamente no app.provide("key", instancia), essa prática é arriscada em projetos escaláveis. Ao centralizar as chaves no objeto, eliminamos o risco de colisões.


2.5 di/index.js:

di centralizada
Para que a injeção de dependências funcione, centralizamos tudo em uma única função que recebe o app e o repassa para os demais controllers. Garantindo que haja apenas uma chamada na main.js.


2.6 controller/controller.js:

Exemplo de uso controller
O controller é o ponto onde a arquitetura se encontra com a reatividade do Vue. O grande diferencial aqui é que ele não 'sequestra' a lógica de busca, em vez disso, ele injeta o caso de uso por meio da chave única, garantindo que a lógica de negócio permaneça isolada e testável.


2.7 view/company_view.js:

Exemplo da view

E finalmente, o arquivo .vue aqui apenas importamos o controller e montamos o que a tela precisa.


3.0 Vantagem

Ao usarmos essa abordagem temos um sistema extremamente modular. E por estarmos usando JavaScript puro, isso nos dá o poder de uma arquitetura robusta sem a verbosidade ou a necessidade de bibliotecas externas de DI.

"Mas poderíamos simplesmente importar o UseCase direto no controller?" Sim, funcionaria. Mas ao usar provide/inject do Vue, quebramos o acoplamento rígido. Isso transforma nossos componentes em peças de lego, podemos trocar peças (usecase), por uma implementação diferente sem precisar alterar o código da interface.


Referências e Links úteis:

  • Inspiração da Estrutura: Este artigo foi baseado na arquitetura apresentada em: mestre-da-clean-architeure
  • API utilizada: Para os exemplos do repositório, utilizei a API: link da api

Conclusão:

Essa estrutura foi a solução que encontrei para garantir a escalabilidade do projeto em que estou trabalhando, meu objetivo aqui foi buscar uma 'certeza' a mais de que essa é uma boa abordagem para grandes projetos e, ao mesmo tempo, compartilhar um possível caminho para aqueles que talvez estejam com a mesma dificuldade que eu tive no início. Em invés de confiar cegamente na IA ou padrões pré-estabelecidos, decidi abrir essa abordagem para a comunidade.

Se você ficou com alguma dúvida , tem outra sugestão de melhoria ou utiliza uma abordagem diferente, adoraria ouvir seu comentário.