Lesson 3 - Three-tier architecture and other multi-tier architectures
In the previous lesson, Monolithic and two-tier architecture, we explained what the logical architecture is and why it's important for our applications to have some architecture. We mentioned the monolithic architecture and then divided it into two layers, creating a two-tier architecture. To experience the principle of controllers and models, we have created a fully functional two-tier application in PHP that provides the API to get data of cars.
Three-tier architecture
We're getting back to the topic which is asked in most job interviews. You'll definitely hear the question "What is the three-tier architecture?" Since we've already explained the two-layer architecture, there's not much more ahead. The third layer shows the result to the user, which we could omit in the two-tier application since the user was just a machine working with raw JSON.
The application layers will be:
- Models - Logic, typically SQL queries
- Views - Templates, typically HTML files
- Controllers - Middle men with whom the user communicates and whom communicate with the models and views. It's the middle men what allows the full separation of logic and presentation. This is what we try to do in every application. If you should remember something, then it's that architectures usually separate logic and presentation.
Note that the initial letters of the layers form "MVC" together, which is the name of the most famous three-tier architecture. Other three-tier architectures are:
- Model-View-ViewModel (MVVM) - The MVVM architecture comes up with another layer. We use minimalist models in templates, so-called ViewModels, to make passing data between the template and the controller, and vice versa, easier. In practice, we use bindings, which is a very sophisticated tool. It allows us to bind a ViewModel to a template so that any changes, e.g. of values in a template form, immediately results in a change of the corresponding ViewModel properties and vice versa. We are able to read and write template data just by creating a ViewModel.
- Model-View-Presenter (MVP) - The MVP architecture is promoted by some popular frameworks. It works basically just like MVC, sometimes it's understood as an MVC implementation. We can find various MVC and MVP implementations in different applications and some things can be done in a slightly different way. There are small differences between these two patterns. In MVP, we're often able to access presenters from a view. Sometimes it's even the view which takes over the control of the application itself and creates a presenter that retrieves data from the logic. But it doesn't always have to work like that. If you are lost in MVC and MVP differences, it's unfortunately normal because their specification is confusing. I recommend following the previously mentioned MVC.
Just to be sure, let's describe what the layers will do once again:
The life cycle of a request to a three-layer MVC application will be as follows:
- The router calls the appropriate controller by the URL
- The controller asks a model for data
- The controller stores the data
- The data are passed to a View
- The View fills an HTML template with the data
- The complete page is sent to the user
Let's change our two-tier application to a three-layer application so it displays results as HTML pages.
Models/CarManager.php
The model will stay the same.
Controllers/CarController.php
Instead of using echo() and `json_encode()
to print the data,
the controller prepares variables for the template, and then loads the
template:
class CarController { private $carManager; private $database; public function __construct($database) { $this->database = $database; $this->carController = new CarController($this->database); } public function all() { $cars = $this->carManager->getCars(); // A template variable require('Templates/cars.phtml'); // Loads the template } // Other actions like one($id), remove($id), ... }
Templates/cars.phtml
The new application layer is now a view, a template. That's an HTML code with
a small addition of some other language's syntax used to iterate over the data
and to insert variables. We use a loop to iterate the $data
variable retrieved from the controller.
<html lang="en"> <head> <title>Car list</title> <meta charset="UTF-8"> </head> <body> <table border="1"> <?php foreach ($cars as $car) : ?> <tr> <td><?= htmlspecialchars($car['license_plate']) ?></td> <td><?= htmlspecialchars($car['color']) ?></td> </tr> <?php endforeach ?> </table> </body> </html>
The completed template is then sent to the user. It looks like this:
So, we improved our sample application a lot. Although we've passed the dependencies manually so far, the app is now separated as follows:
- Models - Files with pure application/business logic
- Views - Files with relatively clean templates
- Controllers - Relatively small middle men
You can compare the current progress with the example when everything was in a single file. However, it's also necessary to imagine that each application part is complex enough to show the value added by separating these parts.
Form applications
And what about form applications? If the application is not a web-based but a desktop application, of course, there is some similar automatic mechanism above our code calling the right class representing a form or whatever the user is communicating with. The Controller pattern can really be applied everywhere, and we should definitely do that. You've already created a form application for sure, the class handling the form controls was a controller. For example, in JavaFX, classes are even named controllers, but, for example in C# .NET, they are called CodeBehind, even though they are controllers. Be honest, have you ever written your logic directly to this file handling the form? So now you know that you should have encapsulated logic into separated models. Just like we've shown it on the cars web app.
In the next lesson, Wrong ways of passing dependencies - Static members, we'll finally talk about passing dependencies. We'll show what happens if we don't pass dependencies at all but create new service instances every time. We also show antipatterns as a passing by static attributes, Singleton and ServiceLocator design patterns, and different variations.
If you are having problems with MVC or you are interested in it, take a look at our separate PHP course on this architecture - Simple object-oriented CMS in PHP (MVC).