In the previous part of the series, we were looking at machine-level architectures,
Alright, now that we are done with the machines (in reality, we are super-not-done with them, I just realized that I am way down the rabbit hole and need to focus on the main goal of the article), we can finally focus on what most software developers mean by software architecture. As we have seen in the overview, this is mostly about how people organize massive amounts of code so that new developers can understand the system faster. It also develops a language that software developers can use when they are talking about certain topics and the way that they want to organize their code.
Let’s take into account the following situation: when you are recruiting a candidate, there are certain words and systems that every recruiter understands. A selling call is going to be a selling call everywhere, albeit it differs in content. A hiring process is referred to as a hiring process everywhere. You have an established set of tools and lingo to deal with hiring people and this is what software developers want to achieve when they are talking about code architecture as well. We would like to use a language that we all understand.
When we are working with small pieces of software, we can use tools like algorithms and data structures to organize our thoughts and our software. However, as the codebase grows, reasoning about individual algorithms and data structures becomes more and more difficult to the point that the entire system is impossible to reason about and the codebase becomes something that resembles a dish that Italian grandmothers would be proud of. Because maintaining a lot of code is hard, clever software engineers came up with the idea of something that we call design patterns. In the next part, we will dive deep (don’t worry, in reality we are only going to dip our pinkies) into these.
The pattern language movement first occurred in the mind of an architect, Christopher Alexander in the late 1970s, to be picked up a decade later by Kent Beck and Ward Cunningham to be adapted to software. The design pattern movement has not been picked up for yet another decade, until the release of the book Design Patterns: Elements of Reusable Object-Oriented Software in 1994, which caused a boom in software development and the scale of the code that was to be delivered with these paradigms. The book describes 23 traditional software design patterns that are used til today to deliver scalable code that every software developer should learn to understand.
This is all great, but what do you mean by design patterns exactly again? The question is good. Let’s take the following metaphor: Hundreds of years ago, books were written by hand by specialized scholars who gave their knowledge to a handful of people over the generations. Every book looked different, the contents were different. The books read mostly the same, but the technologies they were produced was unique and slow. Then came Johannes Gutenberg in the 15th century and created a method to mass-produce books, using patterns. Later, it did not matter if the print was printing a newspaper or a book, the technologies were the same. The idea behind software patterns is similar (but probably not as revolutionary) as Gutenberg’s of using patterns to mass produce content.
An important thing to mention here is that most software design patterns were build around object-oriented programming. We are not going to dive into what this means right now. What might be useful to understand the following paragraphs is to imagine the following: when we are storing items in memory or doing computations, according to the object-oriented paradigm, we are thinking about objects as the core entity of our software. Objects can be many things, they can be quite primitive, like a string or an integer, but they can also be very complex and abstract, like a car or a potato.
Design patterns in software can be split up into 4 big categories, we will take a look at some examples from each of them.
The way of producing objects in our system. Creational patterns can be quite useful to normalize how objects are created across our systems. There might be certain rules that we need to enforce on them or it’s simply a gigantic mess and we need to normalize how the system works. Here are some basic patterns that you might want to understand to have a casual conversation with your programmer partner:
- Singleton - There might be cases when you want to ensure that there is only 1 of a given object in your memory. This could be because your memory is too small or the object itself is too big. In this case, you might want to use the singleton pattern, which ensures during object creation that there will only be 1 object stored in memory and whenever we would like to create a new one, the one that is already stored is returned.
- Builder - Imagine building a house. It’s quite a complex process. Sometimes objects in software can be just as complex as houses. If we would like to split up the object creation into multiple smaller ones, we use the builder pattern to do so. Basically we are separating the process constructing a house from the actual house itself.
- Lazy initialization - In real life, when you want to use something, you usually take it out and use it when you need it. The idea behind lazy initialization is essentially the same. The object will not be created until we are certain that it will be used.
How do different entities in our software communicate with and live by each other? Structural patterns are here to give an answer to this difficult question.
- Adapter - When you would like to talk to someone in a country where you don’t speak the language but you really need to find the closest washroom, you will most likely use Google Translate to make yourself understood. Google Translate is your adapter pattern in this case. There are two objects (please don’t take this as objectification), in the form of you and the kind stranger you are trying to get information from, that do not know how to communicate with each other. In this case, the adapter (or interface) will be Google Translate.
- Decorator - Not everything knows how to to everything. To extend functionality, we have the decorator pattern. You might have a sweater that you really like (I certainly do), however, that sweater does not fare well in rain. However, if we apply a decorator to it in the form of a raincoat, we definitely get a combination that works. We did not change our original object (the sweater), but we have gained extra capabilities (in the form of water-resistance).
- Module - Keeping things organized is a difficult thing to do both in real life and in software. When we are arranging our books on our bookshelves we often use a system. Some sort their books by type, some by color, some by publishing date or author. Modules are similar to bookshelves, they provide a way to somehow organize our code in a structured way where we are the decision makes of what the logic of the structure is.
There are 2 other categories that we will not be discussing here. Behavioral and concurrency patterns these require a bit more understanding about code and will be discussing them in another article in the future.
To keep things moving, we will now shift our focus to the final area of architecture that we’ve mentioned in the beginning of the article, which is network-level architecture.