Domain Driven Design with Laravel 9
Modern web frameworks teach you to take one group of related concepts and split it across multiple places throughout your codebase. Laravel is a robust framework with a big community behind it. Usually it's standard structure is enough for most starting projects.
Building scalable applications, instead, requires a different approach. Have you ever heard from a client to work on controllers or review the models folder? Probably never - they ask you to work on invoicing, clients management or users. These concept groups are called domains.
Let's make a practical exercise applying Domain Driven Design. Our goal is to create a boilerplate to be used universally as base of any Laravel project. Take advantage of the framework power at the same time we meet complex business requirements.
Prerequisites
Understanding of Domain Driven Design and some basic concepts:
We are going to use a fresh Laravel 9 installation for this guide, take a look on how to create your first Laravel project. To run Laravel locally a PHP setup is also required.
You also have the direct option to start the Laravel 9 project with domain driven design. The following command will create a new project in the laravel9-ddd folder and install the required dependencies:
composer create-project hibit-dev/laravel9-ddd
Keep in mind
We must keep in mind some important points planning the architecture of our software:
Clean-code design plays a key role in building highly scalable applications.
Follow unified business language that everyone in the company (not only developers) will understand and that will be used in our business/product development process.
Decoupling the application from the framework can be exhausting and pointless. We want to use the power of the framework having the code as much decoupled as possible.
Carefully choose your third-party services, otherwise, they might cause operational failure.
Architecture layers
There are several ways in which the Laravel framework can be organized to serve as a template for large-scale projects. We will focus on the app (aka src) folder while keeping the framework features almost intact .
Initially, Laravel is structure looks as below:
With modified codebase structure, we are able to follow Domain Driven Design within our Laravel project which will support the future growth of our software. We also will be ready for the upcoming framework upgrades. We want it to be easy to upgrade to the next versions.
In first place, we should create a folder for each DDD layer:
app/Domain
app/Application
app/Infrastructure
app/UserInterface
Domain
Since this layer is where abstractions are made, the design of interfaces are included in the domain layer. It will also contain aggregates, value objects (VOs), data transfer objects (DTOs), domain events, entities, models, etc...
The only exception would be anything related to eloquent models. Eloquent makes very easy to interact with databases, tables and rows but the reality is that it's not a DDD model. It's an ambiguous definition of the concept of model with implementation of database connection. Does it mean that we can not use Eloquent? Yes we can, it can be used as repository implementation (infrastructure layer). We do have a significant advantage with this approach: we are no longer dependent on Laravel's method names and we can use some naming that reflects the language of the domain.
Actually we have nothing in domain layer so we will keep it empty.
Application
Application layer provides the required base to use and manipulate the domain in a user-friendly way. It is where business process flows are handled, commands are executed and reactions to domain events are coded.
Actually we have nothing in application layer so we will keep it empty.
Infrastructure
Infrastructure layer is responsible for communication with external websites, access to services such as database (persistence), messaging systems and email services.
We are going to treat Laravel as a third-party service for our application. So all the framework files are going to be grouped inside the infrastructure folder.
What does it imply:
Create app/Infrastructure/Laravel folder
Remove app/Http/Models/User.php
Move the base controller app/Http/Controllers/Controller.php to app/Infrastructure/Laravel/Controller.php
Move HTTP Kernel app/Http/Kernel.php to app/Infrastructure/Laravel/Kernel/Http.php
Move Console Kernel app/Console/Kernel.php to app/Infrastructure/Laravel/Kernel/Console.php
Move app/Exceptions/Handler.php to app/Infrastructure/Laravel/Exceptions/Handler.php
Update bootstrap/app.php to point the correct Kernels and exception handler
Move app/Providers/* to app/Infrastructure/Laravel/Providers
Update config/app.php to point the correct Providers
Move app/Http/Middleware/* to app/Infrastructure/Laravel/Middleware
Update the HTTPKernel to point the correct middleware.
Note: make sure to update namespaces when moving files.
The final result look as following:
User Interface (UI)
User interface layer is the part where interaction with external world happens. The responsible of displaying information to the user and accepting new data. It could be implemented for web, console or any presentation technology.
Actually we have nothing in user interface layer so we will keep it empty.
Binding interfaces
One last thing that our architecture is lacking: the connection of concrete implementations with interfaces within our domain, e.g. repository interfaces.
For each module on the domain layer, we need a matching module in the infrastructure layer which takes responsibility for what the domain layer cannot afford to care about.
We recommend to use EventServiceProvider.php to bind domain events, commands and queries:
For other type of modules, AppServiceProvider.php is the best binding place:
The definition of the abstract interface and the concrete implementation in the service provider serves as class wiring configuration.
Bonus
As a small bonus, we've included shared domain VOs for basic types.
That classes provide an abstraction and shared methods for the final VO definition. An example of usage:
<?php namespace App\\Domain\\PostValueObject;
use App\\Domain\\Shared\\ValueObject\\StringValueObject;
class Message extends StringValueObject
{
}
Note: constructor, getters and additional shared methods can be included in the parent StringValueObject.
Conclusion
Note that so far nothing has changed in the way we use Laravel. We still have our Kernels, Providers, Exception Handlers, Rules, Mails and more inside the app folder.
Implementing Domain-Driven Design is always going to be a challenge no matter what framework we use, there is no unique way of defining things. Almost everything depends on the specific project you're working on and it probably makes sense to apply a different structure or architecture in other cases.
Domain Drive Design is a continuous process that must be carried out according to specific needs that can be adapted over time. Also it's a trade off: investing time on having a perfect structure or creating a starting base and improving with the time.
Next steps
If you have an interest to explore further details about Domain Drive Design, we have an article available that includes a practical example. The article focuses on the user domain and utilizes the Laravel 9 structure created in this article:
We would be happy to receive your feedback and thoughts on it.
Credits
Official GitHub: https://github.com/hibit-dev/laravel9-ddd
8 Comments
Rodriguez Diakiese Reply
A another question is where to validate my form data before saving in DB
HiBit Reply
The choice of where to place the validation for incoming data depends on your preferred approach. You can opt to use Laravel form requests, which should be integrated as a framework dependency within the Infrastructure layer. Alternatively, you have the option to use Value Objects (VOs) for direct validation.
Rodriguez Diakiese Reply
hi Hibit
This is what a did, your coments.
My Controller
public function index(): JsonResponse
{
$supplier = $this->supplierListUseCase->execute(10);
return response()->json($supplier);
}
useCase
class GetSupplierListUseCase
{
public function __construct(protected GetSupplierListService $supplierListService)
{
}
public function execute(int $page): ?LengthAwarePaginator
{
return $this->supplierListService->execute($page);
}
}
Service
class GetSupplierListService
{
public function __construct(protected SupplierRepository $supplierRepository)
{
}
public function execute(int $page): ?LengthAwarePaginator
{
return $this->supplierRepository->getAll($page);
}
}
HiBit Reply
I lack the full context, but I would likely merge the use case and service into a single class.
Rodriguez Diakiese Reply
Thanks for your post, but i have questions about
1. Possible to add Providers, UseCases and Services in Application layer ?
2. Using this approach how can i upgrade my Laravel version to 10 ?
HiBit Reply
Appreciate your feedback!
Let's explore those questions further:
1. Adding use cases and services within the application layer is advisable. However, service providers are closely tied to Laravel framework, which is why they should be placed in the infrastructure layer.
2. Usually, the Laravel upgrade guide offers comprehensive instructions on which files to modify or the specific sections within a file that need changes. When performing the upgrade, your primary focus should be on following the guide and ensuring that you update the namespaces and file locations accordingly.
Maikson Reply
Great post, is there a sequel?
HiBit Reply
Thanks for the feedback! We already work on DDD using Laravel framework sequel. As always it will include practical examples and use cases.