We already know the business goals we want to achieve with our solution (don’t know? Check here). Now we need to focus on the product architecture. It is the technical lead’s task to meet the technical expectations of the project being executed.
But how do we recognize what to optimize our architecture for? And what exactly is architecture? Let’s discuss this today 😊
What is System Architecture?
When we think about system architecture, it is easy to fall into the trap of thinking only about structure. It seems that only the construction of the solution matters. We don’t see the connection between architecture and business. Then, we instinctively think of architecture as the responsibility of technical people.
Thinking structurally about product architecture is a mistake. Some decisions affect architecture, even if they are not typically structural. If we decide that different business processes should use the same information, then area A will affect area B, even if we initially didn’t want that. This complicates significant architectural changes.
That’s why Grady Booch’s words are more fit to describe architecture:
“Architecture represents the significant design decisions that shape a system, where significance is measured by the cost of change."
This shows that we should view architecture very broadly – as a set of factors that influence the final product. And what are these factors? They are Architectural Drivers.
What are Architectural Drivers?
When designing and implementing a new system or functionality, architectural drivers come into play. They are a set of aspects that create the framework for a given solution, steering it in a specific direction.
Drivers are determined individually for a given problem and form the foundation of software architecture.
We distinguish four categories of architectural drivers in system design:
- Main functionalities.
- Quality attributes.
- Constraints.
- Technical and architectural practices.
This division comes from “Software Architecture for Developers” by Simon Brown.
Main Functionalities
Every new project should start with analyzing and defining the main functionalities of the system being developed. To facilitate this process, you can answer the following universal questions:
- What needs to be delivered to achieve the main goals?
- For which roles do we need to deliver these functions?
- What is our main MVP?
- What larger functions are planned for later implementation?
For example, one of our e-commerce clients wanted to allow customers to log in to the system independently to make a purchase. As a Tech Lead, along with my team, I had to design and create an authentication system. This made us consider delivery options:
- Custom-built – time-consuming;
- Off-the-shelf – requiring much configuration;
- SaaS – dependent on an external provider.
Each solution has its pros and cons. And this will be the case with main functionalities – regardless of the task entrusted to us.
When defining main functionalities in architectural drivers, we must decide which option is most beneficial for the process, keeping in mind that there is no perfect mechanism: in every case, we will have to compromise.
Defining main functionalities is a fundamental step in every project. The success of the entire endeavor often depends on it. By focusing on it, we create a base for further actions.
Quality Attributes
After choosing the main functionalities of the designed system, we move to the next stage: defining quality attributes. It is crucial to select specific indicators and determine their measurements.
There can be many quality attributes defined, as shown in the graphic from Love Sharma’s article:
(Often quality attributes are also called architectural characteristics, although I do not like this name.)
At this stage of design, the greatest difficulty is the number of architectural drivers. The number of possibilities can be overwhelming, and individual attributes may exclude each other. To simplify the process, specific attributes should be chosen, and others discarded. This focuses us on the problem and narrows the scope.
For example, when designing an online store’s structure, we can optimize it for speed. This gains: performance, flexibility, and availability. However, if we choose not to optimize for speed, we might gain: recoverability, configurability, and auditability.
Each option is good. We must choose the one that is better for the project and fits the goals set at the beginning. Choosing certain indicators means sacrificing others. It is said you can’t have your cake and eat it too, but not in this case 😊.
Project Constraints
After defining the main functionalities and quality attributes, it’s time to look at the project constraints. These are limitations that will shape our design choices.
Constraints are an essential architectural driver in the design process. Especially if it seems like an easy stage, we should pause here longer and analyze carefully.
To facilitate this task, constraints and their definition can be divided into three parts:
- Technological – What technical requirements must we meet? What technologies are allowed? What shape does the team have?
- Organizational – What are the organization’s requirements? What are the organizational standards? What processes must we consider?
- Environmental – What external constraints do we face? What limitations are imposed by companies we collaborate with?
A common constraint is time. We must know the deadline to adapt our work accordingly. For example, delivering functionality after Black Friday will yield low profit. So, we need to focus on completing it before Friday and launching it by Thursday 😅.
Often, without considering project constraints, we won’t achieve goals even if we deliver new features. By understanding project constraints, we better understand the system we’re working on. We anticipate potential difficulties that may arise in later stages.
Specific Practices
Practices in architectural design are a set of principles that guide the team in developing a solution. They are specific to the project we are working on. Good and bad practices are defined independently and used to improve work standards and meet previously set goals.
Practices can be further divided into:
- Programming practices (focused on code)
- Architectural practices (focused on solution structure)
Practices affect the consistency of the created solution and help make decisions within the team. How to use them in your product? For example, this way:
Programming Practices:
- Language: We write in C#.
- Testing: We write unit tests, integration tests, and component tests.
- Code analysis: We use linters. Architectural Practices:
- Structure: We create modules for specific business areas.
- Statelessness: We create stateless components on the front end.
- Microservices: We don’t connect through the database but through exposed APIs.
Why Define Architectural Drivers?
Primarily, we get specific information on the direction in which the product should develop. The problems we solve at this stage are real, not hypothetical.
By defining the criteria for evaluating what we deliver, we know which elements to focus on during work. It also helps avoid over-engineering. We see that optimizing for performance is not crucial when we have only a few hundred customers.
Long-term, the team builds an understanding of different aspects of product work. Even product people start to understand that you can’t optimize for everything at once.
Interested in Architectural Drivers?
You can read more about architectural drivers and their use in design architecture in books like “Software Architecture for Developers” by Simon Brown, and “Fundamentals of Software Architecture”