Lean Architecture

It is high time to ‘kill’ Clean Architecture, the Hex, the Onion and the VSA and come up with something better

Nicholas Ocket
11 min readApr 14, 2024

One of the most common ‘architectures’ among aspiring software designers is either the Clean Spaghetti Architecture or the Hexagonal Bubble Architecture. I don’t blame them for using these antipatterns. At least they are trying something new. They are going out of their comfort zone, ready to make mistakes. Here is a visual representation of both these architectures:

Clean Spaghetti Architecture
Hexagonal Bubble Architecture

These ill-advised designs are symptoms of a larger problem though. Most people who talk or write about these topics don’t have a clue what they are writing about. Novice developers and people new to the field of software design pick up these articles or talks and do not know what they are learning. That is a big problem actually.

Therefore I would like to redefine Clean Architecture. To be more precise, the meaning of ‘Clean Architecture’. To be fair, it is not only Clean Architecture but all of the ‘Great Architectures’: Hexagonal Architecture, Onion Architecture, Vertical Slice and what have you.

What they are today, what they stand for, how people generally think about them and apply them, is severely flawed. These architectures have been reduced to a set of rules to follow. The reasoning behind those rules, the problems these ways of structuring software seem to have been lost. Despite these rules, we still create huge messes of code. Because mentors keep on teaching the rules but seem to forget to add the real value behind those rules.

Technology seems to evolve every day, but the way we build software doesn’t seem to evolve at the same rate. On the contrary, it seems like we are doing worse than before.

I think it is high time to ‘kill’ Clean Architecture, the Hex, the Onion and the VSA. Let’s put them in a cocoon, feed it honest reflection and let it grow into a new and better way of teaching and applying software design. A better way that is based on what these architectures were trying to show us.

Flaws of the ‘Great Architectures’

I like the intent of, for example, clean architecture. The intent embodies what software design is all about. At least in my opinion. It inspires people to do a better job, it makes them aware that software needs to be maintained. If you do a poor job preparing your code for this, maintaining it is going to be hard and costly.

The word ‘Clean’ implies this intent. Something that is clean and structured is easy to maintain. Despite the wonderful intentions and being promoted as good solutions, the ‘Great Architectures’ come with major flaws. Basically, Clean Architecture, hexagonal architecture, the onion and what not address the same problems and therefore have the same major flaws.

The biggest flaw in all of these ‘architectures’ is that they focus on solving one specific aspect: either keeping exterior dependencies in check or focus on slicing the code per functionality. What about the rest of the application?

The intent is to build maintainable solutions, but these architectures generally tackle only one aspect of maintainability.

The next major flaw is that all of these architectures seem to promote an end result. As an example, the result of applying Clean Architecture is a certain structure in the solution. That is usually what starting developers remember. These architectures seem to be oblivious of the ongoing process that is behind software design. There is no structural approach to build these architectures either. This leads people to believe that they have done a good job as long as they mimic the presented end result.

A structured way of building these solutions, a strategy, would be a great help to tackle all aspects of maintainability and to greatly enhance the success of your solution.

Who can honestly say they have a stable approach, a structured strategy to break down a business process and turn it into maintainable code? Do you know how to go from problem to code? Do you know what is truly important, what to keep in mind while building software so that we have a maintainable state in between the ongoing process of change?

The last major flaw of these architectures is how they are being taught. Many ‘mentors’ and tutors are teaching something they don’t fully understand. Downstream the river of knowledge passing, the knowledge and insights itself gets watered down to a point where people implement stuff they learned but have no clue what they have done nor what the purpose is. They are implementing architecture for the sake of the architecture itself. They no longer know what design problem they are solving and so they implement things that are not needed.

Understanding before rules

I would like to see a ‘Lean Architecture’. An architecture that is based on efficiency of implementation, offering little waste of time on concerns that do not matter. An architecture that solves the problems of today, not those of tomorrow. There is no extra proverbial ‘fat’ for future problems. In this architecture, we care more about the characteristics of the architecture than how it actually looks like.

This ‘Lean Architecture’ is based on understanding rather than on rules. It is based on an understanding of how we can build maintainable software using the many aspects, rules and practices which we already know.

This Lean Architecture should focus on what is really important. The foundation should be the ability to use these aspects and practices as tools to get to the core of the problem we have to solve.

I would like to see Lean Architecture as an ideal which we try to reach by applying principles and better practices in the context of the problem. A Lean Architecture is not an end result. It should be a methodology, backed up by an ideal and an excellent understanding of better practices to get to that ideal.

See it like this: People should think more about the resulting characteristics of their code than whether or not they broke some rules that don’t seem to fit their context anyway.

The cornerstone of Lean Architecture should be understanding. It beats following rules for the sake of following rules.

We need to understand the ideal we are chasing. What is the goal we are working towards? If we don’t understand the goal, what it is and what it means, we can’t make progress towards that goal.

We also need to understand that we are chasing an ideal. We work in a certain problem context and this context will force us to deviate from that ideal. I am talking about trade-offs. We need a good understanding of trade offs, both as individual developers and as a development team. Without taking this context into account, we are just writing code instead of creating a solution.

Finally, we need to understand the practices we want to apply. We need to know what problem they solve so that we can identify why we apply them and when we should apply them.

How should it be?

I think we could already achieve a lot if we can focus on what the intended goal is of software design and if we can agree to some strategy or method to get closer to that goal. That is how we should tackle software design. Keep the goal in mind. The process of how you get there is merely a detail. In other words, the process should not be the goal.

Let’s start with the goal. The goal of software design is to come up with a solution in the form of code (not an architecture). Not just any solution. A solution that is cost effective to maintain.

A cost effective solution is a solution that is easy to maintain. We are talking about code that is easy to understand, easy to test, it enables cooperation and in general it is easy to change and evolve.

Evolution is really important. Stakeholders evolve, their problems evolve and thus your solution should be able to evolve along. Being able to evolve along shouldn’t be a huge cost.

That is why I would like to see Lean Architecture more as a methodology / mindset rather than an end state. It should be a process of intermediate states only. The process only stops when change stops. It’s a constant search for a code solution that solves the current problems and at the same time keeps the amount of design patterns, principles and what not to the bare minimum.

We keep the following question in mind: How much design do we need to implement right now to keep the codebase maintainable?

That is how we want to keep our solutions lean, simple and minimal. This is how we can shift away from thinking about endstates and required structures.

Change and the ability to evolve separate parts of the entire application should be central in our solution as that is how we will keep the solution cost effective to maintain. With our design, we don’t want to focus on only one part of the solution. We should strive for all parts to be able to deal with change.

These are the biggest challenges that come with change in a code base:

How can we minimise losing time and money by unwanted/unexpected changes or changes that aren’t in the scope of what we are supposed to be doing?

How can we avoid losing time and money by introducing bugs and unwanted side effects that impact the correct working of our application?

How can we avoid losing time and money trying to understand the code and what it is supposed to be doing.

These are the things we need to solve with our design / architecture. These are the questions we need to ask when making design decisions.

A Lean Architecture design process

There is no one right way of building software. But it pays off to have a good way of building software. A strategy that will guide you from breaking down a problem to a solution, paying attention to the important decisions along the way. I think this is especially valuable for starting developers.

What follows here is an overview of my Lean Architecture strategy, the path I use in my day to day job and the one I am teaching in my About Coding Dojo. This is the first version btw. While it has already proven highly effective, I am still looking to improve.

In rough lines, it is a 5 step process where I tackle 3 important points in creating a maintainable solution:

  • Step 1: Analysis of the problem and the stakeholders.
  • Step 2: Analysis of the current use case.
  • Step 3: Modelling
  • Step 4: Implementing the different pieces and their tests
  • Step 5: Dependency management

The first point is separation of concerns. How will I split up the code of my solution in such a way that the impact of change between different components is really small? Separation of concerns is really important, especially in the beginning. With separation of concerns, you define how the relationships between your components are going to look like.

It will also help in making the code more understandable.

The second point is modelling. Your solution is basically input/output of information that is being transformed in processes that you will define. These processes usually revolve around some concepts of the problem domain. What are these concepts we are dealing with? What are they representing?

Modelling is important because it is the core of the application. These pieces of code will be reused a lot. Changing them will most likely have a big impact. So we want them to be good representations of the problem in order to avoid unwanted behaviour and trigger unwanted change in our code base.

The last point is managing dependencies. Managing the dependencies between code components and code units is important if we want to keep the code cost effective to change. We manage the dependencies, deciding which code units and components can have an impact and which ones shouldn’t have an impact on others when changed.

Here are the steps I follow:

Step 1: Analysis of the problem and the stakeholders.

In this step we do a big part of the analysis. We need to know who the stakeholders are and what they want. This is where we get to know the different use cases and which groups of stakeholders are interested in those use cases. At the same time, this is where we start separating the different concerns.

In this first step, we also decide which use case we are going to tackle first. This should be in agreement with stakeholders, going for the most valuable one.

Step 2: Analysis of the current use case.

In this step, we continue separating concerns. We analyse the use case and get a first idea of the different code units which will work together to accomplish the use case. In this step, our knowledge of the stakeholder composition can already be of use.

In this step, we want to know the following:

  • What is the input data and how should it enter the system?
  • What is the desired result?
  • What are the different steps required to go from input to desired result?

The first 2 steps are my decomposition strategy. It is the analysis of the problem which results in an overview of the main code units. It forms the skeleton of the solution for this particular use case.

Step 3: Modelling

In this step, we look at the centre of the problem. What are the concepts we can represent? In this step we go about thinking about who is interested in what information. Is all of the information as important in this use case? What models can we build from that? We might also think about persistence.

Step 4: Implementing the different pieces and their tests.

In this step we finally start coding the different pieces. TDD can come very handy in this step. From step 2 we already know what the input is and what the desired result should be. This is enough to start writing tests. We focus on testing the behaviour of the use case.

Step 5: Glue everything together with a decent structure and some decent dependency management.

In this step we add the missing pieces, such as a decent structure, the input and output infrastructure like controllers and databases, etc. We think about the impact of our components on each other and adjust where necessary.

At all times, we keep our goal in mind. Because the process itself does not account for Lean Architecture. With every decision we try to evaluate if it brings us closer to our goal of maintainability or not. We fix the design problems we have now. We don’t try to fix possible changes that have yet to come. This is the hard part, deciding when to add more design. This becomes easier when you truly understand the design problems that certain techniques and principles solve.

Epilogue

This is my way of working, my little ‘kata’ sort of speech that I perform every time I am implementing new features. It is not perfect, but it is effective. The funny part is that none of this is new. We have done all of this before but somehow we seem to have forgotten what is important. Somehow the way we build software has evolved into something that is no longer effective.

This strategy is what I am teaching in my About Coding Dojo. We do this kata over and over again in different situations. We come across different challenges only to find different ways of conquering them. In my opinion, that is how you grow with a mentor.

--

--

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.