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

Lesson 1 - Introduction to software architectures

There's a big fuss over the dependency injection. This design pattern is used by larger frameworks to pass dependencies within the application. Understanding DI is not quite trivial, it took me quite a while to start using it at least intuitively and I understood the pattern completely not before I implemented it myself in my framework. In this software design course, I'll guide you through different ways of passing dependencies within the application, we'll attempt to use various mechanisms to pass dependencies step by step and come to a conclusion that DI is the only right option. We'll, of course, show examples and describe them in detail. This course puts the DI into the context of other bad dependency management practices such as Singletons or ServiceLocators and explains their disadvantages. This is why the course is different from other separate articles here on the network, where different ways of passing dependencies are described separately, but the context disappears, making it very difficult to understand a fundamentally simple principle. At the end of the course, we'll even show a minimalist implementation of our own DI container in just 50 lines. This course assumes you know the fundamentals of the object-oriented programming.

Dependency Injection (DI)

Although DI is used mainly in web frameworks, it's not limited to web applications. The problem of sharing dependencies between objects exists in every object-oriented application even though it has no more than 10 classes. Without DI, design problems escalate with the increasing number of classes. As an example, I chose a simple web application in PHP, but the DI is, of course, used the same way in Java (beans) or in C# .NET or, eventually, in other languages. You'll certainly find this knowledge useful during job interviews, I can confirm that they ask about it almost everywhere. It's a kind of knowledge advanced programmers have.

What is a dependency?

Information systems usually consist of a large amount of code. For the comparison, the ICT.social system has several hundreds of thousands of lines of code. In order to understand such code, we divide information systems into objects. ICT.social is composed of hundreds of objects that are gathered in namespaces (packages). According to the SRP (Single Responsibility Principle), each object should be responsible only for one area. The resulting application is then composed of a larger number of smaller objects that communicate with each other. By this division, we'll get readable and reusable components. We can compose other applications from existing universal objects. In monolithic applications written in a single file, it's often a problem to use any part anywhere else and to even read the code at all.

SRP is related to other principles:

  • High Cohesion - The responsibility for a certain area of the application (e.g. user management) should be concentrated to one place, in the minimum number of classes (e.g. UserManager should be responsible for users).
  • Low Coupling - The responsibility should be assigned to objects so that they have to communicate with as few other objects as possible. Since objects focus only on a small part of the application, they logically need to use the features of other objects from time to time. E.g. the UserManager will not normally communicate with a car manager, it shouldn't have any reason to do so. There are few other principles how to divide responsibility, but they are not the subject of this course.

It happens in every object-oriented application that an object needs to communicate with another object. That's how dependencies are created. Of course, we don't want objects to be created over and over again, but to pass just one single instance of each dependency (sometimes referred to as services) to where it's needed. For example, we'd create an instance of the Db database class once and then pass it to all the objects that need it to communicate with the database, such as to the UserManager class. We'd certainly think of many similar services needed by other objects, besides the database, these are e.g. email senders, loggers, the currently logged user, and so on.

Dependency is when an object needs to access other objects. Sometimes we talk about composing objects, when one object uses several other objects to function.

How it all began

In order to understand all the DI benefits, let's take it from the beginning. I've already mentioned that we'll write examples in the PHP language. It has a standard C-like syntax, so it should be readable for the vast majority of programmers. I think it's also great for examples, because you can very easily project the principles into your programming language.

Unstructured code

In early applications, people used to connect to the database at the beginning of a file, executed an SQL query, rendered some HTML, printed data using the programming language, and then rendered the rest of the page. Such an application looked like this:

<?php
$database = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8mb4', 'name', 'password');
$cars = $database->query("SELECT * FROM cars")->fetchAll();
?>
<table>
<?php foreach ($cars as $car) : ?>
    <tr>
        <td><?= htmlspecialchars($car['licenseplate']) ?></td>
        <td><?= htmlspecialchars($car['color']) ?></td>
    </tr>
<?php endforeach ?>
</table>

We can see that SQL is mixed with PHP (the PDO database library) and with HTML code. All this in a single file. In this example, the chosen strategy seems to be correct. However, when there are tens of thousands of lines in a single file and we'll try to debug an SQL query mixed with a code for button styling, we'll surely find out that this is not the way we can create a real commercial application. Even ICT.social worked this way in the past, but it's been quite a long time ago. We were in a high school and we had no experience with software architecture yet.

Even though we can split the application into multiple files, we'll still never have complete control over it. All the files will have access to all data from the other files. This creates global couplings and we'll start to overwrite our data and identifiers uncontrollably. One can just choose the same function name or variable name as was used in some another file. And it's very easy to do so, trust me :) Fortunately, in modern languages, a similar non-object-oriented code can't be even written anymore.

We wouldn't solve the problem by splitting the code into multiple functions either. Functions, unlike object methods, have no context. Even if we create multiple files, each with several functions, we'll not avoid function name collisions. PHP solved the problem with the huge number of its internal functions by giving them all really ugly and long names that are mostly even not standardized. For example, most PHP array functions start with array_, but we get the length of an array by the count() function. When functions don't belong to objects, we'll get lost in their names after a while and. We'll have hard times sharing data between them, which will result either in using dangerous global variables or in passing too many parameters.

Let's show one more bad example from the Wordpress content management system, probably one of the both worst-designed and popular applications in PHP. See what happens if you write an application with completely no architecture:

Wordpress Functions cheat sheet - Software Architectures and Dependency Injection

Only a small demonstration of global functions shows that some start with "the", probably colliding with others without "the", some with "wp_", some with "get_". Urgh. Hopefully, I have convinced you that functions need to be bound to objects to avoid name collisions. Multiple objects can then easily have a function with the same name, it can be easily determined which function to call according to the object and our IDE also offers function lists automatically when typing their names. We don't have to remember them anymore and don't need silly cheat sheets like the one in the picture above. It's a shame that some people are not programming this way yet. Let's shake it off and leave the non-object-oriented world.

The example listing cars will follow us through the entire course and we'll demonstrate all the possible ways of passing dependencies on it until we get to the Inversion of Control and the Dependency Injection, which is one of the IoC techniques.

Object-oriented code

The object-oriented programming solves the problems of "having it all in one file" or of "lots of strangely named functions with many parameters" (and brings other benefits like inheritance, etc.). But we already know that we have to thing about how to divide the application into objects and mostly, how they will communicate with their dependencies. I suppose that you know the basics of OOP, if not, please complete the OOP course for your programming language first, see the navigation menu.

We'll continue in the next lesson, Monolithic and two-tier architecture, where we'll introduce different object architectures.


 

All articles in this section
Software Architectures and Dependency Injection
Skip article
(not recommended)
Monolithic and two-tier architecture
Article has been written for you by David Capka Hartinger
Avatar
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 ICT.social. 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.
Activities