
Good architecture makes Code easy: Let code be your guide to good architecture
Good architecture makes Code easy: Let code be your guide to good architecture

This article is based on my talk at the recent WSO2 technical conference.
When we see code we do not like, we usually dig in to find out what went wrong. When we dig in, sometimes we find the developer is to blame. However, sometimes, after reviewing the details, we can’t blame the developer.
Yes, you can mess up good architecture with bad code. But it is hard to fix bad architecture with good code.
Let me take an example.
In one of the SaaS products, we found that the getComponets() method is taking too long, and when we dug, we found that the method does nine API calls, each of which, in turn, calls others. However, those calls were unavoidable because different aspects of the component are scattered across many services, due to the microservices design we had adopted. This design is not necessarily the fault of microservices; although microservices do trigger this, in some situations, it can be avoided with careful domain decomposition.
Even good ideas depend on the context and should be used in the right place, in the right way, at the right time.
In today’s article, I want to talk about how good architecture and good code depend on each other. How to use code as a smell test on architecture, and how can we find good architecture?
When we talk about good architecture, I remember a story. Someone said that when he was building the house, he had gone to the structural engineer to check the structure. This was years ago when doing these checks was not the norm. The structural engineer had asked to remove most of the reinforcements.
At first, the structural engineer’s request to reduce the structure surprised me. However, it made sense, as otherwise, all other houses not designed by engineers should fall down. Engineers who understand the problem in detail and precision solve the problem most efficiently, while others can solve the problem less efficiently.

I learned an important lesson about good architecture from this incident. Good and bad architecture looks like an arch built with bricks vs. concrete. Efficient horizontal forces hold up the former, while the brute force concrete holds up the latter. Good architecture lets us solve the problem with simple and efficient code.
Good architecture and good code are flip sides of each other. The following are some properties of good architecture as opposed to bad architecture.
- Bad architecture is overdesigned, while good architecture finds the simplest solution.
- Good architecture finds a clean separation of loosely coupled components, where related things go together, and the most used components are the most stable, reducing code duplication.
- Good architecture will allow for future requirements, which are often achieved by making the system’s critical internal state accessible to components that may need the state.
- Good architecture is easy to understand and explain, providing a clear language to communicate.
Each of the above makes the code simple, easy to understand, and easy to change. However, finding such an architecture is not trivial. Given a problem, how can we find such good architectures and, by extension, good code?
Let us explore three concepts, which I believe will allow us to do better architecture, hence better code, and, by extension, better systems.
Concept 1: Obsessively think from the perspective of the end user.
This is the most common mistake architects make.
When we implement a feature, it is easy to go for a design that will make it easiest to write the code. However, that is a mistake. Code that is easy to write now is not necessarily the best code. The best code is code that is easiest to write, maintain, and understand. To find such code, we must find the design that provides the best experience to the end user, both in the short term and in the long term. If we write code for short-term needs, when inevitable changes come, we will be forced to make the code complex, unmanageable, and cluttered.
Let’s consider two examples.
First is an incident reported from the “Code for America” initiative while building a system to distribute funds as aid. When they first opened up the system, only a small part of the potential users signed up. When they improved the user experience, many of the users signed up. Furthermore, when operations begin, and they start to distribute money monthly, a significant portion is used at midnight, once the money becomes available. Who would go shopping at midnight? Likely, those are people who are hungry. If that is true, the UX of the system was so bad initially that even those with dire needs could not do the signup. For me, this is a vivid reminder of the role of UX. Bad UX practically makes the product useless.
Fixing the UX later is often expensive and often costs ten times or more resources over time. As an architect, only if you look from the end user’s point of view, and look hard, can you find those answers.
Let’s look at the second example to demonstrate what I mean by look hard for answers. Let us assume that you are building an application to deliver fresh produce. Different produce has different shelf lives, and hence, what the app can deliver to different locations will change based on the delivery address. This leads to a problem. To decide what product to show to the user, we need to calculate the time to deliver against all items in the catalog. An obvious implementation of this would be very expensive.
As I have seen several times in real life, a naive UX that sidesteps the above problem would show everything to the user, and when the user selects an item, checks if it can be delivered, and apologize if it can’t be delivered. However, this is a terrible UX because a lot of things the user sees will not be available, frustrating the user. There is nothing crueler than raising expectations and denying.
However, if you think from the user’s perspective and look hard for a way to solve this problem, you will find a solution. For example, in this case, you can break the delivery areas into zones, pre-calculate what items can be delivered to what zones, and when the user comes, we can find what zone he is in and then use the precalculated items for the zones. You will only find the second option if you look for a solution. So the key is to look from the user’s perspective and keep asking how we can build the best experience, thus ignoring technical challenges when formulating the problem.
Looking from the user’s perspective reduces the probability of big surprises later, which usually means it reduces the risk of cluttered code, where cluttered code is often created when we are forced to make a change we are not planning to incorporate.
Concept 2: Orthogonal Design
At WSO2, we use the concept called orthogonal design to find the best architecture. ( It is not my idea; Sanjiva always pushed for this.)
Typically, we start the design with a few use cases at hand. The first step of orthogonal design is to find other related use cases. For example, with the produce delivery app, this may be
- Users want to search by different criteria, like price.
- Users want to be notified when an item is available.
- He might want to cancel the item.
- He may need to notify the courier, etc.
- She might need the delivery at a particular time.
This step anticipates future requests, which will reduce surprises later, which often leads to costly, cluttered code. The next step (Second) is to extract fundamental concepts that will solve the problem.
Those concepts should be orthogonal, a.k.a. independent. Orthogonal concepts minimize duplication. Furthermore, by combining those fundamental aspects, we can implement any use case we need. This is as if, using two orthogonal vectors called X and Y, we can represent any two-dimensional vector because X and Y are fundamental concepts. Our chosen orthogonal aspects should be able to handle all the use cases we have identified. For example, orthogonal concepts in the produce application include search, shopping cart, checkout, and delivery.
A great example of orthogonal design in (building) architecture is the Sydney Opera House. Its complex dome is cut from a single sphere. By doing that, the cost of specifying and analyzing that design is greatly reduced, as a simple concept can explain the whole system.
Among real-world software examples, a great example of an orthogonal design is Kubernetes (k8s). Using a few concepts like pods, containers, tasks, controllers, etc., users can build any system they need. They can be combined in endless ways.
Finding such a design is hard, often requiring several trials and errors. Yet, it is a great goal to aim for. From the coding point of view, orthogonal design naturally leads to components that do not interact with each other much but can be combined easily, which means we will get clean, loosely coupled components, which in turn is good code.
Concept 3: Tradeoff Orthogonal Design against “Crawl, walk, run”
We (WSO2) use a guiding principle in everything: crawl, walk, and run to avoid analysis paralysis. We are often doomed to do nothing if we refuse to act without complete information. Even when we find the information, it takes too long.
However, “crawl, walk, run” often runs against the orthogonal design, as the latter needs deep thinking and trial and error.
Finding this balance is key to being a great architect. There are three things you can do to find the balance.
First, we can only think deeply about some things. So, what things should we think deeply about? We need to think deeply about parts that are hard to change and other parts we can adjust as needed. This is a natural extension of Jeff Bezos’s “Reversible and Irreversible Decisions” to software architecture. The following are some examples of things that are hard to change:
- APIs — hard to change even across full rewrites of the system
- Choice of database and database schema — hard to change even across rewrites of the system due to customer data
- Frameworks we use to build the system
- Cloud or hardware platform we build on top, and the level of close integration with platform services
- Integration points — external APIs, can it scale up? Are there alternatives?
- The scale of the system ( be cautious; you are not Google)
- The users and the security model
- The unique capabilities of the system
Second, we should start thinking about the design as early as possible. That will give us time to think about the design from many perspectives, get views from many, disagree, and hash out differences.
Third, having spent enough time in orthogonal architecture, we must move on to the implementation phase, accepting open questions and trusting our judgment. When we implement, we implement only the parts that need to make progress and delay parts that are not critical to the use case. I call this “think deeply and implement slowly” in my Book, which lets us progress while giving us time to learn and adjust our understanding.
Conclusion
Let us recap.
Good architecture finds a clean separation of loose components, where related things go together, while the most used components are the most stable, reducing duplication. It will allow for future requirement changes. Furthermore, such architecture is easy to understand and explain, providing a clear language for communication.
Each of these traits makes the code simple, easy to understand, and easy to change.
On the other hand, when we see bad code written by competent engineers, it often signals that something in the architecture is wrong. Also, if you do not see a good way to write the code, it is often a signal of a flawed architecture. We need to raise and discuss those concerns, and leadership should listen and invest in fixing them if required.
Then, we discussed three concepts that can help us find a good architecture.
First is thinking from the user’s perspective. The best code is code that is easiest to write, maintain, and understand. To find such code, we must find the design that provides the best experience to the end user, both in the short and long term. If we write code for short-term needs, we will be forced to make the code complex, unmanageable, and cluttered when inevitable changes come.
The second is orthogonal design, which is a design philosophy we used within WSO2, trying to get to the best architecture. The main idea is to find a set of independent concepts that can be combined to solve the current problem ( and its potential future requirements).
The third is to understand that orthogonal design conflicts with another principle, which is crawl, walk, run, which means start small and build up. The tradeoff between the two is necessary; one way to achieve this tradeoff is to think deeply about things that are hard to change while delaying the details about things that we can change easily.
In most cases, good architecture and code go with each other, affecting the other. Code gives us a great test of the smell of architecture. It is often a sign of an architectural flaw if you are forced to write bad code despite an honest effort to improve. Follow that lead, believe you can find the answer, and look hard; you will end up with good architecture and code.
You can find the full talk below:
If you enjoyed this post, you might also like my new Book: Software Architecture and Decision-Making. You can find more examples from the book.

Get the Book, or find more details from the Blog.
Please note that as an Amazon Associate, I earn from qualifying purchases.