DEV Community

Matt Moore
Matt Moore

Posted on • Updated on • Originally published at

Scaling Laravel Architecture

Laravel ships with a great starting directory structure, one which may support your entire project, depending on the size. If, however, you notice it’s getting harder and harder to find code that you want to work on, you may want to step back and rethink your app’s architecture.

The most popular patterns I’ve seen in the Laravel world are Domain Driven Design and Hexagonal Architecture. While I’m by no means a DDD expert, I have found it works well within Laravel’s patterns, given you don’t try to follow it exactly.

For instance, as this blog post points out, Eloquent substantially breaks DDD principles. I think the mistake here is trying to implement “pure” DDD rather than using it as a guideline.

Eloquent is an Active Record ORM, and therefore is inherently incompatible with DDD. This is not a bad thing! I prefer leaning into this rather than fighting it and embracing the power Eloquent gives you alongside the benefits of DDD.

I find that these Laravel components map to the following Domain-Driven Design layers quite nicely.

  • Controllers/Console Command Classes - Presentation
  • Jobs - Application
  • Repositories - Infrastructure
  • Models - Domain


Controllers are responsible for the presentation layer of DDD. Whether you are building an API or an application that returns HTML views, your controllers are responsible for presenting this content to the user. Custom Artisan command classes can also be considered part of the presentation layer.


The application layer of a domain-driven codebase is responsible for orchestrating the Domain and Infrastructure layers to perform high-level tasks in your application.

I like to use Laravel’s Command Bus for this layer. I adopted the idea from PyroCMS, and haven’t looked back since. I have also seen the Application layer represented using Gateways, which is outlined in this post, but I think encapsulating features into a single job class is cleaner.

A big benefit of using Jobs is that you can dispatch them from any presentation layer. Just make sure you use dispatch_now() function so it doesn’t get queued.

$result = dispatch_now(new CreatePost($request));
Enter fullscreen mode Exit fullscreen mode


The infrastructure layer deals with anything that interacts with code or services outside of the application. This includes things like emails, API calls, and,most commonly, database interactions. I am a huge fan of the repository pattern for interacting with the database in a Laravel application. Encapsulating query logic and model retrieval is a sure way to reduce bugs and promote code reuse for database interactions.


The domain layer contains all business logic and is the heart of your application. This is the layer that can get muddy when using Eloquent since your domain models know about your database. If we ignore this aspect, and only use repositories to interact with the database, I believe we can satisfy the requirement of separating these concerns sufficiently.

I’ve seen hacks in Eloquent to disable database calls but I believe this is unnecessary. I find that baking these standards into the project at the documentation/code review level is sufficient.

An alternative to Eloquent is using Laravel Doctrine. This gives you everything you need to create a true Domain layer using Entities and a data-mapper for the infrastructure layer. There are a number of benefits to this approach when working on a truly large application, but it might be overkill for smaller applications.

I typically enjoy Eloquent, especially for the speed it provides when you’re hacking together a prototype or proof of concept, and believe it can even work fine for large applications. I have, however, experienced some of its shortcomings first hand, and think that in certain situations, an alternative such as Data Mapper is a completely valid choice. It’s important that you always weigh your options and pick the right tool for the job.

Here is a fantastic article that takes an honest look at both patterns and their tradeoffs

Okay, so what does this actually look like in a Laravel application? Let’s go through the steps I typically take when starting a new project following this architecture.


Let’s say we want to build a personal site with a blog and a projects section. Let’s call it our Portfolio.

Note: A site this small would work fine with the default directory structure and basic Laravel architecture, but ignore that for now.

Download a fresh copy of Laravel using either composer create-project or the laravel CLI as outlined in the documentation.

Namespace your app using

php artisan app:name Portfolio
Enter fullscreen mode Exit fullscreen mode

Moving the core code.

Your initial app directory structure should look something like this:

├── Console
│   └── Kernel.php
├── Exceptions
│   └── Handler.php
├── Http
│   ├── Controllers
│   │   ├── Auth
│   │   │   ├── ForgotPasswordController.php
│   │   │   ├── LoginController.php
│   │   │   ├── RegisterController.php
│   │   │   ├── ResetPasswordController.php
│   │   │   └── VerificationController.php
│   │   └── Controller.php
│   ├── Kernel.php
│   └── Middleware
│       ├── Authenticate.php
│       ├── CheckForMaintenanceMode.php
│       ├── EncryptCookies.php
│       ├── RedirectIfAuthenticated.php
│       ├── TrimStrings.php
│       ├── TrustProxies.php
│       └── VerifyCsrfToken.php
├── Providers
│   ├── AppServiceProvider.php
│   ├── AuthServiceProvider.php
│   ├── BroadcastServiceProvider.php
│   ├── EventServiceProvider.php
│   └── RouteServiceProvider.php
└── User.php
Enter fullscreen mode Exit fullscreen mode

I first like to move all the core code into a Core\ namespace. This contains all the laravel specific setup code such as the main service provider, kernel, etc.

To do this, create a Core folder in the app/ directory and move everything into it except the User model. Either using an IDE, or by hand, you must now adjust the namespaces of all the classes within these directories. Simply add the Core namespace after Portfolio\ like so:

namespace Portfolio\Core\Providers;
Enter fullscreen mode Exit fullscreen mode

Once namespaces are adjusted, we have to fix a few references.

  • bootstrap/app.php
    Fix the singleton bindings for the HTTP and Console Kernels, as well as the Exception handler.

  • config/app.php
    Fix the references to your application service providers.

  • app/Core/Http/Kernel.php
    Fix the middleware references for the CheckForMaintenaceMode, TrimStrings, TrustProxies, EncryptCookies, VerifyCsrfToken middlewares, as well as the Authenticate and RedirectIfAuthenticated route middlewares. These references can be simplified by simply prepending the middleware class with the Middleware\ namespace e.g. Middleware\CheckForMaintenanceMode::class

  • app/Core/Providers/RouteServiceProvider.php
    Fix the $namespace attribute to allow for multiple controller directories. I prefer to shorten this to just the route namespace, in this case Portfolio

  • app/Core/Console/Kernel.php
    Remove the call to load() (line 38) as commands will be declared in numerous places. I prefer to declare commands explicitly using the $commands attribute, or you can use the newer console route file.

You now have a clean slate to build your first feature module. Your app/ directory structure should now look like this.

    ├── Console
    ├── Exceptions
    ├── Http
    │   ├── Controllers
    │   │   └── Auth
    │   └── Middleware
    └── Providers
Enter fullscreen mode Exit fullscreen mode

The Blog Module

Let’s start the Blog module. Every module requires some of the same components. Controllers (presentation), Jobs (application), Models (domain), and Repositories (infrastructure). I like to loosely follow the default Laravel structure inside each Module like so:

├── Http
│   ├── Controllers
│   │   └── PostController.php
│   ├── Requests
│   │   ├── CreateOrUpdatePost.php
│   │   └── DeletePost.php
│   └── Transformers
│       └── PostTransformer.php
├── Jobs
│   ├── EditPost.php
│   ├── RemovePost.php
│   └── WritePost.php
├── Repositories
│   ├── EloquentPostRepository.php
│   └── PostRepository.php
├── Author.php
├── Comment.php
└── Post.php
Enter fullscreen mode Exit fullscreen mode

The presentation layer lives in Http/ and Console/. This is where you will find your controllers, form requests and transformers (I recommend Fractal).

The bulk of the application layer can be found in in the Jobs directory. The only thing your Jobs should do is delegate to lower layers such as Repositories and Service classes. No business logic should live here.

The repositories directory seems self-explanatory, this is where your repositories live.

Models are in the root of the module. I prefer this structure because it’s where most of the business logic should live, which should be the most visible and readily available part of any module. Any other service classes could live here as well, or you might opt to put them in a Service or Utilities namespace.

While this is a very basic module, I believe it demonstrates the idea of how you can shape a Laravel application with principles from DD. You can freely implement any other Laravel component in the same way, for instance Events or Listeners would become directories in the root of the module, as would Notifications or Mail classes.

If your module starts to become too large you can split it out into submodules, but I recommend doing everything you can to keep your directory tree as flat as possible. Only nest when it is the cleanest solution! Having 5 submodules with 1 model each is probably overkill.

The Project Module

Our “Projects” module will look very similar in structure.

├── Http
│   ├── Controllers
│   │   └── ProjectController.php
│   ├── Requests
│   │   ├── CreateOrUpdateProject.php
│   │   └── DeleteProject.php
│   └── Transformers
│       └── ProjectTransformer.php
├── Jobs
│   └── CreateProject.php
├── Repositories
│   ├── EloquentProjectRepository.php
│   └── ProjectRepository.php
└── Project.php
Enter fullscreen mode Exit fullscreen mode

Extracting Comments into its own module

Let’s imagine that we want to enable comments in our Project module. Currently the comment code lives in the Blog module, so we must refactor it into a shared module.

Shared modules can either live inside the Core namespace or a top-level module of its own. Or you might want to create a Shared module with utility classes, traits, or sub-modules. Any of these are perfectly valid and will depend on a multitude of factors.

Let’s create a top-level Comments module for both the Blog and Project modules to consume.

├── Jobs
│   ├── CreateComment.php
│   ├── RemoveComment.php
│   └── UpdateComment.php
├── CommentController.php
├── Comment.php
├── CommentRepository.php
└── EloquentCommentRepository.php
Enter fullscreen mode Exit fullscreen mode

Notice how the model, controller, and repository live in the root module directory. This is just personal preference. If there is only one type of a component, I will not put it in its own directory as it only makes it harder to get to the file. I only start to split things into their own directories when a directory gets too big to easily grok. It might not be the best architecture pattern but it has worked well for me so far. Do what feels best for you, but the important thing is to remain consistent! Don’t go by feel alone, create rules for organizing files so that other people who work on the project can replicate your organization.


In the future I want to dig deeper into what this architecture pattern looks like at the code-level but I hope I’ve shed some light on how you can organize a growing project using Laravel effectively.

I’m currently reading Domain Driven Design and recommend it to anyone looking to learn more about DDD. I’m by no means an expert on this topic and it is something I’m constantly striving to improve at. If you have any suggestions or want to point out any flaws in anything I’ve demonstrated, please feel free to do so in the comments. Thanks for reading!

The post Scaling Laravel Architecture appeared first on Matt Does Code.

Top comments (0)