The Presentation Layer - Clean Architecture & Domain-Driven Design on PHP
The entry point of our application is not coincidentally the last article of the introduction series about Clean Architecture and DDD on PHP. When developing using Clean Architecture and DDD, the Presentation layer is the last thing we focus on.
We already discussed the main aspects of our application, but I've never really mentioned HTTP yet. This should come as a surprise if you never develop using Clean Architecture and DDD.
When you rely too much on frameworks like Laravel, Express.js, etc and/or is used to MVC architecture, you usually first start with the Controllers and work your way down to the other layers, if there is any other layer at all.
Plug-and-play Presentation
The beauty of Clean Architecture is not only having a plug-and-play infrastructure, but also having a plug-and-play presentation. It really does not matter if your user is going to access the application via HTTP, CLI, telnet or even smoke signals.
Accessing the Presentation directory you will only see a single directory there: Api. The proof of concept code available at GitHub created for this series has only one entry point, therefore so far our application is only a REST API.
It could easily be more than that, but for the sake of simplicity we'll focus on how the Presentation layer was created so that our application becomes accessible as a REST API.
The Presentation layer has 2 fundamental goals: handle I/O and inject infrastructure. Let's dive into these goals, shall we? 🦈
Handling I/O
The Presentation layer has 3 layers:
- Controllers are responsible for handling the inputs;
- Presenters are responsible for handling the outputs;
- Middlewares exist in the middle, they can either help during the input or the output.
Inside the Api directory you will only see Middlewares and Controllers. That's because the Presenters are embedded into the Controllers for the sake of simplicity.
INPUT
The first task when receiving an user input in the Controllers is to transform the received data into Value Objects. Remember, we'll hardly never use native types on our application, everything must be objects.
This is done by the getExecutionParams() method which gets the params sent by the user and tries to create the Value Objects.
The proof of concept is a 2 years old code, currently I'm not longer using try/catch on the Presentation layer at all. You may want to check an article I wrote about Error Handling with Middlware later to understand how I was able to simplify the process a lot.
Middlewares can also play an important role in converting inputs. Take for example the Authorization process of an API. The API key can be converted into a Value Object by an Authorization Middleware so that the format of the key is checked and then an infrastructure implementation can be used to query a database to validate if the API key is or not valid.
OUTPUT
Most of the time, the output of the API is a success message (line 66) or an information that was requested by the user. That information is very likely an Entity or a Value Object, such as seem on the GetContactController.php:
All Entities and Value Objects on the code implements the JsonSerializable interface of PHP so it's convertible into JSON by the json_encode() method. That's why the "Presenter" part of our Controller is basically the json_encode() with the Entity Contact inside it (line 47).
In cases where the Presenter does more than just return an object, for example an endpoint that returns an HTML table converted from an Entity, then it would make sense to have it separated. In CLIs having the Presenter in it's own class makes a lot of sense so you can format the output with colors, columns, etc.
Oh, we can't forget about the role of Middlewares in the output. A ResponseMiddleware can later be used to transform the JSON into XML if the user sends an Accept header requesting a response in JSON. That way your API is able to respond both in JSON and in XML for instance.
You can also use Middlewares to handle the output for exceptional cases, such as the one mentioned on the Error Handling with Middlware article. Seriously, check that article, you won't regret. 😁
Enough about I/Os. Now let's get into the real fun part. Dependency Injection.
Injecting Infrastructure
Dependency Injection sounds cool because it's cool. With Clean Architecture you won't be requiring dependencies directing on your code as you use to. Now, all the infrastructure is regulated by interfaces, just like explained on the very first article of this series.
Since we're following interfaces, we can have multiple implementations for the infrastructure. It's the Controller task to provide an UseCase (Domain Layer) with an infrastructure implementation that guess what, implements an interface the UseCase knows how to use.
It's probably sounding like jibber-jabber for you now, but we can see that on code. Take the GetContactController.php controller again:
You can see that I'm initiating a ContactQueryRepository object (line 34). This is an infrastructure implementation, file-based (although the name does not tell right away - my bad), of the ContactQueryRepositoryInterface:
Once I initiate theContactQueryRepository object, I pass it to the GetContactInteractor UseCase right on the next line. That's it. That's dependency injection.
Why is that great? Well, let's see. Imagine that tomorrow I want to have a database-based implementation of that same interface, to get contacts. However, I only want to use that database-based implementation if the ContactId is greater than 1000. I don't know, maybe I was in a rush and couldn't migrate the files into a database, thus the first 1000 Contacts will be kept in file format.
The only thing I need to do besides creating a DatabaseContactQueryRepository class, is to add an if in my controller so that I pass to the GetContactInteractor UseCase the database implementation of ContactQueryRepositoryInterface instead of the standard file-based one.
I didn't change a single line on the Domain layer. In fact I didn't change anything, I created a new implementation and added an if into my controller. That's plug-and-play infrastructure right there.
The idea of having multiple implementations for the same interface is denominated "Strategy Design Pattern". I highly recommend you read more about Strategy in the Refactoring Guru website. That website is pure gold.
Final Thoughts
Combining Clean Architecture and DDD is a natural process. They share a lot of common principles. Once you get the hang of it, it feels like you have just learned how to program for the first time. At the same that it's liberating, you feel ashamed of what you have been writing so far.
Don't worry about that though. That's what progress feels like. Now your code has exactly the idea of what you mean by a concept that only existed in real life. No more fat controllers and skinny models, now the model (Domain) is at the center of your application, everything just fits right together.
I'm pretty sure the ethical hackers out there will agree with me when I say DDD and Clean Architecture makes an attempt to invade way, way, way harder. It's such a pain that I would rather resort to social engineering if I'm trying to pentest a system designed with DDD and Clean Architecture concepts in mind rather than spending the whole month barking up the wrong tree. 😁
What we usually see when pentesting is that code is as much vulnerable as a team without social engineering training. A chain is only as strong as its weakest link they say. At least with Clean Architecture and DDD you're strengthening one part of your business. It's one last thing to worry about when going to sleep.
« The Domain Layer - Clean Architecture & Domain-Driven Design on PHP
Comments