January 27, 2017
The post was first published in Today Software Magazine, no. 55
This article aims to answer a set of core questions about software architecture, providing answers that come from modern software architecture thinking. Its inspiration came from:
- conversations with Rebecca Wirfs-Brock and Simon Brown
- architecting the eventrix.co product, running “Architectural Katas”
- countless conversations with architects and developers at international conferences
- conversations with participants to architecture workshops
The article is structured in a Q&A format, allowing for easier reading. Feel free to skip to the questions you find more interesting, and to read it in any order you like. It’s best to start with the first answer since it impacts the understanding of the whole article. Happy reading!
What is software architecture?
There is no clear definition of software architecture. Take for example Wikipedia:
“Software architecture refers to the fundamental structures of a software system, the discipline of creating such structures, and the documentation of these structures”
This definition has a few issues. What are the “fundamental structures” of a software system? What are the “structures” of a software system?
We propose a better definition:
“Software architecture refers to making a set of strategic technical decisions related to a software product, documenting them and ensuring their implementation”
Strategic decisions are those decisions who:
- affect more people
- for a longer period of time
- are difficult to change
Let’s see a few examples that clarify what strategic technical decisions mean:
Example 1: The choice of programming language for an application is a strategic decision. It will affect not only the whole development team but also the recruitment team. Once a programming language was chosen, it’s very likely it will be used for years. If the need to switch to another programming language arises, it will require a lot of money and effort to make the switch.*
Example 2: The choice of a framework is a strategic decision. The Same reasoning applies as above.
Example 3: The choice of a microservices architecture style (or REST, or monolith etc.) is a strategic decision. Once a few thousand lines of code have been written using the chosen architecture style, the cost of changing to another style will be high because it includes: training all team members, modifying or rewriting existing code to adapt it to the new architecture, maintaining different architecture styles for a while etc.
The decisions that are not strategic are called “tactical”. The obvious example is writing a private method in a class. While it might affect some people, it is very easy to change. Similarly, writing classes that are not exposed to other modules is a tactical decision. This article will use the term “software design” to designate these tactical decisions.
What is the goal of software architecture?
Why does someone need to make these decisions? Can’t they just happen? What is their goal?
You can certainly make strategic decisions on the go. The problem is that doing so might be very inefficient.
Let’s imagine you start an application with your favorite programming language. Soon, more people join the team, and each of them wants to write code in another programming language. It’s definitely possible, but it might lead to bugs, risks (if the Haskell programmer gets sick, who will change the Haskell code?) and additional costs. It’s an extreme example, but it shows that certain decisions affect more things than others. If you want to optimize the development, these decisions need to be made and implemented.
Therefore, the goals of software architecture are:
- Build a product that works within required quality constraints
- Reduce development costs
- Reduce risks
What is difficult in software architecture?
The most difficult thing is making strategic decisions (that is, decisions with a long-lasting impact) with incomplete information and knowing the context could change at any time.
We often joke saying that “architecture is where code meets the real life”. The code is neat because it gives the same result under the same conditions. Once the application is deployed, unexpected things start happening: servers fail, users find strange ways of using the application, spammers and hackers attack it, solutions that worked on paper and in controlled environments are inadequate in the production environment etc.
This is why architecting requires a specific type of mentality. One that is analytical enough to find the holes in the system before there’s a problem, but that at the same time is not stopped by incomplete information. One that can balance short term needs with long term possibilities, keeping the development time short for each feature while investing just enough into flexibility for the future. One that can present eloquently the technical choices to both technical and non-technical people.
It’s not really the technical part that’s difficult, although there’s a lot to learn there as well. The most difficult part of software architecture is the mindset.
What are the quality constraints of software architecture?
The quality constraints of software architecture differ from product to product. This is why they need to be clearly expressed and, whenever possible, quantified. Let’s see some examples:
- Correctness (important in core banking, embedded medical software etc.): identify the areas of the system that need to have as close to 0 bugs as possible
- Performance: time to response for requests to functionality X has to be under 1s
- Availability: the system will be available 99.9% of the time (so-called “one nine”)
- Scalability: the system will perform with the required performance even when the load is of 10.000 requests / second
- Robustness: a failure in one of the modules will not expand to the whole system (subset of availability)
- Resistance to errors: a miscalculation in a part of the system will be caught in another part of the system
Each of these quality constraints can lead to a different structure of the system and even to the need for specific technologies. For example, if the application needs to scale to millions of requests per second, most of them reads, a possible solution is to introduce a NoSQL read replica (if the application allows for eventual consistency).
How can the software architecture reduce development costs?
Architectural decisions can have a huge impact on development costs. Here are some examples:
- Use 3rd party services or libraries instead of developing certain features
- Follow consistent conventions throughout the code. For example all APIs use REST; never throw exceptions from module APIs etc.
- Reduce the cost of fixing developer mistakes through a clear automated testing policy
- Reduce the cost of investigating bugs through a clear logging and error handling policy
- Use specific database servers, HTTP servers etc.
A good heuristic is that decisions that makes things easier, clearer while keeping options open are the best at reducing development costs.
How can software architecture prevent risks?
The development of a product is subject to multiple types of risks. Here are a few examples:
- Availability of developers: developing in Haskell might be nice, but there are very few developers. Should we choose Java instead?
- Team risks: what if one-third of the team gets sick (or moves to another job)? Can the others continue with the same speed?
- Human errors: what if someone is very tired and introduces a serious bug? Can we catch it early?
- Security: what if an attacker tries to steal data from the system? Can we prevent it? Can we find out while they’re trying? What’s our response to a successful attack? (aka contingency plan)
- Lack of knowledge: we’ve never implemented something like this before
While some of these risks seem to be part of other people’s job (project man- ager, HR etc.), it’s important to realize that strategical technical decisions can affect other aspects of the business. Such effects have to be discussed with the appropriate roles from the business and decided together with them.
Security is a set of risks that impacts all the developers. Preventing these risks is a matter of training, guidelines, even specific design patterns to be used in the code. It also requires ensuring that they are actually used in development, through periodic reviews.
Lack of knowledge can be dealt with in a few ways: hiring a specialized consultant or investing in learning. In both cases, the result should be a working prototype that proves a certain feature can be implemented in a certain way. If
the problem requires a short learning time, using a spike (timeboxed period for creating a prototype, for example, 2 days) was proven to be a good approach.
What are the building blocks of software architecture?
When architecting software, what are the building blocks we use? The basic blocks are**:
Modules are parts of the system that can be replaced with other implementations. To allow replacement, they need to have clear protocols and contracts. Protocols define how modules communicate with each other, including the correct responses for different types of requests. You can imagine the software system like a living organism whose organs are the modules, and protocols are the circulatory system and the immune system.
In this context, a protocol is not the transport mechanism, although it can build on top of one (the most common transport protocol nowadays being HTTP, but countless other exist). Here are some examples of things that should be part of a protocol:
- Always call “initialize” before calling another method from the module
- All calls to the module are synchronous / asynchronous
- In case of error, the module returns error codes / throws exceptions / returns error messages
- A list of possible errors for each method call
This description points to the fact that a clear protocol goes hand in hand with a clear API for the module.
What about deployment?
There are a few ways to deploy a module:
- as a namespace inside a larger package (e.g. a Java war/jar, a .NET assembly, a C++ lib / dll etc.)
- as a runtime library
- as a plugin
- as a service
Each of the deployment options has advantages and disadvantages. Briefly, the more packages you have to deploy, the more complex the deployment, monitoring, debugging and operations become. The simplest way to deploy is a monolith, and the most complex is microservices.
Fortunately, there’s a way to keep your options open and introduce complexity late: start with a modular monolith and extract libraries, plugins or services only when needed.
How to communicate architecture?
Making the right decisions is not enough. The development team and the leadership team need to be aware of them, understand them and implement them.
The best ways to communicate these decisions to the development team are:
- code samples
- giving a presentation
- And, whichever you use, pairing with developers from time to time should not be optional
Some of the documents that should be part of the architecture but are very rarely created are:
- Design guidelines (specific patterns to use in specific contexts, for example, to prevent security issues or to speed up development)
- A logging and error management policy that applies consistently to all modules
- Testing policy (what is automated, what types of tests are used, code samples)
- Personas that clarify the users to clarify context for development decisions***
The leadership team ideally follows the “three amigos” pattern:
- One business person, who cares about the financial results of the product
- One product person, who cares about the user’s happiness
- One architect, who cares about the technological aspects
Presenting the decisions inside this team requires a different language and a different level of abstraction. While developers are focused on why and how to implement the decisions, the leaders care about the risks, the choices and the effects of the product as business.****
This article has tried to present the main ideas in modern software architecture thinking. Each of them deserves a longer discussion, and there are things we haven’t touched. We can only hope it helps to clarify and to separate fashionable ideas from the hard ideas that will still be useful 10 years from now. We will let you be the judge of that. We invite to share your remarks in a comment below!
*Some people will say that microservices solve this problem; actually following a multi- language microservices approach results into paying a continuous cost. It’s a trade-off, it does not avoid the core problem.
**Larger applications require additional building blocks: subsystems and systems
***The marketing team often has this list, but marketing rarely discusses with architects.
**** Same thing applies when you have separate testing & operations roles, teams or departments