If you ask a group of developers what kind of architecture works for them, you will receive many different responses reflecting each person’s experience. Architecture is a term used to define many structures, but we often hear about it within the construction domain. The parallel here extends beyond the use of the noun itself to the common fact that, as we will discuss in Chapter 5, Design versus Architecture, structure, and design, whether for software or a building, are the results of the requirements of different stakeholders.
Explaining precisely what software architecture is or does is hard. Still, luckily, an at-hand comparison that gives us some perspective on the impact of an architectural decision is building architecture. Just think about how hard it is to change the architecture of a finished construction. The same goes for software. Even if the “damage” is not as visible as it would be when tearing a house apart and building it again, the implications are the same: time, finances, and an impact on different areas and stakeholders.
Can we define software architecture?
The debate always comes down to one question: “What is software architecture?” Although it is a highly discussed and important matter, there is no definitive and generally applicable definition. While trying to shape a meaning as thoroughly as possible, I have found many interpretations and explanations, many confusing and hard to tackle, while others were simple and to the point. For me, the subject of architecture started to make sense when I started working on a project as a full-stack developer. As I had to go through different layers in the application (working from the database to the client), I had to respect the rules that this architecture imposed. Then, step by step, by becoming more curious about different technologies and approaches in other projects, I discovered that this is an exciting and essential matter that I had to explore fully. So, to gain some clarity, let’s take the first step by discussing some existing definitions and perspectives, and then we will try to shape a report.
According to Ralph Johnson, “architecture is about the important stuff. Whatever that is.” This is a very abstract definition but may be a good representation of how important the context of the project is when determining the architecture.
Another exciting perspective I found from Eoin Woods states that “software architecture is the set of design decisions that, if made incorrectly, may cause your project to be canceled.” Though radical, Eoin Woods’ definition exemplifies how important it is to always ensure we have explicit requirements when making decisions about a system’s structure.
The list of examples can go on; however, my conclusion was that many seemed pretty abstract and ambiguous for a developer, especially one in the early years of their career. From “the process of defining a structured solution that meets all of the technical and operational requirements” to “the set of structures needed to reason about the system, which comprises software elements, relations among them, and properties of both,” each of them yet again simply underline my idea that defining architecture would create constraints and possibly many debates. What if software architecture is a set of characteristics to pay attention to while expressing a direction on which we build step by step?
From my experience, the simplest way to think about software architecture, no matter the level of experience, is this:
Software architecture is a set of characteristics that combine technical and business requirements (at the project and organization levels). You will be able to define a skeleton to help you build a performant, secure, and easy-to-extend system (or application).
So, it is a structure upon which we build step by step, one component after another, creating the relationships between them. Consider that all this is being realized while considering the bigger picture of the product.
Even though we talk about components and interactions, we must be careful with the level of abstraction. We can quickly get too absorbed in the implementation details and lose our view from where we are to where we want to go within our application development map. There is a fine line between software development and software architecture. In my opinion, and from the experiences I’ve had within the teams I’ve had the chance to interact with, I can say that it is obvious when an architect is too focused on abstractions and has no idea about what is happening in the application. At the same time, I can see when a developer lacks awareness of the software architecture.
We will debate an architect’s involvement in the development process in Chapter 6, Types of Architects and Their Focus. That’s why right now, I would like us to focus a bit more on how software architecture knowledge can help us write better code.
Among all the questions we address when talking about software architecture, two can significantly help in the development process: “What are the main components the application can be divided into?” and “How do these components share responsibility?”
Understanding how the system is divided into components and how they interact brings much greater clarity to the process of defining the responsibilities of each, seeing where best practices integrate, bringing value to reduce the interdependence of different parts of the code, and easing the process of creating unit tests. At the same time, we have a huge advantage because it will become easier to identify frequently used components, create a common ground, and apply the correct patterns.
It is easier to respect best practices and choose suitable design patterns and abstractions when we understand what we are building and why certain decisions were made at the architecture level. Everything works together beautifully and we find it easier to build a quality-oriented product (a product that respects the quality attributes set at the birth of the product).
Understanding that my team needs an overview of the application’s architecture was a stepping point in reconsidering how certain pieces of the system interact. This is extremely useful, mainly when we discuss complex team organization with dedicated subteams. It’s hard to keep track of how all the components interact, define precise requirements (especially for the teams we are dependent on), and create pieces of components that fit together when there is no overview.
All in all, if the structure of the system is built the right way, we will have a successful product. If not, at some point, we will have to start over or rewrite essential parts.
When I’m referring to the base, I particularly consider two directions:
- Architecture is the foundation of the application. We already discussed this; it is as though we were building a house. It has to allow you to create the walls, the roof later, and other details that you might want along the way or as trends evolve and change, but above all, it should give stability and direction for how the rest will grow at a structural level. The architecture is a system’s base and must be carefully thought through to bypass any significant design changes and code refactoring later.
- On the other side, the development team is also the base. We can have the best architecture plan, but if the ones building it step by step don’t know what they’re supposed to be doing or don’t have an overview to support this, we will come into a lot of trouble.
A slight improvement at the code level to support the system’s architecture or design will have a considerable impact over time.
An important aspect to consider when it comes to architecture is the fact that it has to be shaped around requirements of significant impact. Good architecture is not one with fixed definitions and limits but one in which technical needs and business requirements are aligned and work well together. I was part of a team where the development team was composed of great developers, but the requirements were dramatically changing, and this made it hard to have continuity in the product life cycle. We had to constantly change the structure, rewrite parts, and had a lot of technical debt to take care of.
Another critical aspect is that the architecture should focus on the high-level structure, independent of the implementation details. Of course, implementation details are being decided along the way and are not to be ignored, but they shouldn’t impact the structure; instead, they should build upon the structure.
From the start, it is essential to know that we have to be as pragmatic as possible and consider as many scenarios as we can when deciding the architectural shape. Since we are discussing strategies and edge cases, an excellent way of being as precise as possible about these is by exploring and analyzing the future steps and plans with the stakeholders. Stakeholders are critical and of great help in building a valuable product by providing feedback. In the Understanding the role of stakeholders section of this chapter, we will discuss the main stakeholders and why they impact the application.
So, we agreed that it is hard to define architecture, and we saw that what needs attention is determined by the context of the product you are building. At the same time, even though we can provide a clear definition, we can still learn best from the experience of others and look at some points that, in time, show themselves to be of great value.
Requirements
Often, we might feel as though architecture is all about technical requirements and concerns, but the actual value of a project is given by the functional requirements and the validation of stakeholders. You can have the technically best product with the best architecture and components, but if no one uses it, all that effort is in vain. One of the best skills an architect can build is balancing the functional and non-functional requirements.
Working closely with the various stakeholders as possible helps us correctly identify, understand, and refine our requirements. Ultimately, the end software architecture solution will be defined by those requirements. This is part of the context we have already mentioned. Suppose the system architecture is defined too much by the business context and we make a choice that cannot meet all requirements. In that case, we will end up with anything but a solid architecture that can later be extended or even implemented.
Maintainability and extensibility
System maintainability is the subject of many books but is still very subjectively defined in many cases. This property determines how easily you can change, refactor, extend, or repair your software. Software maintenance is an ongoing process, so it’s essential to be prepared to use the least amount of resources possible to make this possible.
Your architecture should be flexible enough to allow you to work with requirements later that you were not aware of in the beginning. As an architect, even if you don’t have 100% of the details in place, to be safe, you have to at least think in terms of the evolution of your system. Predict possible risks and find ways to avoid them.
But I have a disclaimer here, which also applies in the development process: don’t over-engineer scenarios if you don’t know whether they will ever even happen. When I mention that we need to prepare for change, I refer to respecting best practices, making the code easy to work with, and extending and modifying, rather than predicting what might happen in the future. Here, we can turn to the You Ain’t Gonna Need It (YAGNI) concept, which represents incrementally creating a simple system design. When you find yourself saying, “Maybe the client, user, or product owner will want this functionality in the future, so I will also code for that scenario,” stop for a second, and acknowledge that you are overthinking and that you’re about to code for a scenario that will never happen. Instead, evaluate how testable, clean, and extendable your code is and move on.
Response to change
As business evolves, so does software; it is a natural consequence. Technology evolves, business perspectives change, technical debt appears, requirements are reconsidered, team structures change, and the need for a more performant technology stack arises. What we need is to think in terms of building tolerance to change in the system. Observe where and what kind of changes appear through iterations. You can expect these changes and be prepared to meet them. Why so much analysis? Because every time we assume a final decision, we are also saying no to other details we might receive along the way, which will help us make a more informed decision. Once you have committed to a way of shaping your architecture, you have already decided how the next period of time will be spent and how the product and the architectural structure will evolve.
James Clear aptly defines the importance of decision-making by stating that “when you say no, you are only saying no to one option. When you say yes, you are saying no to every other option. No is a decision. Yes, it is a responsibility. Be careful what (and who) you say yes to. It will shape your day, career, family, life.” This is also valid when it comes to software development decisions.
Take care of how the pieces of the system interact, reduce complexity and independence as much as possible, and identify the essential components and how they are being implemented. This way, even if you have to change something along the way, the changes will be isolated, and a complete architectural remake can be avoided.
Practicality
Defining architecture is not just a preliminary stage; it is a continuous process. In this case, the architect is responsible for having a clear perspective on how the system evolves and if the decisions made at the beginning are still valid. At the same time, the software architect must ensure that everyone on board understands how the system works. Whether we like it or not, systems become more complex as they evolve. This complexity can sometimes be hard to observe or tackle early on. When we start losing our understanding of what we are building, we end up with a huge problem. As the system becomes harder to understand, it becomes tedious to reuse some parts, stick to the design decisions, maintain it, or extend it. As the team structure changes, the learning curve for new members increases.
Looking back at the history of software architecture, we notice a shift in creating this timeline of decisions. In the beginning, architectural decisions were, without exception, made in the early development stage, seen as important and hard-to-change decisions. Later, with the rise of agile and the philosophy of working with iterations and being open to change, the approach changed, and the conclusion reached was that the process of shaping a system should be smooth and in sync with change. So, the emphasis was placed on how open to change, extensible, and maintainable the system was in reality.
Quality attributes
Let’s say we defined and agreed upon requirements with the stakeholders; so, what’s next? Another significant step that needs to be done as early as possible in the life cycle of a product is considering quality attributes. You know you have explicit quality attributes when they shine through the application. You don’t have to wait for it to be developed or deployed; the architecture should speak for itself. By defining quality attributes, we become more particular about whether we can meet all the requirements or not. For example, by thinking about performance at every step of extending the system, we end up with a performant application because, for example, we will consider what change detectors we have from the perspective of a frontend developer, in the client and where, how many times we load a page, or how many calls an API can handle. A quality attribute guides us. Later, it might become complex and time-consuming to make impactful decisions and changes in these areas.
The product quality model defined in ISO/IEC 25010 comprises the eight quality characteristics shown in the following figure:
Figure 1.1– Quality model (source: https://iso25000.com/)
Next, we will discuss how architecture is more than technical decisions and how considering the context in which we build the product will help us make better decisions.