How to Pick the Right Software Architecture for a Product From the Start
An approach to designing future-proof software architectures using non-functional requirements analysis and product quality attributes.
When building a new software product, it is crucial to pick the optimal architecture as early as possible. The right architecture supports the engineering team in building the product. Poor architecture choices hinder development and may lead to expensive rework in the future.
In this article I’d like to share an approach to designing a future-proof software architecture that has helped me to come up with simpler designs, ship new functionality faster, and avoid big architecture changes as the products evolved.
What I particularly like about that approach is that it scales up and down depending on the project size (product, service, or just a large feature), and that it can be as rigorous or as informal as appropriate for the engineering culture in an organisation.
Let’s briefly look at two ways how an engineering team may come up with a software architecture for their product: ad hoc vs planned and then talk about the approach itself and look at an example how it can be used.
Ad Hoc Architecture vs Planned Architecture
Ad hoc architecture emerges when the dev team just hacks ahead and developers are building the product according to their own assumptions, opinions, and preferences without much of a plan.
This is alright for small projects with a short life and no plans for further evolution, such as marketing campaign websites.
Planned architecture is the opposite. It is the product of the design process ran by the engineering team.
Planned architecture can be driven:
- either by subjective factors —personal experience, preferences and assumptions of the engineers in the team;
- or by objective factors — the organisation goals and the conditions of the environment where the product is going to be used.
The issue with the former case is that it doesn't guarantee that the team would come up with the architecture optimised for achieving the actual organisation goals. The reason is that developers would be proposing technologies and design patterns based on their opinions but there won’t be a shared set of criteria for them to evaluate available options and pick the most appropriate ones.
On the contrary, the latter case encourages the team to first agree on a shared set of criteria derived from the analysis of the organisation goals and the environment conditions.
In addition, it reduces the impact of subjective factors on the outcome and encourages engineers to go beyond their current experience and knowledge to look for the optimal technical solutions.
As a result, the team increases their chance of designing the architecture optimised for achieving the organisation goals and reduces their chance of having to do expensive architecture changes in the future.
An Approach to Architecture Design Based on Non-Functional Requirements Analysis
The approach to architecture design that has been working well for me consists of three steps:
- Identification and analysis of the non-functional requirements for the product.
- Selection of the relevant software quality attributes for the product and setting their targets.
- Selecting technologies and design patterns that would meet the targets for the relevant quality attributes to satisfy the non-functional requirements.
For those who are not familiar with the terminology, non-functional requirements are the criteria for evaluating how a software system should perform rather than what it should do. An example would be a requirement for a web API endpoint response time to be under 200ms.
When we say that a software product should be “secure”, “highly-available”, “portable”, “scalable” and so on, we are talking about its quality attributes.
In other words, a software product must have certain quality attributes to meet certain non-functional requirements.
Step 1. Identification and Analysis of Non-Functional Requirements
Product managers and business analysts rarely can provide engineers with a comprehensive list of non-functional requirements: it is difficult for many people to uncover their implicit expectations and assumptions. In addition, some non-functional requirements are related to purely technical aspects of the product and the Product team may be completely unaware of them.
As a result, the engineering team has to lead this process themselves.
Working with Product, Design and possibly Marketing, Support, Analytics, Legal and other departments, engineers need to walk through what the new product is meant to be doing to identify expectations, assumptions and requirements to how the new product should perform its functions.
Please note, that it doesn’t have to be a series of formal meetings. Sometimes a couple of quick chats with representatives of those departments is enough to clarify the details and move to the next stage.
Let’s look at an example where a team is tasked with developing a mobile app for streaming video content. This leaves a lot of room for interpretation.
After a conversation with the Product team and asking them questions like:
- How many customers would you expect in 6-12 months after launch?
- How many customers do you think will be using this at the same time?
- How long the videos are going to be?
- How bad that would be if the product would be down for an hour?
- What market are you planning to launch it in the first 12 months?
that may be expanded into something like “in 12 months after launch the product must scalable enough to stream video content to 0.5–1M concurrent users 24/7 all over the world”. That would encourage developers to consider only those design patterns and technologies that allow to develop a highly-scalable, highly-available, and fault-tolerant architecture.
Furthermore, Marketing may add to that that the product actually needs to be localisable since it is going to be available world-wide.
In addition to that, Legal team may require the product to be compliant with the laws of various jurisdictions around the world regarding what video content may and may not be available to various age groups.
As the outcome of this step, we have identified five quality attributes potentially relevant for the architecture of the product: scalability, availability, fault-tolerance, localisability, and compliance.
As mentioned above, there is no need to clarify every possible aspect of the future product behaviour at this stage. Engineers should be pragmatic: as soon as the essential non-functional requirements have been identified and analysed and the team has reached the point of diminishing returns, it may be the time to move to the next step.
Step 2. Selection of Relevant Quality Attributes
Having the list of quality attributes from the previous step, developers can select which ones the product architecture should be optimised for and define their targets.
Developers may also want to know what quality attributes their product architecture doesn’t have to be optimised for. Then they would know which attributes to sacrifice to meet the important requirements. Candidates for such irrelevant attributes either have no related non-functional requirements or have ones that are easy to meet.
For the example above, the engineering team may decide to optimise for:
- scalability, so the product could handle 0.5–1M concurrent users,
- availability, so it could have, let’s say, 99.995% uptime,
- localisability, so its content, support and marketing materials could be available in the most popular 10 (20, 30, etc.) languages in the world.
As for the remaining two quality attributes, the team may decide that there is no need to optimise the architecture for them:
- fault-tolerance, may be achieved together with availability,
- compliance, as content restrictions per geographic location don’t have significant impact on technologies and architecture patterns that are going to be used in the product.
It is convenient for developers to have a comprehensive list of possible quality attributes to go through and check whether each attribute has any related non-functional requirements for the product. Having such a list greatly simplifies steps 1 and 2.
I use the list of 31 quality attributes grouped into eight characteristics that are defined in the international standard ISO/IEC 25010 in the section that introduces a software product quality model:
- functional suitability: functional completeness, functional correctness, functional appropriateness;
- performance efficiency: time behaviour, resource utilisation, capacity;
- reliability: maturity, availability, fault tolerance, recoverability;
- usability: appropriateness, recognisability, learnability, operability, user error protection, user interface aesthetics, accessibility;
- security: confidentiality, integrity, non-repudiation, accountability, authenticity;
- compatibility: co-existence, interoperability;
- maintainability: modularity, reusability, analysability, modifiability, testability;
- portability: adaptability, installability, replaceability.
You can find their definitions either in the free fragment of the official standard, or here. Even though they sound quite abstract, in practice experienced developers quickly pick up what they mean and start using them.
There are other quality attributes. This Wikipedia article has a few examples. Also, some projects, organisations or business domains may need unique quality attributes.
As in the previous step, there is no need to capture every possible quality attribute. Only the relevant ones matter. Four to seven relevant quality attributes may be sufficient to efficiently generate and evaluate design options.
Step 3. Making Architecture Decisions
Having the list of relevant quality attributes and their targets, the engineering team can start generating architecture options. Let’s take a look at some obvious high-level architecture options that may be proposed for the three quality attributes identified in the example above.
- Scalability: the product can use a CDN to deliver at least static content faster to its users; the videos can be stored in a cloud storage possibly in multiple regions globally to make streaming faster; application servers also may need to be deployed to multiple regions; the databases should to be highly scalable and have to support replication to other regions to avoid high latency when called from the app servers located on the opposite side of the world; part of the backend functionality may be implemented with cloud functions.
- Availability: the product may need to be deployed to several availability zones within each region; deployment strategies should allow deployment without downtime; each region may have to have the copy of all data to be able to run independently.
- Localisability: the product must support integration with multiple payment methods popular in different countries; it must be integrated with a system for managing UI translations; each content item needs to support multiple localised versions; the product needs to reliably identify the location of a user to know which content is and is not available there.
Having identified the available options, the team can finally select those ones that should achieve the targets for the relevant quality attributes in the most efficient way.
Even though, that doesn’t guarantee that the product architecture won’t have to change in the future, that still reduces the chance that the changes will be significant and gives the engineers enough information to confidently move forward.
To sum up, having identified non-functional requirements and relevant quality attributes, a product engineering team can generate architecture options and choose the most efficient ones based on analysis rather than on personal preferences and opinions.
That should help them to achieve the organisation goals faster and avoid significant architecture changes in the future.
Please note, that this approach is not meant to result in Big Design Up Front or waterfall model. The goal here is to quickly identify the objective criteria that the architecture should be meeting in the foreseeable future, not to lock the design down and substitute real product development with drawing diagrams and writing documents.
The approach also scales up and down pretty well, so it can be as rigorous or as informal as the specific product requires it to be. I’ve used it when designing new products, new systems and even just large feature sets.
I hope you have found this article useful.