Beyond Defensive Programming: The Rise of Defensive Architecture

by Kris Hansen, Investor / Advisor

Defensive Architecture: Absorbing Change in Software Systems

Introduction Defensive architecture is the practice of designing software systems which are able to absorb change and adapt to shifting requirements and conditions. Having a defensive architecture doesn't mean that all of the potentially needed capabilities are built - it means they are expected and anticipated. It also generally does not take much more effort to design defensively, it just takes a bit more thinking and experience.

Where defensive programming is about potential issues are avoided through validation, testing, assertions and other best practices defensive architecture uses a similar mindset at the systems architecture scale.

The Need for Defensive Architecture Building brittle systems is expensive. It is the 'fast fashion' version of software systems design. You build something, things change, you throw it away and build again. It is more efficient to build systems which flex as things change and adapt to the new realities without having to be completely rebuilt.

One constant is change. Yet this is often overlooked when designing software systems. The result is that systems are built to solve the problems of today, but not necessarily the problems of tomorrow. This leads to systems that are difficult to maintain, update, and scale.

Key Principles of Defensive Architecture

a. Anticipation of Change When looking at a set of requirements for a solution design, consider the underlying assumptions. Which of these are solid, which of these are likely to change? How can the design be made to accommodate these changes? This is at the core of defensive architecture. Question and probe these core assumptions and evaluate the impact of change should they become invalid.

Use Domain Driven Design to carve the architecture up into bounded contexts. This allows for the creation of a modular architecture where each module is responsible for a specific domain. This makes it easier to consider the underlying assumptions in each domain and potential changes that may occur.

b. Abstraction Tightly couple only what you know and trust, loosely couple everything else. This is a core principle of defensive architecture. The more you can abstract away the details of a system, the more you can change the underlying implementation without impacting the rest of the system. This is the core of the 'Dependency Inversion Principle' and 'Interface Segregation Principle' of SOLID design.

Consider the interfaces, integration points and third party systems in the design. What happens when the singular becomes plural or the opposite? How can the design be made to accommodate these changes? This is where the use of abstraction and loose coupling can help.

c. Modularity Design for fungability. The ability to swap out one module for another without impacting the rest of the system. This is the core of the 'Open/Closed Principle' of SOLID design.

If you do this well, when the world changes you can reassemble your modules and the system will adapt to the new reality. This is the core of the 'Dependency Inversion Principle' of SOLID design.

d. Flexibility Consider flexibility of use, scale, and deployment. What happens if usage becomes 1000x in a day? What happens if we need to deploy to a region which requires systems residency? Flexibility is an asset and should be celebrated in the design.

Examples here include the use of appropriately scoped services, the use of containers and the use of tooling for deployment and orchestration.

Real-world Examples At one point in my career I designed a Java J2EE application (it was 2003) which was a hotel booking engine for group travel. I was in Hawaii and we were focused on group travel for the Hawaiian Islands. I tested this core assumption several times - "We will never need to book a hotel outside of Hawaii" I was told. "Don't worry about it" I was assured. "Will never happen" was the sentiment.

Fast forward to 2005 after launch, a major Mexican chain asked us to expand to Mexico. Cancun had just been hit by Hurricane Wilma and had done extensive damage. Hotels needed to book group travel more than ever and they implored us to extend our system to be able to handle Mexico. I sat down and looked at the data model.

The data model really started with island - if you are booking a trip to Hawaii this is really the first question that comes up: "which island would you like to visit?" and under island we had a concept of region. Under region we had property and then we went into different types of inventory. This was a very simple data model and it worked well for Hawaii. To adapt it to Mexico I had to take a step back and abstract the concept of island and region. I had to make the data model more generic and flexible.

The result was fine, it worked well but not being ready for this caused us to delay the launch of the Mexico product by 3 months. This was a big deal at the time and it was a lesson learned. I had not anticipated the change and I had not designed defensively.

Had we just zoomed out from the Hawaii specific architecture and considered "what if we need to book hotels in other places?" we would have been able to take on the new business in stride. This is the core of defensive architecture.

Challenges in Implementing Defensive Architecture There is a risk when designing defensively that you will over-engineer the solution. This is a real risk and it is important to balance the need for flexibility with the need for simplicity. The goal is to design a system that is flexible enough to absorb change, but not so flexible that it is overly complex and difficult to maintain. One way to mitigate this risk is to consider the probability of the change occurring and the cost of having the risk covered vs leaving it open. Even documenting a potential change and how it would be handled is a form of defensive architecture.

Understanding where your solution will need to be adaptable and flexible is key. This is not about presumptive building, but rather about anticipating change and designing for it.

Architects love abstraction and it's possible to overdo it. Keeping abstraction simple and focused is key. The goal is to abstract away the details of a system, not to abstract away the system itself. This is a common mistake and it can lead to overly complex systems that are difficult to maintain.

The Role of Tools and Technologies To think about how tools and technologies have reshaped the way we design software systems, consider the following examples:

a. Cloud Computing There was a time that when you designed a system as an architect you would also need to order the equipment and rack and stack it. Getting the sizing right took a great deal of consideration - will we have ten thousand users logging in on Monday or will it be ten? Ordering too much capacity would be expensive and embarrassing - ordering too little would be fatal. Using cloud native techniques like auto scaling eliminates this concern and allows for a more flexible and cost effective approach to capacity planning.

Understanding what cloud native means and how to design for it is a key skill for architects today. This is a core part of defensive architecture.

b. Infrastructure as Code Using Terraform and other tools to define infrastructure as code allows for the creation of repeatable, consistent and flexible infrastructure. Adapting, scaling and evolving infrastructure patterns is much easier when it is defined as code. This is a core part of defensive architecture.

c. Events Generating events for things you care about and reacting to events from other systems is a way of capturing information you can mine later. This allows for the creation of loosely coupled systems that can adapt to change and each other and the capture of data for future insights. This is a core part of defensive architecture.

Conclusion

In the dynamic landscape of software development, where change is the only constant, the principles of defensive architecture emerge as a beacon for sustainable and adaptable design. By anticipating potential shifts and embedding flexibility at the core of our systems, we not only ensure longevity but also foster innovation. As we've journeyed through the intricacies of defensive architecture, from its foundational principles to real-world applications, it becomes evident that this approach is not just about safeguarding against the unknown. It's about embracing change, harnessing it, and turning it into an asset. As developers and architects, our mission is to craft solutions that we can be proud of - solutions that are not only resilient but also future-ready. And defensive architecture is the key to achieving this goal.

Relevant References

a. Books

b. Articles

c. Research Papers

Tell us about your project

Our offices

  • Toronto
    Toronto, Ontario
    M8X 1W3, Canada