Domain Driven Design with Laravel 9

  • avatar

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:

Laravel 9 original folders and files structure

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.

Laravel 9 Domain Drive Design: domain layer folders and files

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.

Laravel 9 Domain Drive Design: application layer folders and files

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:

Laravel 9 Domain Drive Design: infrastructure layer folders and files

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.

Laravel 9 Domain Drive Design: user interface layer folders and files

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:

Laravel 9: event service provider

For other type of modules, AppServiceProvider.php is the best binding place:

Laravel 9: app service provider

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.

Domain Driven Design: typed value objects (VOs)

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

 Join Our Monthly Newsletter

Get the latest news and popular articles to your inbox every month

We never send SPAM nor unsolicited emails

8 Comments

avatar

Rodriguez Diakiese Reply

A another question is where to validate my form data before saving in DB

avatar

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.

avatar

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);
}
}

avatar

Reply

I lack the full context, but I would likely merge the use case and service into a single class.

avatar

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 ?

avatar

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.

avatar

Maikson Reply

Great post, is there a sequel?

0
avatar

Reply

Thanks for the feedback! We already work on DDD using Laravel framework sequel. As always it will include practical examples and use cases.

Leave a Reply

Your email address will not be published.

Replying to the message: View original

Hey visitor! Unlock access to featured articles, remove ads and much more - it's free.