Martin Joo

« back published by @mmartin_joo on March 29, 2022

Domain-Driven Design with Laravel: Domains and Applications

What Is Domain-Driven Design or DDD?

Domain-Driven Design is a software development approach that tries to bring the business language and the source code as close as possible.

I think that's the most simple yet correct definition of DDD. To be a little bit more specific it achieves this by making every important "stuff" a first-class citizen. If "Change invoice status" or "Increase order item quantity" are two sentences that your business people say a lot then you probably should have

  • ChangeInvoiceStatus
  • IncreaseOrderItemQuantity

classes somewhere in your application. That's one example of being a first-class citizen.

Now let's say you're working on some server management / monitoring application. Maybe a commercial one, or some internal tool for your own projects. What is the single most important "stuff" in this application? In my opinion, it's the server status. If it says "Healthy" everyone is happy, but if it says "Down" you know you're about to have a hard day. We know it's important but still, it's just a string in our database table. It's the 15th attribute on our Server model. And we're changing it from "Healthy" to "Deploying" in the 873rd line of some random Command or Job, or in a Model somewhere, or even worse in a Controller. Instead of having these string values and changing them, we can have classes like:

  • ServerStatus/Healthy
  • ServerStatus/Down
  • ServerStatus/Deploying

We can also have transitions like:

  • HealthyToDeploying

So this is what I mean by making things, first-class citizens. This is what Domain-Driven Design is all about (and a little bit more, but now this is the important thing).

What Problems Do Domains Solve?

The name Domain-Driven Design implies that a domain is something that drives the development process. So it must be important, right? Put simply, a domain is a “module” or a “container” that contains code that belongs together. But the main criteria of this grouping is not technical at all. It’s all about real-world problems and business language.

Laravel is a great framework and a good starting point for beginners. By default it structures your code based on technical details. I’m talking about this:

Domain-Driven Laravel Concepts - Domains and Applications - Default Laravel Structure

This is a very small application, but you can see the code that handles Departments is spread across six different places. It’s grouped by technical attributes:

  • Controllers
  • Models
  • Requests
  • Resources
  • Actions
  • DTOs

Now imagine a project that has 200 models, 300 actions, 500 controllers, and 500+ routes. After a while, it becomes a nightmare. If you need to introduce a new attribute to the department model, and you accept it in your API, you probably have to change 5 files in 6 different directories.

By the way, have you ever heard a stakeholder saying something like: “Please work a little bit on the DepartmentController and add a new attribute to the DepartmentResource”? Of course not. They say things like: “On the department page I need to see the number of employees within the department”. And from that information, you know that you probably need to tweak the DepartmentController and the DepartmentResource.

A developer is someone who translates the business language into code.

So in my opinion is a good idea if the code is similar to the business language and follows the same structure.

Domains for the Rescue

DDD provides a solution called Domains. They are similar to “modules”, so they allow us to do something like this:

Domain-Driven Laravel Concepts - Domains and Applications - Domains

As you can see all I did was create a folder called Domains. In this directory I have a directory for my domains or let’s say modules. The above example comes from an E-Commerce project so it has domains like:

  • Orders
  • Products
  • Invoices
  • Customers

So the first level of the domain directory is business-related. In each of the domains there are folders for the technical stuff like:

  • Models
  • Services
  • Actions
  • Events
  • And so on

This comes with a number of advantages:

  • Easier to navigate. Everything order-related lives in the Order directory.
  • Easier to onboard new developers. After all, we work in teams with other developers. In my experience, it’s much easier to onboard new devs if I can say: “if you need something related to invoices you can go to the Domains/Invoices directory”.
  • It gives you some boundaries. By the way, in the original DDD concepts, this is called bounded context. It’s a great idea but has an over-engineered name and explanation.

At the end of the day: a domain is a folder for your stuff grouped by real-life meaning.

In most cases, I put my domains into src/Domains. All you have to do is autoload this directory in your composer.json:

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

And you can access the Order model: Domains\Orders\Models\Order

The Application

Where are the controller?

So domains are independent of the environment or application. For example, the actions in the Domains/Orders folder can be used from:

  • Api (used by the frontend)
  • Admin (used by the admin)
  • Console command (mainly used by the scheduler)
  • Or any other applications

These are the applications that use our domains.

Most of the time we’re working with HTTP applications (after all, we’re web developers), so these applications have every HTTP-related component, such as:

  • Controllers
  • Middlewares
  • Requests
  • Resources

So this is your regular app directory in Laravel, but you can (and should) namespace it by domains, for example:

Domain-Driven Laravel Concepts - Domains and Applications - Applications

So in this example I have 3 applications:

  • API
  • Web (Inertiajs)
  • Console

Api and Web both contains folders such as:

  • Controllers
  • Requests

Inside Controllers I have namespaces based on domains, such as:

  • Mail
  • Subscriber
  • Automation

In the Console application, I also have these namespaces for the commands.

It’s important to note: this structure doesn't mess with the structure of the app directory. So there's no need to change:

  • How Laravel bootstraps your app
  • How it loads application service providers
  • How composer maps to the App namespace

Everything remains the same as before and you can upgrade Laravel without any extra work.

Conclusion

Domains and applications are a great way to structure your code in a nice, modular way. The benefits are:

  • Applications are separated which is really useful.
  • Domains are self-contained meaning everything you can do with an order live inside the Domains/Orders directory.
  • In a large application it’s a huge win in my opinion.
  • Easier to work in teams.
  • Better responsibility. Imagine you’re working on Invoices for two weeks. All you have to do is dig into two directories (Invoices domain and the corresponding application) and you have everything you need**.**
Domain-Driven Design with Laravel