Foto de Mohammad Rahmani na Unsplash
Quando o projeto Laravel é pequeno, quase qualquer organização funciona. Você cria controllers, models, requests, alguns services, resolve tudo no “app/” e segue. O problema começa quando o sistema cresce: mais módulos, mais regras, integrações, filas, diferentes times mexendo no mesmo lugar… e, de repente, o que era simples vira uma base difícil de evoluir.
Estruturar um projeto Laravel bem não é sobre “inventar arquitetura”. É sobre reduzir atrito: facilitar manutenção, evitar acoplamento desnecessário e deixar o código previsível para quem chega depois.
A seguir estão práticas que funcionam bem em projetos médios e grandes, especialmente quando já existe operação de produção, roadmap e mudanças frequentes.
Controller deveria orquestrar: receber request, validar, chamar a camada certa e responder. Quando regra de negócio fica no controller, você cria um ponto de acoplamento difícil de testar e difícil de reutilizar. Em projeto grande isso vira padrão ruim rapidamente, porque todo mundo copia o que já existe.
O que funciona melhor é mover lógica para classes dedicadas (services/use cases/actions), e deixar o controller como uma “casca” fina. Você ganha clareza, reaproveitamento e testes mais simples.
A estrutura padrão do Laravel (Controllers, Models, Jobs, etc.) é ótima até um certo ponto. Em sistemas grandes, ela tende a espalhar a mesma funcionalidade por várias pastas e o dev precisa “caçar” arquivos para entender um fluxo.
Uma abordagem que escala melhor é organizar por domínio/feature. Exemplo: tudo relacionado a “Billing” fica próximo (requests, actions, policies, resources, etc.). Isso reduz a fricção para manter e evoluir partes específicas do sistema.
Você não precisa mudar tudo de uma vez. Dá para começar com um ou dois domínios e ir migrando aos poucos, sem reescrever o projeto inteiro.
app/
├── Http/
│ ├── Controllers/
│ ├── Requests/
│ └── Resources/
├── Models/
├── Jobs/
├── Policies/
├── Services/
Problema em projeto grande:
para entender Billing, você precisa abrir 6 pastas diferentes.
Estrutura por domínio / feature
app/
└── Domains/
└── Billing/
├── Http/
│ ├── Controllers/
│ ├── Requests/
│ └── Resources/
├── Actions/
├── Jobs/
├── Policies/
├── Models/
└── Services/
Ou até mais simples:
app/
└── Billing/
├── BillingController.php
├── CreateInvoice.php
├── Invoice.php
├── InvoicePolicy.php
├── SendInvoiceJob.php
└── InvoiceResource.php
O Laravel não se importa com a pasta, desde que o namespace esteja correto.
Porque quando alguém entra no projeto e precisa mexer em Billing:
Em projeto grande, isso faz muita diferença.
Em projeto grande, dado ruim entrando vira bug caro. Centralizar validação em Form Requests mantém o controller limpo e torna regras de entrada explícitas.
Além disso, vale padronizar:
Quando a validação é consistente, o sistema vira mais previsível e o time para de reinventar a roda a cada endpoint.
Quando cada endpoint responde de um jeito, cada client precisa de lógica diferente, e isso vira dívida técnica distribuída. Para projetos médios e grandes, uma camada consistente de resposta é obrigatória: Resources, transformers, ou uma padronização interna.
O ponto aqui não é “estética”. É operação. Quando algo quebra, você quer logs previsíveis, payload previsível, erros previsíveis. Isso reduz tempo de diagnóstico.
Um erro comum em Laravel é “decidir” coisas dentro de models e ao mesmo tempo persistir em todo lugar, sem fronteira clara. Em sistemas maiores, o ideal é separar:
Isso permite evoluir regra sem quebrar camada de persistência, e vice-versa. E facilita testar o que realmente importa: a regra.
Em projeto grande, você inevitavelmente vai para filas: e-mails, notificações, integrações, processamento pesado. O que quebra sistemas grandes não é “usar fila”. É usar fila sem cuidado com:
Se o seu projeto depende de integração externa, trate isso como cenário normal: o externo vai falhar. A fila é o lugar certo para absorver isso, desde que bem desenhado.
Integrações externas deveriam estar em classes próprias (clients/adapters), com:
Evite espalhar Http::post() por todo lugar. Em projeto grande isso vira caos, porque não existe ponto único para ajustar comportamento (ex: mudar header, adicionar auth, alterar timeout).
Projeto grande normalmente tem muita migration. E migration desorganizada vira armadilha: ambientes quebram, dev novo não sobe projeto, staging fica inconsistente.
Boas práticas que ajudam:
Isso evita que o setup do projeto vire uma gincana.
Quando cresce, o sistema precisa rodar em múltiplos ambientes e, muitas vezes, múltiplos clientes. Configuração hardcoded vira problema de deploy e vira risco de segurança.
Padronize:
config/*.php como fonte de configuração;.env apenas para valores variáveis;Em projetos grandes, o custo de “descobrir o que aconteceu” é alto. Sem logs bons você perde tempo, perde confiança e perde previsibilidade operacional.
O que vale padronizar:
Isso é o que transforma incidentes em diagnóstico rápido.
Não dá para testar tudo em projeto grande, e tentar fazê-lo costuma falhar. O que dá mais retorno é:
Teste não é para “subir porcentagem”. É para reduzir risco onde mais dói.
Projetos Laravel grandes não quebram porque Laravel é limitado. Eles quebram porque a base cresce sem estrutura, sem padrões e sem fronteiras claras entre responsabilidades.
Se você mantiver controllers finos, separar domínio/feature, isolar integrações, tratar filas com seriedade e padronizar validação/retorno/log, o projeto continua evoluindo sem virar um monstro. E o melhor: o time consegue manter velocidade sem sacrificar estabilidade.
WhatsApp é um canal excelente para alerta porque ele tem uma característica que e-mail e…
Durante alguns anos, o AMP foi tratado como solução quase obrigatória para quem queria desempenho…
Performance, acessibilidade e boas práticas deixaram de ser “detalhes técnicos” e passaram a impactar diretamente…
O cenário (realista) Você quer receber um evento via API (Webhook), enviar uma mensagem no…
Automação deixou de ser algo exclusivo de grandes sistemas. Hoje, boa parte das aplicações depende…
Durante muito tempo, o WhatsApp foi visto apenas como um canal de comunicação direta: mensagens,…