Large and scalable Laravel application with Domains
Tags: laravel, programming, ddd, discuss
Published: 2022-04-29 12:19:49

A few months ago, we talk about the [repository pattern in Laravel](https://dev.to/victoor/repository-pattern-in-laravel-it-s-worth-3mn4) and the reason I don't like it. 

In this post, we are going to talk about how to structure our project if we are working on a large Laravel application.

I found that the default Laravel structure doesn't work with large projects while I was working in my side project, [monse](https://get.monse.app). With every commit I made, I felt that the project it's getting overwhelming, because it was impossible to see all the code related to a desire model at a glance.

> 🎉  By the way, **monse is a simple and automated personal finances
> for normal people**.
>  
> If you want to stop over-expending, retire early and happier, 
> [take a look now](https://get.monse.app).

#### Table of content
1. [The problem with the default folder structure](#the-problem-with-the-default-folder-structure)
2. [The solution](#the-solution)
3. [How to implement this in Laravel](#how-to-implement-this-in-laravel)
   1. [Routes](#1-routes)
   2. [Events](#2-events)
   3. [Commands](#3-commands)
   4. [Factories](#4-factories)

### The problem with the default folder structure
By default, a Laravel application structure is something like this:

```shell
├── app
│   ├── Console
│   │   └── Commands
│   ├── Contracts
│   ├── Events
│   ├── Exceptions
│   │   └── Auth
│   ├── Http
│   │   ├── Controllers
│   │   ├── Middleware
│   │   ├── Requests
│   │   └── Resources
│   ├── Jobs
│   ├── Listeners
│   ├── Mail
│   │   ├── Auth
│   │   └── User
│   ├── Models
│   ├── Notifications
│   ├── Policies
│   └── Providers
├── database
│    ├── factories
│    ├── migrations
│    └── seeders
├── config
├── routes
└── resources
    ├── js
    ├── sass
    └── views
        ├── mail
        └── vendor

```

There is nothing wrong  with this structure, but when we are working on a big application it's difficult to check, at a glance, all the models, controllers, and services involved in one request, for example.

Imagine that you have a model called `Article` with a `PostArticleController`, a `GetArticleController` and also some events like `ArticleCreated`, `ArticleUpdated` and more.  

You can create a subfolder inside any of this already created folders called Article to split different parts of your application, and you will have something like this:

```shell
├── app
│   ├── Console
│   │   └── Commands
│   ├── Contracts
│   ├── Events
│   │   └── Article 
│   │       ├── ArticleCreated.php 
│   │       ├── ArticleUpdated.php 
│   │       └── ArticleDeleted.php 
│   ├── Exceptions
│   │   ├── Auth
│   │   └── Article 
│   │       └── ArticleWithoutValidDate.php
│   ├── Http
│   │   ├── Controllers
│   │   │   └── Article 
│   │   │       ├── PostArticleController.php 
│   │   │       └── GetArticleController.php 
│   │   ├── Middleware
│   │   ├── Requests
│   │   │   └── Article 
│   │   │       ├── PostArticleRequest.php 
│   │   │       └── GetArticleRequest.php 
│   │   └── Resources
│   │   │   └── Article 
│   │   │       └── ArticleResource.php 
│   ├── Jobs
│   ├── Listeners
│   │   └── Article 
│   │       ├── SendNotificationOnArticleCreated.php 
│   │       └── UpdateDashboardStatsOnArticleDeleted.php 
│   ├── Mail
│   │   ├── Auth
│   │   └── User
│   ├── Models
│   │   └── Article.php 
│   ├── Notifications
│   │   └── Article 
│   │       └── NewArticleNotification.php 
│   ├── Policies
│   └── Providers
├── database
│    ├── factories
│    ├── migrations
│    └── seeders
├── config
├── routes
└── resources
    ├── js
    ├── sass
    └── views
        ├── mail
        └── vendor
```

As you can see, the folders are growing a lot, and we still have all our Article code spreader between all the application.

It's difficult to see, at a glance, all the code related with our domain model Article.

And this is only the beginning, in a big application we are going to have like 10 or more models with his controllers, events, listeners, and more. This is going to be a problem.

## The solution

The idea it's to make domain folders or bounded contexts to store all the code related to one model of our application in one folder only.

The idea it's to achieve a folder structure like this:

```bash
src
├── BankAccount
│   ├── Actions
│   ├── Http
│   ├── Infrastructure
│   ├── Policies
│   └── BankAccount.php
├── BankConnection
│   ├── Actions
│   ├── Console
│   ├── Events
│   ├── Http
│   ├── Infrastructure
│   ├── Jobs
│   ├── Listeners
│   ├── Mail
│   ├── Notifications
│   ├── Policies
│   └── BankConnection.php
├── StockOrder
│   ├── Actions
│   ├── Http
│   ├── Infrastructure
│   ├── Listeners
│   ├── Policies
│   ├── Support
│   └── StockOrder.php
└── UserStockPortfolio
    ├── Actions
    ├── Console
    ├── Http
    ├── Infrastructure
    ├── Jobs
    └── UserStockPortfolio.php
```

> This is the current folder structure of my Laravel project,  [get.monse.app](https://get.monse.app).

As you can see, inside each folder we have the model and different classes related to it. We can see all the actions, HTTP controllers, jobs, listeners, policies, and more.

This way, when I'm working on `StockOrders`, for example, I can see all the code related, and it's easier to add new controllers, routes, and more without affecting all the application.

## How to implement this in Laravel
Implementing this in Laravel it's simple.

First, you need to create a new `src` folder and manually add it to the composer file. I define the namespace as `Monse\\`, because it's my project name, but you can use anything else.

```
    // ...

    "psr-4": {
      "App\\": "app/",
      "Monse\\": "src/",
      "Database\\Factories\\": "database/factories/",
      "Database\\Seeders\\": "database/seeders/"
    },

    // ...
```

Now we can add whatever we want to this folder and will be automatically load by composer, but there are some things that we need to take care.

We want each module to have his own routes, events, commands and all. To do this, we need to create different service providers and add them to our `config/app.php` file.

### 1. Routes
For routes we are going to create a new service provider extending from `Illuminate\Foundation\Support\Providers\RouteServiceProvider`. In this file we just need to implement the boot method like this:

```php
    // ...

    public function boot(): void
    {
        $this->routes(function () {
            Route::middleware(['api', 'auth:api'])->prefix('api')->group(function () {
                Route::get('user', GetUserController::class);

                // ...
            });
        });
    }

    // ...
```

### 2. Events
For events, we can extend from `Illuminate\Foundation\Support\Providers\EventServiceProvider` and define the `$listen` array. Something like this:

```php
    protected $listen = [
        UserCreated::class => [
            SendWelcomeMailOnUserCreated::class,
            // ...
        ],
    ];
```

### 3. Commands
For commands, we will create a normal service provider and register them using the `register` method:

```php
    // ...

    private array $commands = [
        CreateUserCommand::class,
    ];

    public function register(): void
    {
        $this->commands($this->commands);
    }

    // ...
```

### 4. Factories
These are a bit tricky, but it's easy.

We need to put the factory UserFactory, for example, inside the `factories/Monse/User` folder (because we define namespace of our folder as Monse in the first step).

Also, we need to define, inside the factory the model that is owning that factory:

```php
    public function modelName(): string
    {
        return User::class;
    }
```

Everything else will be working as expected without anything special to do.

---

**What do you think? Do you like this project structure?**