Introduction to Software Architecture

July 05, 2023

My next blog post on my Software Engineer self-(re)study curriculum is about Software Architecture. Let's begin!

Learning to code and becoming a software developer has become quite trendy these days (at least, before the mass layoffs), however most think that all a software developer does is write code.

This couldn't be further from the truth - let's zoom out from coding and look at things from a bigger picture. Let's talk about architecture.

Think of a house. To build a house you need builders of course, but before you start building, you need an architect to design the house, only then can the builders build it.

The software developer is the builder, and the software architect, is well, the architect.

Software architecture is the stuff before the coding, it's all about designing a software system to meet requirements.

Let's start off with the first rule of software architecture: Everything is a trade-off.

This is to say that there is no perfect architecture, rather that, given the fact that nothing is perfect, what few things do we want make sure we can get right, in any given architecture? And I say "few" things with emphasis because if you try and make an architecture that does everything very well, it will become so generic that it won't do anything specific very well at all, and will likely become useless.

I'm currently reading the book "Fundamentals of Software Architecture" by A. Mark Richards and Neal Ford. In the book, the authors state that in order to define architecture, you must define these 4 things:

  • Architecture characteristics

  • Architecture decisions

  • Design principles

  • Structure

Architecture Characteristics

These are all about the non-functional requirements of the system, things like reliability, scalability, testability, maintainability, basically a lot of words that end in "ility". These are important because, if we go back to the first rule of software architecture "everything is a trade-off", the architecture characteristics determine the trade-offs we will have to make by choosing the things we can't possibly miss out, like say scalability. Anything we don't choose is no longer a priority.

Architecture Decisions

These are the structural rules that govern how the inner workings of a system should work from a low-level code perspective. For example one decision may be to use message queues for asynchronous communication. This is a structural rule because the developers will have to write actual code that uses message queues for communication.

Design Principles

These are almost like architectural decisions but they are much less strict, they are typically just general guidelines on how you should organise the code. It's best practice to follow these when you can.

Structure

These are about the overall "shape" of your system. There are various structures, such as: Microservices, Monolith, Microkernel. I will talk more about structure later. But generally, the structure you choose should fit well with the problem it solves, each structure is designed to solve specific types of problems.

As you can see, architecture gets very detailed. But the good thing is that you don't need to specify all these details upfront before you build your system. Building up an architecture is an iterative process, and they typically evolve over time. Ultimately it doesn't matter how your architecture has been designed, it's all about the purpose, the WHY.

This takes us to the second rule of software architecture: WHY is more important than HOW.

Software people are smart, they can usually determine HOW a system works by looking at it or looking through diagrams. But no one can find out WHY a system has been designed the way it has, just by looking at it. This is why communicating the reasons for each Architecture Decision, Architecture Characteristic, Design Principle and Structure choice are so important, so people know the why.

Before we dive into some of the different Architectural Styles available, let's talk about the high-level domain partitioning, and we'll dive into the two types.

In software architecture, partitioning is the way we break down individual components into tiny pieces that work together in order to make the overall component work. There are different ways to break down, or partition, the components, the two main ways are layered partitioning, and domain partitioning.

Layered/Technical Partitioning

Layered partitioning, is known as technical partitioning because each partitioned layer represents a different technical purpose. Some example layers may be Presentation Layer, Business Layer, Persistence Layer, Database Layer. The first layer is the external interface that something external, like a user, can interact with. The next layer is where the business logic is stored, the third layer is where the database models and persistence/storage logic lives, and the last layer is the database where all the actual data is stored.

Layered Partitioning

Domain Partitioning

Domain partitioning is different. Rather than using layers to separate everything by their technical responsibility, we can separate everything according to their purpose, or domain. For example, in an online shopping system, we may separate everything to do with the customer into its own separate area, and everything that is to do with the products into another area. Whilst in layered partitioning all these different parts would live in the same area of the code, see the below diagram for a visual understanding.

Domain Partitioning

Architectural styles

Let's get back to our discussion on Structure also known as Architectural Styles. There are a variety of possible architecture styles, entire books can be dedicated to each one, but I'll give you a brief description of a few popular ones. Firstly, there are two main categories of architectural styles. Monolithic and Distributed. Monolithic styles are relatively simple, but don't really scale, although they are a great start to a system. And distributed styles offer better scalability, better performance, and they have usually been designed from lessons learned from the shortcomings of monolithic styles.

Monolithic Architectures:

Layered

This is the architectural manifestation of layered partitioning. It's a simple layered architecture, the entire system typically shares a single database. Typically each layer talks to the layer above and below it, except for circumstances where this rule must be broken such that layers can speak to distant layers for the sake of speed and convenience. It's considered a monolith because the entire system is all lumped together as one big system and must all be deplayed (application servers, and database servers) whenever a change is made anywhere, it's not modular and is not scalable. An example would be, in an ecommerce system, you'd have all the logic for three different components such as: Shop, Customer and Product, all in the same application server. Although this isn't scalable, it's beneficial because the three components will be able to communicate to each other with no external network latency, because they live together in the same application server.

Layered Partitioning

As you can see, this is the same as technical partitioning, described above.

Microkernel

Also known as a plug-in architecture, the idea behind this style is to have a core component with all the basic functionality required, and when extra functionality is needed, "plug-in" components can be created and connected to the core component. A good example of something similar is Google Chrome. The basic functionality is a web browser, but if you want extra functionality that hasn't been built into the browser, you can go to the Chrome Web Store and download an extension with the functionality required. This is the idea behind Microkernal architecture. Again, it's a monolith because you typically only have one instance of the core component's application server, and one shared database for the whole system. Not scalable.

Microkernel

Distributed Architectures:

Service-based

Now we're getting distributed. Service-based architecture is one step away from monolithic layered architecture. Instead of having one big application server or service with everything in it, functionality is split into separate coarse-grained services with different responsibilities. An example would be, in an ecommerce system, instead of having Shop, Customer and Product components all in one service as in the layered monolithic architecture, instead you'd have three separate services for each. This means that making a change to one service will allow you to redeploy that service alone and not necessarily the whole system.

Service-based

Microservices

This is arguably the most popular architecture, it's really new. It gained popularity due to the reduced costs of spinning up new servers and leverages technologies like cloud computing, and containerization. Now it's easy to have multiple application servers running on the same cloud server, easy to start and stop the application servers, and easy to redeploy them. Microservices take the idea behind service-based architecture of splitting things into services, but microservcies takes things a step further and splits the services into even smaller services than in service-based architecture, these services typically contain their own database making each microservice indepdently deployable. To achieve this, microservices are self-contained which means that they have everything they need to run and nothing else. Going back to the first rule of software architecture, everything is a trade-off, one trade-off made in microservices is choosing low coupling in favour of high duplication. This means that each microservice will likely have similar code written in it, rather than sharing the common code between services.

Service-based

Example Software Architecture session:

For fun, lets do an example software architecture session. We'll create a new architecture according to the requirements we extract from a task.

Task

An African restaurant called Mama Africa wants to disrupt the food industry with their completely new recepie for Ugandan Jollof Rice. They've recruited the best food scientists from all over the world, including culinary specialists from Africa. Mama Africa is looking to secure funding and wants to put together an MVP for their food delivery platform to ensure that this Ugandan Jollof Rice gets into the hands of their 1,000 daily customers.

Requirements

If we break down the task into architectural characteristics, the non-functional requirements we know that this service has to have. We know it needs to have scalability to handle a potential large amount of users, right now they have 1,000 daily customers but they're looking to scale! We also know that reliability and performance is key as we don't want the food delivery platform to be too slow - people are hungry, and will get fed up and order food elsewhere! And finally, because this is a new platform and it's an MVP let's opt for simplicity, we don't want to overengineer a solution.

Result

Let's dig in. The key components of this system are:

  • Mama Africa (MA)

  • Customer (C)

  • Ugandan Jollof Rice (UJR)

This is simple enough that we can start with a layered monolithic architecture as below:

Initial example

This architecture should be able to handle the 1,000 daily users, should be reliable, and is definitely simple. But the problem is that it's not as scalable as it could be due to the fact we are expecting users to increase beyond the current daily 1,000 users, we're ambitious here - but don't want to overengineer, so let's keep things simple but opt for a distributed architecture, service-based architecture, and split the three components, Mama Africa (MA), Customer (C), and Ugandan Jollof Rice (UJR).

Our resultant architecture for the Mama Africa MVP is as follows:

Final example

Ultimately there are many possible architectures, but if we finish off with the second rule of software architecture, the WHY behind the architecture is much more important than HOW it has been designed. As long as you can reason about your architecture, you're good to go!

Happy learning (and building)!



Go back