I have quite a few friends and acquaintances who are working in recruiting or areas where they require some knowledge around software engineering but not too much. I’ve decided to start a series where we are going to explore some basic programming concepts targeting you, recruiters and sourcers, so you don’t get super confused when you are talking to your respective engineering manager when they start talking about monads and other insane crap.
In this edition, we are going to aim for understanding what software architectures are in general. This will be particularly interesting for those of you who are working with web developers. Let’s start by learning what the word architecture means when we are talking about software.
Note before we get started: the definitions I am going to list here are by no means scientific, don’t cover the entire topic and are somewhat opinionated towards my understanding to the topic. I will be linking articles to as many places as I can to make sure that I am not talking complete mumbo-jumbo.
Software architectures from a bird’s-eye view
So the first question that we might be asking ourselves is what this entire architecture bogus is all about.
According to Wikipedia, software development and (building) architecture has been compared as early as the 1960, however, the term software architecture has only been around for about 25 years, since the early-mid 1990s. Those were the times when the usage of the internet started spreading rapidly and the software that was to be delivered became more and more complex. Earlier, people we able to use algorithms and data structures to manage complexity, however, these did not describe precisely how an entire system and it’s sub-parts were operating together. The earliest significant appearance could be associated with a legendary computer scientist Edsger Dijkstra.
So basically, the word “architecture” would indicate a high-level overview of software and systems. But what systems am I actually talking about here? Software has multiple layers where we can talk about systems. Here are some that might be important for you, with a short tl;dr if you didn’t have time to read the rest of the article.
- Machine-level architecture - The architecture of the machine that the software is executed on. This is probably the system layer that stands closest to electrical engineers and real architects, since we are mostly talking about physical things here, such as wires, transistors, gates (not Bill) and chips. Typically when you are talking to a software engineer, you will not be addressing these items, unless you are hiring for hardware developers or IoT.
- Code-level architecture - The way of organizing code in the way that it’s (presumed to be) the least confusing. This is the part where most software engineers land when they talk about architecture, since this is literally about the code itself. Architecture on this level mostly talks about design patterns (if the word architecture was not confusing enough, I’d gonna throw in design here as well) that are used to give form and structure to massive amounts of code. Design patterns are taught at schools and are sometimes required to know from apprentice to master level engineers. Most likely everyone you are recruiting should be vaguely familiar with the idea of code-level architecture.
- Network-level architecture - The way of describing how software systems that live in different places talk to each other. Software does not necessarily live in one place. It’s possible that a piece of code that is running in Ireland in an Amazon data center needs access to data from the machine of a 15 year old kid from their mother’s basement in Illinois. In this case the software needs to have access to the other piece of software which is usually done over the internet or some sort of network.
Now that we have a list of interesting architectures to talk about, it’s time to get serious. Let’s take a look at them one-by-one, starting with the iron.
As we’ve mentioned above, when we are talking about machine-level architectures, we are literally talking about physical systems that are interacting with each-other over wires using electricity. These electric impulses trigger simple computations on atomic items in software that we call bits.
What the flip-flop is a bit? Good question! A bit is a basic building block in information theory (not my words). It can have 2 values: 0 and 1. Multiple bits can describe more complex entities, such as integers (101010 means 42 in binary) or text (01100001 01110000 01110000 01101100 01100101 01110100 01110010 01100101 01100101 stands for appletree in ASCII binary). Why do we do something so unreadable? The answer is simple: 0 and 1 matches perfectly with electrical impulses. When there is electricity, the bits says 1, when there’s none, it says 0. Everything in modern software is composed of bits. We can thank this wonderful model to John von Neumann.
Bits are can be stored in many ways depending oh how we want to use them. Before we jump into the usage, we might want to take a higher-level look at how a computer actually works.
The Central Processing Unit
The Central Processing Unit (from now referred to as CPU) is the part of the computer that does the computation and all the calculations that your machine needs to operate. The CPU receives inputs from the rest of the system and calculates the next state the system should be in. Here are some of the things that the CPU calculates for us:
- When you move your mouse, what is the next place it should show up in on your screen.
- When you open LinkedId recruiter and search for “data scientist” which people it should show up to you.
- When you are preparing a report in Excel, and want to aggregate data, what the result should be.
As you can see these are a wide-variety of problems to solve and CPUs do most of the heavy-lifting here. The architecture of a CPU is quite complex and probably should not be included in the scope of this article. But here is how you can imagine it:
- There is a part that does the math, this we call the Arithmetic Logic Unit (ALU)
- There is a part that does the logic, this we call the Control Unit (CU)
- There are parts that we call the registers, this is where we store temporary information about calculations
- There are parts that we call buses, these are the systems that carry information between the CPU and memory
Speaking of memory, it is probably time to talk about that as well. If you’d like to learn more about the CPU and how they operate, I highly recommend checking out the Wikipedia article which contains a bunch of useful information around this topic.
Let’s zoom back to bits for a second. We have checked out how the CPU takes bits and manipulates them to become new ones through complex math. When we think about our brain and how we operate in real-life, usually this is the process that happens:
- Something around us is happening
- We get information about previous similar situations from our memory
- We do something according to the memories and the current situation
Just like in real-life computations, the computations in software usually require access to memory as well. The idea is quite simple, but the physical space requires tricks to make things actually fast and reliable. The idea is, that the closer the memory is to the CPU, the faster we can access it, however, the space around the CPU is limited, so the memory we can access quickly is usually small. The farther we go from the CPU, the slower we get, but the more we can store. Something for something I guess. Here are the different types of memory that we like to differentiate:
- Caches - The fastest type of memory. When we are talking about hardware we are mostly referring to caches that live inside or right by the CPU. We categorize these caches as Level 1, Level 2 and so on, Level 1 being the fastest of them. Caches are usually very-very tiny, sometimes only being able to store a couple of bits at once, however, the access to them is rapid beyond belief, making them very valuable assets for people who are trained to use them.
- Random-access memory - The long version of RAM. Slower than the cache, RAMs are still very fast to read from and write to. The naming of “random-access” comes from the past when software engineers could only write things into memory in order, making software very rigid and difficult to work with. From the 1940s there was development to make things a bit easier using magnets (how flipping cool) and it kinda worked, making the first version of the random-access memory viable. RAM is the memory that is used by most software that is running on your machine. The important state of your software is stored in the RAM, such as your photos when you open iCloud or the text that you are typing when you are working on a Word document.
- Disks, drives, the storage - Now we have gotten really far from the CPU to the areas of the cold storage. This is where your photographs are stored when you are not working with them. This is where your games are installed. This is where the data that is collected about you is stored when you are browsing the web unsuspectingly. Cold storages are slow to access.
You still wouldn’t notice the difference with your eyes between a cache read and a cold storage read, however, here are some comparisons between different types of memories:
- RAM can be up to 100.000 faster than disks
- Accessing a Level 1 cache can be 100 times faster than RAM
Some cool numbers can be read in this StackOverflow post (although I don’t necessarily agree that every programmer needs to know that light travels 1ft in 1ns).
There would be a lot more to say about the different type of memories. I recommend checking out this article for some more reading on how to differentiate between memories.
We have gone quite far in hardware-land so I am going to zoom away from the hardware architectures for now. Probably will dig deeper in this area in a future post. However, let’s get into something that is way more in my area of expertise, code-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.
We’ve gotten this far, congratulations. It’s time for us to talk about network level architectures, or how different groups of software communicate with each other.
Whenever we are talking about communication, there is always a phrase that comes up in software that defines the nature of how code talks to other code. This concept is the concept of a protocol.
What is a protocol
Protocols are widely used in both software and non-software communication across the globe. A protocol is essentially a set of rules that the two parties agree in when they are attempting to communicate with each other. When you are reading this article we are using a protocol that is called the English language. This protocol consists of various subsets, like the alphabet, the syntactic ordering of letters, the semantic meaning of the words and the grammar that is behind the entire thing. All of these things are required for you to understand the words that I am typing tight now.
The same is true for software. In order for 2 systems to talk to each other, a protocol needs to be established. Probably the most famous protocol that you might have heard about as well is the HTTP/HTTPS protocol (yes, this is the one you can see in your browser and all the meme links that you send to your friends on Instagram). We are not going to talk about this a lot now. What’s important is that we are going to draw some diagrams below where we will be using lines. Lines indicate communication that require a protocol. On the lines we usually indicate what protocol we are using, in all the diagrams we are going to assume that we use HTTP/HTTPS protocols.
Clients and servers
Now that we know what a protocol is, we are going to talk about other atomic building blocks of network level architectures:
- Client - A client is a system that would like to access information from another system.
- Server - A server is a system where we store some information or do some specialized computation that clients do not have access to. Servers can also act as clients, if they require information from other servers to complete their work.
A client can be you, browsing Medium in search for an interesting article. In this case we can draw a diagram that looks like this figure:
In the above figure you can see a client making a request to a server. In this case the request is happening over the HTTP protocol to medium.com. The Medium servers process this request and return with a response that is the page rendered on the medium.com page. Making requests should be easy and in this case is most likely done using the browser of the client (i.e. typing medium.com in the url bar of the browser that the client is using).
Behind the scenes there might be things happening, of course. When we make the request to the medium website for an article, there are systems which are fetching additional data for the given page that we would like to examine. I am not familiar with the medium infrastructure, but it might be easy to imagine that when we are requesting an article, we would also like to get the comments with it. If I were Medium I would store the comments on a separate system, in which case the communication might look something like this in the background:
In the above figure you can see that we are fetching information from the comments server of Medium from another server and returning the comments. This can be useful if you are trying to separate some systems from each other. Some teams might specialize in rendering the articles in Medium, however, they might be not very good at maintaining comments, so another team will take care of that for them. This is something that we call domain-driven design in software, which we are going to take a closer look at in a later article.
Oftentimes servers themselves don’t host data, they just collect it from various places and return it to a client. These places are usually some sort of cold storage (remember from the architectures above?) that are accessed using different (usually faster but more difficult to understand) protocols. This was just a very difficult and long way of explaining databases. Thanks, Akos.
As you can see on the above diagram, the comments server has made a select request to the comments database and then that database has returned with a list of comments to the server that it can later return to whoever needs it. The main idea is that the comments server owns the domain of comments, meaning it knows the rules of how commenting works, like who can comment on what articles, profanity filters and even logic of how comments relate to each other, whereas the database hosts only the data of the comments and does not worry about the business logic behind them.
This article is way too long. I hope that you, the reader, now has a better understanding about the different type of architectures in software development.