Get up to 80 % extra points for free! More info:

Lesson 2 - Monolithic and two-tier architecture

In the previous lesson, Introduction to software architectures, we explained the concept of dependencies in object-oriented programming, and shown why we should write object-oriented code while examining bad code examples. How the application is composed of the objects is determined by its architecture. Today's software design lesson is all about different architectures.

We can imagine programming an application as building a tower. If the architect designed it wrong, it will fall once it reaches a certain height. It must have a solid backbone structure. Adding more code to the application is bread and butter of programmers, so the application design must be of a high-quality to allow it. The term "quality" means, of course, so good division of object responsibilities that even in hundreds of classes we'll always know where to find the code a certain feature and the whole project will be 100% clear. Does it seem impossible? Certainly not. Perhaps you won't be surprised that we'll introduce more design patterns. Let's take it step by step to get a really detailed overview of the different architectures.


Now we'll talk about logical architectures. Don't confuse them with physical architectures that describe the hardware structure, for example, the physical client-server architecture. In this course, we are interested in organizing code into objects.

Monolithic architecture

With this architecture, the whole application works only as a single layer. A layer is a group of objects. Smaller applications are usually written this way. The monolithic architecture is rather an antipattern and we shouldn't use it for larger applications. But, of course, we need to start somewhere :)

Pipes and filters

Just to have the whole picture, let's mention the pipes architecture used by UNIX operating systems. The functionality is divided into a large number of small separate programs that call each other and transfer data. Perhaps you can transfer data between different commands using pipes in Linux. Linux is also a proof that a system can work well when you build it from hundreds of small programs and each of these programs does well just its small part.

Two-tier architecture

The two-tier architecture is often used with API servers or services in general. Sometimes, we can also encounter the Service-Oriented Architecture (SOA) term, which, besides the software architecture, also includes the logical architecture on the server, typically client-server. Two-tier applications typically don't present data to users. They usually communicate with another applications, handle requests, process them and return the resulting raw data.

Now, we get to the promised architectural design patterns. These are:

  • Indirection - We can simplify the references between objects and make the application more clear using middle men. You could say it's a paradox that if we add a purely fabricated layer to the application, which will mediate the communication between 2 object groups, the application will be simpler than without this layer. Sometimes we refer to these classes as service classes. However, we should not confuse it with services in general.
  • Controller - The Controller design pattern talks about Indirection even more precisely and focuses on applications that communicate with the user (and that's the vast majority of applications). It says that all the communication with the user should be concentrated in separate objects intended for these purposes, the controllers. These then form the application's control layer. In some modifications, it can be also called the presentation layer. On the other hand, classes containing logical operations should be completely relieved from the communication with the user. Such business logic classes are then called Models, sometimes they are referred to as entity classes, managers or repositories.

So in a two-tier application, we have 2 object groups - Controllers and Models.

If we programmed an API server which we asked for all the cars in the database and it would send them back in an exchangeable format (not as an HTML page but as data for other machine processing, e.g. in JSON), the source code of the application would look as follows:

We divide the application into 2 folders - Models and Controllers.


The car manager provides the logic for working with cars. That's its responsibility. Similarly, for example, the users logic would be concentrated in the UserManager class and so on. We pass the $database dependency to the object through its constructor.

class CarManager

    private $database;

    public function __construct($database)
        $this->database = $database;

    public function getCars()
        return $this->database->query("SELECT * FROM cars")->fetchAll(PDO::FETCH_ASSOC);

    // Other methods for working with cars, such as adding new cars, searching for cars, removing cars, editing cars ...


Models don't handle the communication with the user at all, they only provide logical operations within their responsibility.


The controller accepts requests from the user. In this case, the user will be the machine calling our API server, but even this counts as a user. The controller calls the appropriate models based on accepted requests, and returns the result of their work. Its purpose is only to mediate the communication between the user and the model/logical layer of the application. Typically, we try to write the controllers as short as possible and they serve only as "wires" between the user and the logic.

class CarController

    private $carManager;
    private $database;

    public function __construct($database)
        $this->database = $database;
        $this->carManager = new CarManager($this->database);

    public function all()

    // Other actions such as one($id), remove($id), ...


The code should be clear. The action which is about to be executed is called in the controller. The controller gets data from the model, which doesn't know about any users or requests at all. It just takes care of data within its responsibility, which is cars' data. On the contrary, the controller doesn't care about the data, it receives them from the model and deals only with which data the user wants and how will it send it back to them. It sends these data to the user using the json_encode() function. In a real application, we'd pass some more parameters and the controller might inherit from an ancestor, but let's not worry about it now.

Because we like to provide complete solutions here on, let's also show how the system would call the appropriate controller internally, based on the request. This is a very simplified example, if you find it interesting, a link to the complete implementation and other information can be found below.


Once index.php is called, the autoloader is registered to search classes in the Controllers/ folder first and then in the Models/ folder. For a smarter implementation, see other PHP courses. If you are programming in a language other than PHP, then your classes are probably loaded automatically and it doesn't matter in what folder/package they are.

Let's consider we've opened:


From this, we need to determine that CarController should be created and the all() method called on it. The code should be at least intuitively comprehensible, I added some comments to it.


// Simple autoloader
// Ignore for other programming languages
spl_autoload_register(function($className) {
    if (file_exists("Controllers/$className.php"))

// Creates a database class instance
$database = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'root', '');
// Creates the appropriate controller according to the URL address
$controllerName = $_GET['controller'] . "Controller";
$controller = new $controllerName($database);
// Calls the appropriate method by the URL address on the controller

To get all the cars from the server, we would then open this address:


And we would get back a JSON as follows:


Of course, actions may also have parameters, and we typically use so-called pretty URLs. So a URL address of a two-tier application could also look like this:

According to this, the ArticleController would be instantiated, and the find() method called on it with the "php" parameter. The part of a web application that calls the appropriate controller by the URL address is called a Router. The example above was very minimalist. A router that could pass method parameters, supported pretty URLs and could redirect to error pages, for example, would be little longer. You can have a look at it in the Simple object-oriented CMS in PHP (MVC). The functional application can be downloaded in the attachment as it's usual here.

In the next lesson, Three-tier architecture and other multi-tier architectures, we'll explain the three-tier architecture, including a functional example again.


Previous article
Introduction to software architectures
All articles in this section
Software Architectures and Dependency Injection
Skip article
(not recommended)
Three-tier architecture and other multi-tier architectures
Article has been written for you by David Capka
User rating:
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.