Flexibility?—?a Software Architecture Principle
An important capability for businesses to thrive and grow, and even survive, is the ability to react and adapt to change. This relates to customer needs, expansion into new markets, or utilizing the latest and greatest in technology. The software systems that businesses rely upon must reflect this.
A flexible software architecture is essential in this regard. Here we discuss some trade-offs that must be considered and give a high-level view of some suitable architectural styles.
The Principles of Good Architecture
Already in ancient times, the principles of well-designed architecture were known and described, e.g. by the Roman architect Vitruvius in ‘De architectura’; “firmitas, utilitas, venustas”. The architecture should be stable, useful, and beautiful.
For software architecture, the principles of stability and usefulness represent system resiliency and life cycle, and they are closely related to how the architecture withstands and enables changes over time. The principle of beauty arises when the architecture is in harmony with its surroundings, and with no unnecessary clutter and complexity.
A flexible software architecture is able to adapt to changes in both environment and usability requirements without encompassing structural changes. It is also free of rigid structures that otherwise would obstruct functional evolution and growth.
A Flexible Architecture is Enabling
All systems have a structure and a composition, which together defines the architecture. Composition here relates to the included software components and structure how these components communicate and interact with each other. The architecture is either deliberate and known, or accidental and grown over time.
A systematic and engineered approach will yield a more flexible architecture than allowing the architecture to evolve organically without any overall design and consideration for how to enable and withstand change. However, the balance between these and the risk of over-engineering upfront will always be an issue to consider.
Designing a flexible architecture is also well adapted to agile development methodologies, where decisions regarding the various system components can be delayed until necessary. A flexible architecture allows interfaces that integrates with both current and future components. This also mitigates risk, as the most complex system components can be prioritized early. Thus, not all requirements need to be set in stone before development can start. Note however, that it is essential to ensure that the architectural viewpoint is further away than only the next iteration. This is to avoid implicitly growing an organic system where changes will be expensive.
Likewise, a flexible architecture is a key enabler for DevOps and continuous deployment. Being able to upgrade or customize only the necessary parts of a system simplifies and optimizes the deployment pipelines. Typically, there will be several development and deployment pipelines, and streamlining these will save time and effort in the long run. The best solutions mitigate risks related to deployment by implementing a flexible core architecture that allows seamless extensions and upgrades.
Architecture Trade-offs
All architectures have implicit or explicit trade-offs between flexibility, complexity, and performance. In the triangle of architecture design considerations, focusing on any two of these attributes will likely have a negative impact on the third.
Designing a highly flexible and performant architecture will give a complex architecture, an architecture with high performance and low complexity will be rigid and inflexible, and a complex and flexible architecture will most likely not yield high performance.
Domain knowledge and enough time is the foundation for tailor-making a system architecture with the right amount of flexibility where needed. Other parts of the system can then fulfill requirements such as high performance and low complexity as needed. The performant components often encapsulate core domain functionality and are by design complex but will often also require less flexibility and have a closed structure.
Design and implementation choices are often made based on false assumptions, such as ease of accessibility, ill-perceived benefits that will shorten the development time, or lack of a long-term vision. This will often result in an inflexible architecture that limits the possibilities for future adaptation and growth. Implementing a solution with only the current requirements in mind will restrict the possible future expansion space without considerable re-work.
Configuration or Customization
Flexibility can be facilitated by either configuration and customization. However, there are some significant differences between these two approaches. Where configuration is usually a deployment process, customization is typically a larger endeavor that requires technical insight. Configuration could include specifying an installation path, or other metadata that changes technical parameters or the look and feel of a system. Customization usually requires technical tasks and development effort, such as creating a new plugin, adapting an API connector, or adding a new integration interface.
Using toggles or feature flags is a form of configuration that is gaining popularity, allowing gradual roll-out of features to specific users or for deployment purposes. This somewhat bridges the gap between configuration and customization but beware the dangers of increased maintenance and testing efforts. A well-functioning deployment pipeline is crucial to make this feasible.
A solution based mainly on configuration will typically not yield the flexibility to adapt over time. Likewise, any customization should not require changes to the core architecture, as this capability should rather be built-in. Customization must be enabled from the start, as it will require a lot of development effort to add retroactively (if at all possible). Systems that are inflexible will also often invite quick fixes that will accumulate technical debt and clutter the code base. Over time, this will lead to increased maintenance costs and reduced implementation velocity.
Some Final Remarks
There are certainly scenarios where upfront focus on architectural flexibility is of less importance. However, have in mind that even a standalone application that was never supposed to change, could suddenly be required to integrate with external systems or evolve with new functionality.
For most large-scale and business critical systems, spending effort on designing a good architecture is necessary to ensure they can enable change, achieve longevity, and look beautiful in all stages of their life cycles.