Domain-Based Laravel Project Structure for Scalability

Domain-Based Laravel Project Structure

In this article you will learn about, Domain-Based Laravel Project Structure.
As Laravel projects grow, maintaining a clear structure becomes critical. Traditional organization (controllers and models in separate folders) can lead to spaghetti code in complex systems. In this guide, we’ll explore domain-based organization—a professional strategy adopted by companies like Spotify and Amazon to scale applications successfully.


What Is Domain-Based Organization?

Domain-based organization groups all artifacts of a feature (controllers, models, services, views) into a single folder. Imagine an e-commerce platform:

  • “Orders” Domain: Contains OrderControllerOrderOrderService, policies, and order migrations.
  • “Users” Domain: Includes UserProfileControllerSubscription, and authentication services.

Comparison with Traditional Structure

Traditional Domain-Based
app/Http/Controllers/* app/Domains/Orders/Controllers/
app/Models/* app/Domains/Orders/Models/
resources/views/* app/Domains/Orders/Views/

Key Advantage: Reduces cognitive distance between related files, speeding up team development.


Benefits for Growing Projects

1. Intuitive Navigation

Instead of jumping between app/Modelsapp/Services, and app/Http/Controllers, developers focus on one folder per feature. Example:

  • app/Domains/Products/:
    • Controllers/ProductController.php
    • Models/Product.php
    • Services/InventoryService.php
    • Views/products/*.blade.php

2. Context Isolation

Each domain acts as an independent module, enabling:

  • Focused unit tests (e.g., tests/Domains/Orders/OrderTest.php).
  • Reusability in other projects (e.g., a payment module as an internal package).
  • Clear task delegation across teams (e.g., a “Checkout” squad managing the Orders domain).

3. Evolution Without Coupling

Adding features like “discount coupons” doesn’t require modifying global folders—just create app/Domains/Discounts/.


Step-by-Step Implementation in Laravel 11

Step 1: Creating the Folder Structure

Generate domain folders via the terminal:

mkdir -p app/Domains/{Orders,Users,Products}/{Controllers,Models,Services,Views} 

Step 2: Moving Existing Files

Restructure incrementally:

// Old: app/Models/Order.php  
// New: app/Domains/Orders/Models/Order.php  

namespace App\Domains\Orders\Models;  

use Illuminate\Database\Eloquent\Model;  

class Order extends Model { /* ... */ } 

Step 3: Configuring Namespaces

Update composer.json to autoload domains:

"autoload": {  
    "psr-4": {  
        "App\\": "app/",  
        "App\\Domains\\": "app/Domains/"  
    }  
}  

Run:

composer dump-autoload 

Step 4: Organizing Routes

Group routes by domain in routes/web.php:

Route::group(['namespace' => 'App\Domains\Orders\Controllers'], function () {  
    Route::get('/orders', 'OrderController@index');  
    Route::post('/orders', 'OrderController@store');  
});  

Advanced Alternative: Create separate route files like routes/orders.php and register them via RouteServiceProvider.


Practical Example: “Subscriptions” Domain Structure

app/  
└── Domains/  
    └── Subscriptions/  
        ├── Controllers/  
        │   ├── SubscriptionController.php  
        │   └── InvoiceController.php  
        ├── Models/  
        │   ├── Subscription.php  
        │   └── Payment.php  
        ├── Services/  
        │   ├── BillingService.php  
        │   └── NotificationService.php  
        ├── Views/  
        │   └── subscriptions/  
        │       ├── index.blade.php  
        │       └── invoice.blade.php  
        └── Policies/  
            └── SubscriptionPolicy.php

When (and When Not) to Use This Approach

Ideal Cases:

  • Enterprise Apps: ERPs, marketplaces, or SaaS platforms with 50+ models.
  • Large Teams: Squads working on parallel features without conflicts.
  • Long-Term Projects: Where maintainability is a priority.

Avoid When:

  • Rapid Prototypes: MVP apps with fewer than 10 models.
  • Small Teams: Lacking resources for restructuring.
  • Third-Party Packages: External libraries typically follow the default structure.

Common Challenges and Solutions

Issue 1: Service Providers Can’t Find Classes

Solution: Register domain-specific providers:

// AppServiceProvider.php  
public function register() {  
    $this->app->register(\App\Domains\Orders\Providers\OrderServiceProvider::class);  
} 

Issue 2: Disorganized Migrations

Solution: Create subfolders and update composer.json:

"autoload": {  
    "classmap": [  
        "database/migrations/orders/",  
        "database/migrations/users/"  
    ]  
} 

Tools to Simplify the Process

  1. Laravel Modules: Package for creating isolated domain modules.
  2. IDE Helper: Generates autocompletion for custom namespaces.
  3. PHPStan: Detects namespace errors post-restructuring.

Success Metrics

After implementation, monitor:

  • Onboarding Time: New developers should understand features 30% faster.
  • Coupling: Less than 5% of classes should depend on other domains.
  • Build Times: Unit tests run faster due to isolation.

Conclusion: Planning for the Future

Domain-based organization is an upfront investment that pays off in long-term projects. Start with critical modules (e.g., payments) and expand gradually. Remember:

  1. Document: Keep a README.md in each domain explaining responsibilities.
  2. Automate: Use scripts to move files and update namespaces.
  3. Validate: Conduct technical retrospectives to adjust the structure.

For small projects, Laravel’s default structure remains valid. The key is choosing the right approach for your system’s current stage.

Thanks for reading,  Domain-Based Laravel Project Structure.

Rolar para cima