The Domain Layer - Clean Architecture & Domain-Driven Design on PHP
The fundamental part of Clean Architecture and Domain-Driven Design is the Domain layer. There all the business and applications rules are laid down and will be controlling the processing of information independently from the infrastructure and frameworks.
In the last post we discussed a little about the Domain Layer but didn’t really get into the details. The proof of concept code available at GitHub created for this series of articles about Clean Architecture and DDD on PHP will be enlightening the whole path for us.
Accessing the Domain directory we can see 4 sub-directories: Entity, Repository, UseCase and ValueObject. Right there we can see concepts that overlap between Clean Architecture and DDD such as the Entity and Repository, followed by UseCase, a Clean Architecture concept and finally ValueObject, a DDD concept.
UseCases: The heart of the application
One of the hard truths about programming is that you will always be struggling to find the right balance between decoupling your code and having a maze in your hands. By using enterprise concepts such as Clean Architecture and DDD, one could say the application code is already flirting with over-engineering when compared to typical patterns such as MVC.
That being said, Domain Services and Application Services - Domain-Driven Design concepts designed to separate the business rules from the application rules - won’t be found on the proof of concept code. Clean Architecture gives us a certain degree of liberty to use UseCases to write every action the application is supposed to perform, as long as you maintain the code very readable, avoid god-like methods and use Entities to define the critical business rules, but we will be approaching that later.
A great idea to keep things more organized is to separate command and queries. For instance, instead of having a Contact class that encapsulates the getContacts with updateContact methods - very different types of code by operation nature - we can have one UseCase for each action as well as different repositories.
In CRUD-like (create, read, update and delete) applications, it’s not unusual that your UseCases will be seen to be only sort of proxying the data received from the controllers and sending to the infrastructure or factoring objects. On the proof of concept code, the only method that isn’t simply sending the information received towards the infrastructure is the GetContacts:
The infrastructure is used to return an array of the requested data and then each field is added to its respective object family (ValueObject) and the Entity is created. In case of a malfunction, the try-catch loop will be responsible for skipping the array element/iteration.
It would actually be better if the infrastructure was responsible for creating the Contact entities with the help of a ContactFactory UseCase for example so we can set extra rules for the creation of Contact Entity if necessary, but the example was written this way intentionally to present the idea that there are a lot of ways to handle the information and you will likely be evolving the process over the time as the complexity grows.
When you’re working on applications that require using two or more interface adapters, the UseCase won’t be just handling the data flow but also manipulating the data in more complex steps, so it becomes necessary that you handle the data with proper factories and other design patterns.
A good example of UseCase doing a little more than proxying to the infrastructure is the InvoicingService from mrkrstphr/cleanphp-example written as a case study for the book The Clean Architecture in PHP - good reading material by the way:
There we can see the class receiving the infrastructure implementation and a factory so the invoices received are sent to the factory by the UseCase.
Repositories: The big secret
Speaking on Repositories, also named Interface Adapters, they reveal the big secret behind the replaceable/plugin-in infrastructure mantra. Take a look at the ContactCommandRepositoryInterface:
The code clearly states the methods the infrastructure must implement as well as the parameters it will be receiving and what it’s expected to return. The Domain layer does not care if the infrastructure will be using a database, file-system or external API to execute such actions, it only cares that the methods have those names, accept those parameters and return those kinds of data.
Since the code is a proof of concept, I didn’t bother with the return of the command actions, that’s why the return is set to void. However, in a production code it is important to return if the action ran as expected so the application/business rules can control the flow and repeat the process or report an specific error without relying on the infrastructure to do so.
I chose to throw exceptions on the infrastructure if something went wrong and capture those exceptions on my UseCases so I can report to the user or perform some other action, here’s an example:
Exceptions can get a little out of hand on larger codebases so I wouldn’t recommend such approach, you would probably be better suited with Boolean returns or throwing custom exceptions that are present on the Domain Layer so you can know exactly how the process went wrong straight from the UseCases.
We usually use the name “repositories” for persistent infrastructure, but you could also create an interface adapter for a phone number validator library for example with a method isPhoneNumberValid(string $phoneNumber): boolean and inject that on the PhoneNumber ValueObject instead of relying on native PHP methods to validate phone numbers, but for now we won’t be exploring this yet.
Entity & ValueObjects: secure by design
As a security engineer, Entities and ValueObjects are the concepts I respect the most. While it’s okay to validate if a phone number, URL or any kind of payload received is valid in the components in a small codebase, this is a nightmare on big applications.
It’s common to have two or more different validations spread across an application and sometimes one after another since each component of the application performs the validation again. Delaying the application and making the code harder to read aren't the only problems, but doing such a routine of validations is a potential source of bugs as while one validation passes the second one may not and finding that will require debug. The goal of the good programmer has to be to try to avoid the need to debug as you only debug what went wrong already.
ValueObjects and Entities come to the rescue, with their immutability nature and clear validation rules, the data can be transmitted over to the components without hassle.
Take the PhoneNumber ValueObject as an example. We can see clearly what defines a valid phone number for our application and if necessary we could have added getters suchs as getAreaCode or getExtensionNumber so we could get specific parts of the data without re-processing it in the middle of UseCases or in the infrastructure.
I mentioned ”immutability nature”, but that’s debatable. You may see setter methods on Entities and ValueObjects, but I personally rather re-create an Entity and ValueObject instead of allowing them to be changed with setters. Another point that is debatable is the use of UseCases to perform actions that according to Clean Architecture should be inside Entities.
To explain that, first I need to mention the difference between Entity and ValueObject. As the article is getting bigger and bigger, let’s simplify: Entity is data that has an unique identification while ValueObjects are more like characteristics. A person for example is always different from another even if they are twins or have the same name. They have an unique identity and therefore would be considered an Entity. Their hair color on the other hand, could be considered ValueObjects because two objects “HairColor with ‘blonde’ value” can be considered to be identical, but two Michael Fox persons can’t.
On the proof of concept code, the Contact Entity contains a couple ValueObjects as properties and a few getter methods:
The validation happens when you require that to construct the Entity you must provide specific types of objects, a.k.a ValueObjects, which will only be possible to create if you already provided the correct data format. Therefore this kind of Entity concept is a simple data model. That’s why it’s debatable, there are programmers that believe an Entity should not only be a data model but also specify if it’s okay for a Loan Entity interest rate for example to be always greater than 5% per month, while I would put that logic in an LoanFactory UseCase because over the time a lot of new rules will be created around the Loan Entity and it would become a giant class.
To sum up, ValueObjects and Entities are data you can trust anywhere in your app. It’s quite common that you implement the JsonSerialize interface because when you answer the request in a JSON format, most frameworks will use the jsonSerialize() method to automatically convert the object nature of the data into primitive data types such as string, arrays and objects. On the Contact Entity of the proof of concept code it returns a nice looking array instead of a ugly object without “labels”.
Conclusion
In this article you understood the idea behind the Domain layer but you may be wondering how the infrastructure and controllers connect to the Domain layer. In the next article we’ll discuss the idea of dependency injection and controllers to hopefully solve this mystery. Stay tuned!
« Introduction to Clean Architecture & Domain-Driven Design on PHP
The Presentation Layer - Clean Architecture & Domain-Driven Design on PHP »
Comments