Por que é importante separar os arquivos da forma certa?
Uma boa organização dos arquivos busca separar responsabilidades e manter códigos relacionados próximos, reduzindo a complexidade e facilitando a manutenção do projeto.
Além disso, uma estrutura bem definida ajuda a:
- Facilitar a manutenção: encontrar e alterar um determinado código se torna mais simples.
- Reduzir o acoplamento: diferentes partes da aplicação ficam menos dependentes umas das outras.
- Aumentar a coesão: códigos que possuem responsabilidades relacionadas permanecem próximos.
- Facilitar os testes: responsabilidades bem separadas são mais fáceis de testar isoladamente.
- Melhorar a escalabilidade: novos recursos podem ser adicionados sem tornar a estrutura cada vez mais confusa.
- Facilitar o trabalho em equipe: desenvolvedores conseguem entender rapidamente onde cada tipo de código deve estar.
Portanto, organizar arquivos não significa apenas "deixar o projeto bonito". A estrutura de diretórios é uma forma de representar as responsabilidades e os limites definidos pela arquitetura da aplicação.
Qual o melhor modelo arquitetural de pastas?
Não existe um modelo de organização de pastas que seja universalmente melhor. A escolha depende do tamanho da aplicação, da sua complexidade, da quantidade de funcionalidades e de como o projeto deve evoluir ao longo do tempo.
Entre as estratégias mais tradicionais de organização do código estão os:
- Pacotes por Camada (Layer Architecture);
- Pacotes por Funcionalidade (Feature Architecture).
A principal diferença está na forma como o código é agrupado:
- Layer Architecture: organiza os arquivos de acordo com sua responsabilidade técnica.
- Feature Architecture: organiza os arquivos de acordo com a funcionalidade ou domínio ao qual pertencem.
Por exemplo, em um sistema financeiro:
Por Camada
controllers/
services/
repositories/
models/
Por Funcionalidade
users/
transactions/
reports/
authentication/
Ambos os modelos são válidos e podem funcionar muito bem. A escolha deve considerar coerência, facilidade de manutenção e capacidade de evolução, e não simplesmente qual estrutura possui mais ou menos pastas.
Pacotes por Camada (Layer Architecture)
A Layer Architecture, também conhecida como Package by Layer, organiza os arquivos de uma aplicação de acordo com a responsabilidade técnica que cada um possui.
Nesse modelo, arquivos que desempenham funções semelhantes ficam agrupados na mesma camada, independentemente da funcionalidade à qual pertencem.
Por exemplo, uma aplicação pode possuir camadas como:
controllers/
services/
repositories/
models/
Nesse caso, todos os controllers ficam em controllers/, todos os services em services/, e assim por diante.
A ideia é estabelecer uma separação clara entre as diferentes responsabilidades da aplicação.
Essa abordagem pode ser utilizada tanto no backend quanto no frontend, embora as camadas e responsabilidades possam variar de acordo com a tecnologia utilizada.
Backend
No backend, a organização por camadas é bastante comum. Uma aplicação pode ser estruturada da seguinte maneira:
Exemplo
src/
├── controller/
│ ├── UserController.java
│ └── TransactionController.java
│
├── service/
│ ├── UserService.java
│ └── TransactionService.java
│
├── repository/
│ ├── UserRepository.java
│ └── TransactionRepository.java
│
├── model/
│ ├── User.java
│ └── Transaction.java
│
└── dto/
├── UserRequest.java
└── TransactionRequest.java
Perceba que a organização acontece pelo tipo de responsabilidade, e não pela funcionalidade.
Por exemplo, todos os controllers estão juntos:
controller/
├── UserController.java
└── TransactionController.java
Mesmo que UserController e TransactionController pertençam a contextos diferentes.
O mesmo acontece com os services e repositories:
service/
├── UserService.java
└── TransactionService.java
repository/
├── UserRepository.java
└── TransactionRepository.java
Essa estrutura é simples, previsível e fácil de entender, especialmente em aplicações menores.
Frontend
O mesmo princípio pode ser aplicado ao frontend. Em vez de organizar os arquivos por funcionalidade, eles são agrupados de acordo com sua responsabilidade.
Exemplo
src/
├── components/
│ ├── UserCard.tsx
│ └── TransactionTable.tsx
│
├── pages/
│ ├── Login.tsx
│ ├── Dashboard.tsx
│ └── Transactions.tsx
│
├── hooks/
│ ├── useAuth.ts
│ └── useTransactions.ts
│
├── services/
│ ├── auth.service.ts
│ └── transaction.service.ts
│
├── types/
│ ├── user.ts
│ └── transaction.ts
│
└── utils/
├── formatCurrency.ts
└── formatDate.ts
Nesse exemplo:
-
components/contém componentes reutilizáveis da interface; -
pages/contém as páginas da aplicação; -
hooks/contém hooks personalizados; -
services/concentra a comunicação com APIs ou outros serviços; -
types/contém as definições de tipos; -
utils/contém funções utilitárias.
Assim como no backend, a principal característica é que arquivos de responsabilidades semelhantes permanecem juntos.
Por exemplo, tudo relacionado à comunicação com a API fica em:
services/
├── auth.service.ts
└── transaction.service.ts
Enquanto os componentes ficam em:
components/
├── UserCard.tsx
└── TransactionTable.tsx
Essa abordagem funciona bem em projetos menores e é bastante simples de compreender. Entretanto, à medida que o sistema cresce, uma mesma funcionalidade pode ficar distribuída entre várias pastas, aumentando a quantidade de locais que o desenvolvedor precisa percorrer para compreender ou modificar aquela funcionalidade.
Pacotes por Funcionalidade (Feature Architecture)
A Feature Architecture, também conhecida como Package by Feature, organiza os arquivos de uma aplicação de acordo com a funcionalidade ou domínio ao qual pertencem.
Diferentemente da organização por camadas, em que arquivos de responsabilidades semelhantes ficam agrupados, nesse modelo os arquivos relacionados a uma mesma funcionalidade permanecem juntos.
Por exemplo, em um sistema financeiro:
authentication/
users/
transactions/
reports/
Dentro de cada funcionalidade podem existir diferentes tipos de arquivos:
transactions/
├── TransactionController.java
├── TransactionService.java
├── TransactionRepository.java
└── Transaction.java
Nesse caso, todos os arquivos relacionados a transações estão dentro do mesmo pacote.
A principal ideia dessa abordagem é aumentar a coesão, mantendo próximo tudo aquilo que pertence ao mesmo contexto ou funcionalidade.
Assim, enquanto a organização por camadas responde:
"Qual é a responsabilidade deste arquivo?"
A organização por funcionalidade responde:
"A qual funcionalidade este arquivo pertence?"
Essa abordagem pode ser utilizada tanto no backend quanto no frontend.
Backend
No backend, podemos organizar a aplicação agrupando todos os componentes de uma determinada funcionalidade em um mesmo pacote.
Exemplo
src/
├── authentication/
│ ├── AuthenticationController.java
│ ├── AuthenticationService.java
│ └── AuthenticationRepository.java
│
├── user/
│ ├── UserController.java
│ ├── UserService.java
│ ├── UserRepository.java
│ └── User.java
│
├── transaction/
│ ├── TransactionController.java
│ ├── TransactionService.java
│ ├── TransactionRepository.java
│ └── Transaction.java
│
└── report/
├── ReportController.java
├── ReportService.java
└── ReportService.java
Perceba que agora os arquivos não estão mais agrupados apenas pelo seu tipo.
Por exemplo, em vez de termos:
controller/
├── UserController.java
└── TransactionController.java
service/
├── UserService.java
└── TransactionService.java
repository/
├── UserRepository.java
└── TransactionRepository.java
temos:
user/
├── UserController.java
├── UserService.java
├── UserRepository.java
└── User.java
transaction/
├── TransactionController.java
├── TransactionService.java
├── TransactionRepository.java
└── Transaction.java
Dessa forma, para trabalhar na funcionalidade de transações, o desenvolvedor encontra grande parte do código necessário em um único local.
Essa característica pode ser especialmente vantajosa em aplicações maiores, nas quais existem muitas funcionalidades e diferentes domínios de negócio.
Frontend
O mesmo conceito pode ser aplicado ao frontend.
Em vez de manter todos os componentes, hooks e services em pastas globais, podemos agrupá-los de acordo com a funcionalidade à qual pertencem.
Exemplo
src/
├── authentication/
│ ├── components/
│ │ └── LoginForm.tsx
│ ├── hooks/
│ │ └── useAuth.ts
│ ├── services/
│ │ └── auth.service.ts
│ └── types/
│ └── auth.ts
│
├── users/
│ ├── components/
│ │ └── UserCard.tsx
│ ├── hooks/
│ │ └── useUsers.ts
│ ├── services/
│ │ └── user.service.ts
│ └── types/
│ └── user.ts
│
└── transactions/
├── components/
│ └── TransactionTable.tsx
├── hooks/
│ └── useTransactions.ts
├── services/
│ └── transaction.service.ts
└── types/
└── transaction.ts
Nesse exemplo, tudo que pertence à funcionalidade de transações está dentro de transactions/.
Isso inclui seus componentes, hooks, services e tipos.
Assim, quando um desenvolvedor precisa trabalhar nessa funcionalidade, não precisa procurar por arquivos espalhados entre components/, hooks/, services/ e types/.
A estrutura passa a refletir mais diretamente as funcionalidades que existem no sistema, e não apenas os tipos de arquivos utilizados para implementá-las.
Essa organização também facilita a evolução do projeto, pois novas funcionalidades podem ser adicionadas como novos módulos independentes:
src/
├── authentication/
├── users/
├── transactions/
├── reports/
└── notifications/
Cada funcionalidade possui seu próprio espaço dentro da aplicação, mantendo seus arquivos relacionados próximos e reduzindo a dispersão do código.
Desde já, agradeço a todos e espero que este conteúdo possa ajudar outros desenvolvedores a montar um ambiente de desenvolvimento mais eficiente para agentes de IA.
Att.
Gustavo Machado Pontes
🌐 Linktree: https://linktr.ee/DevGustavus
💼 LinkedIn: https://www.linkedin.com/in/gustavo-machado-p/
💻 GitHub: https://github.com/DevGustavus
📷 Instagram: https://www.instagram.com/devgustavus/
🐦 X (Twitter): https://twitter.com/Gustavo72166607

Top comments (1)
Caso encontrem alguma inconsistência nas informações, por favor, comentem ou me mandem mensagem!
Desde já agradeço a todos.