What IS and what IS NOT Software Architecture

Rogelio Consejo
15 min readMar 8, 2022

All software systems have an architecture. And most complex software projects fail because of their architecture.

The agile manifesto

Why?

Because when it is done wrong, the development becomes slower and more expensive as the application becomes more complex.

You have probably seen it when the code is so ugly that everyone wants to do a new version of it. And you actually get a green light, so you start a new system from scratch.

New language, new framework, new standards, new database technology, new front-end server, new clean code… but the greenfield is always greener on the other side of the project…

And if you stay long enough, you may notice that the project ends up being a mess, just a different kind of a mess. And development slows down again. And some people start talking about the new-new version.

This is where Software Architecture enters the game.

Architecture is about how things fit together, and about how the software will be used and developed. And most importantly: the purpose of good architecture is to keep the system flexible.

In this article, I will talk about the main things that are actually part of a system’s architectural design and how to know when you are actually thinking or talking about architecture.

Then I will mention a few things that are often confused with “software architecture”, and how to spot them.

So my goal for you is to know what is and what is not software architecture, even if you are not a software architect.

And for that, we need to understand what makes software so special. 🥰

Hint: It’s the “soft” in “software”.

What is the main value of software?

There are 2 main sources of value for any piece of software:

  • The software solves a problem ✔️
  • The software is easy to change (it is flexible) 🧠

Both are very important. But for software one is a lot more important than the other.

Let’s imagine a piece of software that does exactly what it is supposed to do, but it is impossible to change. The problem is that sooner or later the requirements are going to change, and at that point, the system will no longer work.

For example, you have a system to do your taxes and it works very well but it is impossible to change. Then the tax law changes. Now your system does not work.

On the other hand, let’s imagine a piece of software that does not work at first, but it is actually very easy to make it do all sorts of things tax-related. You have a system that is able to do taxes in any way you tell it to, but you have to give it the instructions first.

And the instructions are super easy to give (maybe you choose the actions that you want to perform from a list and you order them in the order that you want them to happen). And you even have templates for the instructions, so most of the time you just chose a template and it works.

Now when the tax law changes, you just change your instructions and the software still works. No need to wait for a new update for it to keep working (but the developers might release a new template soon, just for good measure).

And that is why, for software, “being flexible” is actually a few orders of magnitude more important than “solving the problem”.

We had machines that could “solve the problem” way before we had machines that could “be flexible”. Your computer is probably more useful than your calculator, for example. And both are probably useful in more situations than an abacus.

And that is why the purpose of good architecture is to keep the system flexible, not to make the system solve the problem (any developer should be able to make a system that works).

And we keep the system flexible by deferring decisions.

Now let’s talk about how architecture keeps the system flexible. But first, an example of what is NOT Software Architecture.

The most common mistake that shouldn’t be

Most developers call it “3-layer” o “4-layer” architecture. It does not matter how many layers. “N-layer”. This IS NOT architecture.

The main idea is that you start by designing your database schema (maybe build a DAO/DTO persistence on top of it, to abstract all those queries away), then your business logic, then maybe an API layer, and then the front-end.

So in these cases, the business logic is actually some sort of implementation of the database schema (or the Database Access Objects) and the front-end uses that business logic to access that schema indirectly. Maybe you even add an API to have a client-server deployment strategy between your front and your back end so the front-end uses that API to access the business logic to control the schema. That means that the front-end is still tied up with the database schema.

In that sense, even a “99-layer” architecture is just a monolith “with extra steps”.

This is not a very flexible material (marble)

It does not matter how many layers of abstraction you put between the front and the back, it is all one huge coupled chunk of code.

And that means that your “architecture” is the same as the one in a block of marble. You can carve a system out of it, but good luck making modifications anywhere but on the front-end, down the road. Even if you keep your code clean.

But remember that the purpose of good architecture is to keep the system flexible.

And you will know that you have made this mistake when you are talking about database schemas at a design meeting before any of the core code has been written.

You are making the database the “core” of your application, and tying up the whole thing to it. Also, setting up any kind of automated testing is bound to be risky and/or complicated.

This means that you are probably not using TDD. 🤨

You may have some automated tests, but how would you isolate your layers and mock persistence so that you can unit test everything? How do you get a testable system without dependency inversion?

Then you “mock” persistence by using some global variable and setting it to point to a development database or a production database depending on the environment where it is running. You may even have some docker containers to make it easier for your developers to get everything set up so that they can start working (because they need to have a database server running before they can try even parts of the code). And your developers are constantly having to look at the database for almost everything.

Those would be some of the “common symptoms” that would make me know that you have an “N-layered” monolith, even if you don’t know it yourself.

So if you are talking about database schemas or network protocols during the design phase, you are probably just designing a pretty monolith. A monolith with extra steps.

And you could even have a microservices deployment strategy but use only small monoliths (a micro-monoliths “architecture”?).

That, I guess, is a little better, just because you are actually having to define the boundaries and directions of the dependencies between those microservices. So you are forced to actually think about the architecture to be able to achieve the deployment strategy (and probably that is why microservices are all the rage). But like containers, in this case, microservices are just trying to solve a problem that you shouldn’t even have in the first place.

If you need to deploy a container to be able to work on your project, then you are probably not doing unit testing, only integration testing. And you have to do it that way because your system is monolithic to the point of depending on its deployment environment.

It is not that containers or microservices are bad, it’s that they don’t make sense for these particular problems.

As the old Chinese saying goes:

“A bit of dependency inversion now can save you a ton of deployment management effort for the rest of your life”

It is just a matter of knowing where to invert dependencies. And that is what architecture is about.

How to understand dependencies

Imagine that you develop a system where you have a core text editor and plugins for printing.

You can choose to print in many different ways (to a printer, to a PDF, to a PNG, and so on) and the system will just choose the correct plugin.

As you develop this system, if you update the core application, you may also have to update the plugins, but on the other side, you can update the plugins all you want without ever having to update the core. 🤔💡😲

If you have ever used a library or a framework or an import statement, you have seen some sort of dependency. If you have seen a class using another class, that is a dependency. If you have built a client for an API, that’s another dependency right there.

A depends on B

When A depends on B, any changes in B may end up breaking A. On the flip side, B does not care about changes in A.

It works the same way with a server and a client. The client depends on the server and changes on the server can break the client, but not the other way around.

Changes on the client cannot break the server.

This means that B is more stable (or harder to change) than A and A is more unstable (or prone to change, because if it breaks, it needs to change).

And the more things depend on B, the more stable it becomes. Changes on B become riskier, more expensive, and more difficult. But that could be a good thing in the right context.

If B changes, it could break A1 or A2, so B is hard to change, it is stable

On the other side, if B depends on many things, then B becomes very unstable and prone to change.

If A1 or A2 change, then B may break, so B has to change more often, it is unstable

And in architecture, one of the main things that you do is set boundaries and define the directions of the dependencies between them.

Some parts of the code change very little and some change a lot. Those who change a lot should depend on those who don’t change so that the components that change the less are the more stable.

That’s called the Stable Dependencies Principle. And that IS architecture.

This is where the D in SOLID becomes very useful

In code, normally, the flow of control and the flow of dependencies are the same.

So if component A uses B, it also depends on B.

A controls B

And so, most systems end up having the “N-layer” structure that we talked about.

The flow of control is the same as the flow of dependencies

Now what I am going to do is actually very simple, in some sense. This is where the Dependency Inversion in SOLID comes to the rescue.

Imagine that we do something like this:

Dependency: Inverted

Now from what we just discussed about dependencies, we see that this structure makes the system’s persistence easy to change, and at the same time, it makes the business logic harder to change.

Think about it: Out of the three, the business logic IS the one that should be changing less often. When a new MySQL exploit is discovered, the business logic shouldn’t care. When your front-end styling changes, your back-end doesn’t care (which is how it works naturally for the front-end anyways).

If you do it this way, you could even deploy the business logic independently from the front-end AND the persistence of the system.

This makes a lot of sense.

You could also, for example, make the persistence implementations into plugins and implement a simple text file persistence for developers, so that they don’t need to replicate the whole production environment before being able to work on their code (also known as “being able to easily mock your persistence”).

This means that you can update your MySQL implementation or you can even change it for a new Elasticsearch implementation, or even some other future database technology, all without worrying about the business logic code. And if it is built like a plugin, you can just deploy your new persistence without re-deploying the running core.

What I am saying here is that the business logic should dictate how it wants to save data with a persistence interface… and then the persistence implementation(s) should handle the details, the same as it naturally works with the front-end (the core does not care about the details).

Makes sense, doesn’t it?

Architecture is about those kinds of boundaries and directions.

The main principles of architecture

I want to just quickly talk about the main principles of architecture so that I can really drive my point home.

If you don’t know them, maybe you can think about them in the context of what we have talked about so far.

On one side, we use a few principles to think about what should go together, and in a broader sense, about what boundaries we should define in our system:

  • Things that change together should go together
  • Things that are used together should go together (and users of a component should use everything in that component)
  • things that are used together should be released together

So we draw boundaries between what “does not go together” and what “does not change together” so that we can keep the changes isolated.

And on the other side of the coin we have principles about how we should cross those boundaries:

  • Dependencies should be acyclical (so if you follow the dependencies, you never go back to a point that you already visited)
  • Dependencies should point towards the most stable component
  • The more stable a component is, the more abstract it should be

So we want the more unstable components to depend on the more stable ones. And we want the more stable ones to be more abstract.

Imagine that we have two components (again).

A depends on B

We know that if B changes, A might have to change.

If A is very hard or expensive to change and B is very inexpensive to change, then we just made the whole system hard and expensive to change. On the other hand, if B changes a lot and A doesn’t, then the whole system will probably need to change a lot anyways.

But if B is stable and hard and expensive to change and A is unstable and easy to change and changes a lot, then A can change without affecting B and everybody is happy (especially the developers that join the project at a late stage).

Boundaries and dependencies. That is what architecture IS all about.

Actors, boundaries, and dependencies.

And abstraction and change.

What is NOT software architecture

As much as it does happen in the real world, talking about database schemas or network protocols is definitely NOT talking about design and architecture. Not at all.

Choosing SQL over no-SQL is also NOT talking about design and architecture.

Talking about queues and caches and types of connections is NOT talking about design and architecture.

Talking about client-server is NOT talking about design and architecture.

Talking about load distribution is NOT talking about design and architecture.

Talking about cookies or sessions is NOT talking about design and architecture.

Architecture is not about non-functional requirements.

All of those are details of implementation and talking about them at the design phase is a mistake. Plain and simple.

Good architectures should be flexible enough that they can change their details of implementation easily.

Good architecture should not make trade-off decisions, it should be flexible enough to switch trade-offs when necessary, instead (as all businesses change).

Software architecture is not even so much about the functional requirements themselves directly, but about how you are going to make the system meet those requirements AND change when those requirements change.

Most of the time, when people talk about architecture, they talk about adding more layers to their monolith or about using a microservices deployment strategy (a deployment strategy is not an architecture) or about using a graphQL API (or even trying to define the API 😐) or about other things like that.

Details of implementation.

That is like saying that the design of a house is in the bricks and mortar. But design and architecture are not about the materials, but about the usage.

When you talk about anything client-server, you are not talking about architecture, but about the details of a network-based system.

And… the purpose of good architecture is to “keep the system flexible”, and definitely not to “decide things in advance”.

Software design and architecture is about how you defer decisions for as long as responsibly possible.

So in short:

Architecture is NOT about making decisions about things that you don’t know!

From what I have seen, I think that for most people, architecture is just a matter of choosing details of implementation based on guesses about the future of the system. Sometimes they work, but they are still guesses. A good guess is still a guess.

Do we use MySQL or Maria? What Network Protocol are we going to use for the front-end? How do we keep consistency over a cluster of distributed persistence servers?

But while you are focused on your one-to-one and one-to-many table relations, you are leaving the actual architecture out of sight.

There is no consideration for how to make the system flexible, what the core components are, or what direction the dependencies should be pointing towards.

And so, it is not architecture.

Architecture lets you make informed decisions about boundaries and dependencies that let the system evolve in an agile way.

Architecture is not about guessing.

I don’t know what the guessing thing is.

I would call it “putting constraints on your project based on guesses”. Or maybe “trying to guess how the system will be deployed and implemented before it makes sense to think about that”.

Or “we use kanban and sprints, but we do waterfall-type design anyway”.

Whatever you call it, it is NOT software architecture.

Also, the system’s deployment strategy is not software architecture.

If you are building a messaging system, the core should not care about the network. It should work on a distributed blockchain and it should also work on a client-server deployment over a LAN. Those are just two different implementations of the “Network” abstraction.

It should be as independent of the network as it is of the user interface.

As Uncle Bob said: Software architecture is not like regular architecture. With buildings, the deployment is very expensive, so you try to get everything right in the design phase because it is really expensive to change it later. But for software, the process of “building” is called “compiling” and it is really cheap and really fast.

With software, you don’t do a lot of design at first, because it is smarter to just fail fast and iterate. So you only design the structure of the system. Its boundaries. Its dependencies. And you leave the details of implementation out.

And even construction architecture is going that way.

Now they use BIM to test their designs and iterate and when the building simulation “works”, they start building. So even the construction business is becoming more agile than a lot of software projects being worked on nowadays!

And my guess is that it happens because most people don’t really know what software architecture is or what it is for.

So we have a lot of “detail guessers” that call their work “software architecture” and then everyone thinks that the architecture was dealt with… but it wasn’t… and the system remains inflexible and development still slows down.

They have just started working on a “monolith with extra steps”.

But if you want to make bigger and better systems, systems that you can actually expand indefinitely, you need to actually start thinking about architecture and stop making guesses about things that you don’t know yet. That way, you can even rescue an existing system without having to start from scratch.

Or you can just keep doing the same thing and start preparing for “The new new new new version of the software”. Your choice.

Stay Happy, Productive, and Smart!

--

--