How to bring DDD to Laravel. An introduction to Ubiquitous Language.

Luis Gómez
Applaudo Tech Blog
Published in
7 min readJul 21, 2022

A few days ago, I was browsing Twitter and saw a lot of blog posts about DDD in Laravel. And they do a great job to introduce architectural concepts and why they are useful, and more over, how to make your code cleaner and more readable. So to add to that conversation, we’ll address one key component of DDD that is yet to be explored: Ubiquitous Language, this concept is very abstract so we'll break it down and build up to it.

What is DDD?

In plain English, Domain Driven Design is a technique to develop software that focuses on expressing your business language on your code.

When we talk about the patterns that define DDD, we start thinking about entities, services, repositories, and all of the complexity that often comes with a Layered Architecture. More often than not, such complexity is an overkill for the average Data Driven Laravel Application, to the point that it prevents you from leveraging the benefits that led you to use Laravel in the first place.

Tactical Design vs Strategic Design.

It is important to note that DDD has two sides. The first one: Tactical Design, includes all of the architectural aspects that we mentioned before, and its goal is to provide structure to the second side of DDD, the often overlooked: Strategic Design; which describes the patterns that give predominant importance to your business language on your code.

For now, let’s ignore the patterns described on the Tactical Design, and let’s focus on the Strategic Design. Let's look at an example.

Food delivery app, a practical example

Suppose that you were hired to work for a food delivery company, and your job is to write software to maintain their APIs. Their whole business is their platform that lets customers place orders to have them delivered to their door. Similar to any other food delivery app in the market.

You went to a bunch of meetings, where they talked about how things work, and also the product owner explained their business model, and so on. And after those meetings you have a clear idea about their business rules, but we'll focus on one for now:

A customer goes into the platform and sees our active partners with nearby restaurants (10km radius).

As your first task you’ll work on the endpoint used to retrieve the partners with nearby restaurants. You are asked to add flexibility to it by accepting a new parameter which will let customers change the 10km radius, with a maximum value of 15 km.

After some digging around you find that the code you’re looking for is on `PartnerController`, on the index method, and it looks like this:

It looks like a simple enough task, you can solve it pretty easily by adding a couple of lines:

Although this was pretty simple, you see that the business language that describes this use case is hardly reflected on our code. It can be inferred, but it takes quite a lot of reading, as you have to switch between “levels”. Let’s break down what this means:

  • On the first few lines you’re getting the customer reference location, and you have to switch to a lower level when validating.
  • Later on, you’re retrieving the applicable partners, but you have to switch from that high level to a lower level when querying the database.
  • Then, you’re passing those partners to an Eloquent resource.

So you decide to do the right thing, and clean up the code a bit, and looking ahead to you having to maintain this, you decide it is a good idea to start introducing DDD.

As you go through the iterative process of finding ways to better express your business language on your code, these are the things you do:

Use scopes to build your query.

First we add our scopes to the model:

Then update your controller:

Use a service class

Using query scopes surely reads better, but you’re still switching between levels, and the high level is not very well expressed either. Now, you take it further by extracting that query to a PartnerLocator service class, and inject it to your controller.

For your PartnerLocator class, it might look something like this:

Use DTOs (Data transfer objects)

Using a service class made your code read great! Still, you’re left scratching your head about this `lat`, `lng` parameters, what are they?, and what is that nearby word in reference to?.

You debate on what you should call this, you know it is your customer reference location, but that name feels awfully long. So you go to your product owner, and you start discussing what this should be called.

At first he thinks you’re over engineering, but you convince him, explaining that this is how you’ll talk about this entity from here on out, and should we need to do anything else with that customer reference location, it will be easy to locate it.

Finally, you decide to call it $customerLocation, and you introduce a DTO for this purpose, but you just call that class Location, because that might be re-used at some point in time.

You create the Location class under the Locator namespace, because you plan to place anything that operates with Locating stuff there.

Avoid Magic Numbers and Magic Strings

With that new DTO, you need to update your PartnerLocator class, and while you’re at it, you also address those nasty magic number (10).

Now, your new PartnerLocator class looks like this:

Let's not forget about our controller:

Great, now your code is clean, and you can finally add those few lines of code that lets your customers change the maximum distance.

First you update your PartnerLocator service:

And your controller again:

Avoid generic controller names, and use a FormRequest

Now, the only final thing that really wraps this up, is to rename that controller, to reflect what it actually does, and move it to a Customer namespace.

You can also abstract the validation to a FormRequest, and define some methods to pull information from the request.

Now you're final edit for your controller:

And you’re done! Things work and your tests continue to pass (Tests omitted to avoid the post becoming too lengthy).

Conclusion

This example is good to illustrate, what is known as Ubiquitous Language, which is defined in DDD as the practice of building up a common, rigorous language between developers and users. We’ve been doing baby steps today, but great work so far!. We even got more people involved. And, it is important to note that Ubiquitous Language is not something you arbitrarily define, DDD calls for this to be something that is agreed upon.

That was easy! Where to next?

Remember how we added a new namespace called Locator and another called Customer? That is a very small example about what a Domain actually is, a bounded context, and what this references is that things might not mean the same on different contexts. If we take an order for example, it means a different thing for our customer, and for our drivers, drivers see orders more as a delivery, they don’t actually care about the contents of an order, not further than the fact that it is complete, they care about delivering it. And that what Bounded Context is all about. But we’ll leave that for another post.

--

--