DEV Community

Cover image for Você Organiza seu Projeto por Camada ou por Funcionalidade?
Gustavo Machado
Gustavo Machado

Posted on

Você Organiza seu Projeto por Camada ou por Funcionalidade?

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/
Enter fullscreen mode Exit fullscreen mode

Por Funcionalidade

users/
transactions/
reports/
authentication/
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Enquanto os componentes ficam em:

components/
├── UserCard.tsx
└── TransactionTable.tsx
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

Dentro de cada funcionalidade podem existir diferentes tipos de arquivos:

transactions/
├── TransactionController.java
├── TransactionService.java
├── TransactionRepository.java
└── Transaction.java
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

temos:

user/
├── UserController.java
├── UserService.java
├── UserRepository.java
└── User.java

transaction/
├── TransactionController.java
├── TransactionService.java
├── TransactionRepository.java
└── Transaction.java
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
devgustavus profile image
Gustavo Machado

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