Clean Architecture: The essence of the dependency rule

Thinking differently about layers and dependencies

Nicholas Ocket
8 min readJul 30, 2023

When you are talking about ‘Clean Architecture’ with your peers, they think about layers and the diagram with the circles. You can’t really blame them for that. The diagram is amazingly well marketed. I am talking about this diagram of course:

Every developer will remember the layers. If you would ask them about the fundamental aspect of Clean Architecture, I bet almost every single one of them will pinpoint the layers. I am not sure how many developers would mention the dependency rule at all.

It gets even more interesting when you ask them how they would do the layering. The majority will start talking about some kind of folder structure. Because … dividing code into different folders who are representing these layers, isn’t that how you separate concerns?

Unfortunately people who think like that, are completely missing the point.

The layering in Clean Architecture is not about folder structure. It is about dependency management. That is why the dependency rule as Uncle Bob calls it, is the real way of achieving layers. Layers are the result of the dependency rule and not the other way around.

The goal of this article is to give you deeper insights into the dependency rule and challenge you to think differently about layers. Because once you truly understand this, you will be far more effective at building Clean Architectures and general software design.

What is the dependency rule?

Uncle Bob explains the dependency rule by making use of the diagram with the concentric circles. Software applications are divided in different areas or layers, each represented by a circle. Therefore you would think that the dependency rule cannot exist without the layers or the circles.

Using the circles, the dependency rule is a rule that states that dependencies should only point inwards, towards the centre of the circles. So the outer circle depends on the next inner circle, and so on. To use Uncle Bob’s words: “Nothing in an inner circle can know anything at all about something in an outer circle”. If it comes down to explaining how to use this dependency rule, it doesn’t get any clearer than this.

Using the circles is in my opinion the easiest way of showing people how to use this dependency rule. But it is also a shortcut. Developers are not informed why it has to be done like this. Uncle Bob just lets us know that we don’t want outer layers to have an impact on inner layers.

How could an outer layer have an impact on an inner layer? I think this is important to understand. I would even say that it is crucial to be effective in software design or Clean Architecture for that matter.

We need to dig deeper into what the dependency rule really is if we want to come to a better understanding of Clean Architecture.

Dependencies and change

The dependency rule is a rule that defines which parts of your code base should depend on each other and which parts shouldn’t. The layers emerge from ‘the direction of the dependencies’ between those parts of code. They are actually a consequence.

I realise that this is probably raising more questions than it provides answers. What is this direction of dependencies and why is it so important? Why should certain parts of the code base depend on each other but not the other way around? Bear with me, I will explain everything in depth.

The direction of dependencies is a more clever way of saying which code unit is depending on a different code unit. For example: Code unit A depends on code unit B. If you would represent this with an arrow, you have a direction.

The direction of dependencies

This means that code unit A is a client of code unit B. You might say that unit A has a ‘using-relationship’ with unit B. Code unit A is using something from code unit B because unit A needs that to work properly. In other words: If code unit B wouldn’t exist, then code unit A wouldn’t be able to fulfil its purpose.

The drama of dependencies

There is another consequence of this relationship. Because code unit A depends on code unit B, there is a chance that code unit A will have to change along with code unit B. That is the nature of depending on something.

Dependencies and change

But the opposite is also true: No change to code unit A can ever impact code unit B. So in other words, change doesn’t spread in the direction of dependencies.

As usual with software design, change is the keyword here. In a way, it is the answer to all the questions we had before.

Why is the direction of dependencies important? Because of change. Change will not spread in the direction of dependencies.

Why can some parts of our codebase depend on some others, but not the other way around? Because of change. We don’t want some parts of the code to change along with other parts of the code. It is as simple as that.

Emerging Layers

There is only 1 big question left: which parts of the code should depend on each other? Excellent question! To get to a clear answer, let’s take a step back and think about what it is that we do when we are building software.

When building software, we are solving a business problem. We aim to understand this problem and then abstract it into what we call (domain) models. To put these models to good use, we need input from the outside world. Otherwise our models are just models.

So we program these different ways for users to bring that outside information into our application. We also define how users want or need to use these domain models together with outside information.

A small, but concrete example. Let’s think about hospitality and restaurants in particular. Orders are still taken with pen and paper. Sometimes the order doesn’t contain the table where the ordered food should end up. That is a common problem and we are going to solve this with a software solution.

An order is an core concept in this problem domain. We could model an order. Some properties would be a list of menu items that have been ordered and the number of the table. Which menu items and which table number exactly is information that will vary in different situations. So we could build a UI for our user to input this information and we then also build a flow to bring the input together with the domain models and then persist it somewhere.

Now here is an important question: What would be a valid reason to change the code of the ‘order’ domain model? Would it be ok to change that model in any way because the user wants to use it in a different way? For example, instead of taking information like the table number or the menu items, the user now wants to be able to read for which table an order was. Is that a good reason to change the code of the order?

No. That is not a good reason for a domain model to change. A domain model should only change if the domain itself changes. These domain models are being used by many different users in different use cases. It would cause even more changes in places we don’t want change. We don’t want domain models to change for something that has nothing to do with the problem domain.

Now imagine we are no longer making this software for a dine-in restaurant but all of a sudden it is a quick-serve restaurant, like McDonalds or Burger King. You don’t need the table information anymore. That could be a valid reason.

If we apply here what we have learned about the direction of dependencies, we can say that these domain models should not depend on process code. Instead, code units we write to define these processes should depend on domain model code.

What would be a valid reason for processing code to change? Is it ok if processing code changes because our customers require new ways or different ways for getting input into the system? For example, instead of having a UI to take into order information, customers now need a web service endpoint. Should this have any impact on processing code or use case code?

No, it shouldn’t! The code that describes the use cases of our users and stakeholders should only change if their needs and requirements about these processes changes. How these processes accomplish their goal should not depend on how information is getting in or out of the system.

On the other hand, it is ok to have the use cases impacted by changes in the problem domain. It is natural that if the problem domain changes, the use cases will also change. If we put that into a diagram, we get the following:

I don’t know about you, but to me this looks a lot like layers. You might notice that I only come up with 3 layers. That is what I end up with when I think about software in general. We create models and general problem domain rules. We create code that represents how users and stakeholders want to use or need to use those models and we have code that takes care of the input and output of information.

If you need more layers, I won’t stop you. Create all the layers you want if it helps you with creating better software. As long as you understand it is not really about layers. It is about dependencies and how dependencies will affect the way your code can change. Folder structures are only helpful to organise your code in the way you or your team prefers. But folders alone will not stop change from impacting code that shouldn’t be impacted.

I hope you enjoyed this article. Good insights and understanding are crucial to being great in software development. Whenever you are ready to add Clean Architecture and all of the techniques around it to your personal toolbox, check out my website.

--

--

Nicholas Ocket

I am Nicholas, a software engineer with more than a decade of experience. My mission is to help developers to truly understand and apply software design.