Clean Architecture in Laravel Without the Acronym Sprawl: A Folder Layout That Survives Real Projects
A pragmatic folder split for Laravel that keeps Eloquent at the boundary, respects dependency direction, and avoids over-engineering small CRUD apps.
- Home-
- Categories-
- Backend Development-
Clean Architecture in Laravel Without the Acronym Sprawl: A Folder Layout That Survives Real Projects
Clean Architecture in Laravel Without the Acronym Sprawl: A Folder Layout That Survives Real Projects
Clean architecture is often taught with a wall of acronyms: entities, use cases, interfaces, adapters, ports. That is useful in theory, but in real Laravel projects it usually leads to over-engineering. The goal is not to tick boxes. The goal is to keep business logic independent from framework details so the codebase survives years of change.
The pragmatic split
A simple, battle-tested approach is to split code into three core layers: Domain, Application, and Infrastructure. This keeps the dependency direction clear without inventing twenty namespaces.
Domain contains the core business concepts. This includes entities, value objects, enums, and domain rules. Nothing in Domain should depend on Laravel, Eloquent, HTTP, or external APIs. It should be pure PHP that represents your business.
Application contains the use cases: what the system does. This is where you coordinate workflows, apply business rules, and define interfaces for external dependencies. Application depends only on Domain. It does not know about controllers, jobs, or databases.
Infrastructure is the outermost layer. This is where Laravel lives: Eloquent models, repositories, jobs, HTTP clients, services that call third parties, and framework-specific code. Infrastructure implements the interfaces defined in Application and depends on both Application and Domain.
Keeping Eloquent in the repository boundary
One of the most common mistakes is letting Eloquent leak everywhere. Eloquent models are convenient, but they couple your business logic to the database schema and framework features. The pragmatic fix is to treat Eloquent as an implementation detail.
Define repository interfaces in the Application layer. For example, an OrderRepository interface that describes methods like findById or save. Implement that interface in Infrastructure using Eloquent models. Your use cases depend only on the interface, not on the model classes. This makes it easy to swap implementations or test use cases with in-memory fakes.
Also, avoid returning Eloquent collections or models from repositories to the Application layer. Map them to Domain entities or simple DTOs. This keeps the boundary clean and prevents lazy loading or framework behavior from creeping into your business logic.
Dependency direction matters
The key rule is: dependencies point inward. Infrastructure can depend on Application and Domain. Application can depend only on Domain. Domain depends on nothing outside itself. This is the core of keeping your code decoupled from the framework.
Controllers, routes, jobs, and commands live in Infrastructure. They should be thin orchestrators. They call Application use cases, map HTTP requests to DTOs, and return responses. If your controllers are doing validation, calculations, or complex queries, that logic probably belongs in Application or Domain.
When not to over-layer
Clean architecture has a cost. For a small CRUD app, adding three layers for every feature creates unnecessary indirection. If your app is mostly data entry with little business logic, a traditional Laravel structure is perfectly fine.
The signal to add layers is complexity. If you have non-trivial business rules, multiple integrations, or the domain is likely to outlive the framework choice, the investment pays off. If you are still validating product-market fit, keep it simple and refactor toward these boundaries as complexity emerges.
Practical folder layout
A minimal, maintainable structure looks like this. In app/, create Domain/, Application/, and Infrastructure/. Alternatively, use src/ if you prefer. Keep Laravel's existing directories where they make sense, but move business logic out.
Domain can hold Entities/, ValueObjects/, Enums/, and domain-specific exceptions. Application can hold UseCases/, Contracts/ (interfaces), DTOs/, and Policies/. Infrastructure can hold Repositories/, Services/, Jobs/, Http/Controllers/, and any framework-specific adapters.
This keeps namespaces clear and makes it obvious where new code belongs. New developers can follow the dependency rule without memorizing every acronym from the textbooks.
Key takeaways
- Split into Domain, Application, Infrastructure to keep business logic framework-agnostic.
- Define repository interfaces in Application and implement them with Eloquent in Infrastructure.
- Map Eloquent models to Domain entities or DTOs at the boundary, never leak models inward.
- Follow the inward dependency rule: Infrastructure depends outward, Domain depends on nothing.
- Only layer when business complexity justifies it. Small CRUD apps do not need full clean architecture.
FAQ
Q: Do I have to throw away Eloquent? A: No. Eloquent is a great ORM. Keep it in Infrastructure as an implementation detail behind repository interfaces.
Q: Is this the same as hexagonal architecture? A: Similar idea, different naming. The goal is ports and adapters: isolate the core from external concerns. This pragmatic split captures that without acronym sprawl.
Q: How do I test this structure? A: Test Domain and Application with fast unit tests (no Laravel). Test Infrastructure with integration tests that use the database or mocks as needed. This keeps your feedback loop fast.
136
1940
you might also like...
The staged path from a slow WordPress site to a fast Next.js one, keeping Persian content, Persian URLs and search traffic intact.
The App Router and React Server Components are not just new APIs; they change where your code runs. Here is how they cut bundle size, simplified data fetching, and made my pages measurably faster.


