Eliciting Capabilities Deterministically - Design by Constraint
In the multifaceted world of software architecture, there’s a tantalizing allure to architectural patterns promising given set of capabilities. We, as architects, gravitate towards these seemingly well-structured models, hoping that by employing ‘Pattern X,’ our deployed system will exhibit ‘Capabilities A, B, and C.’ Yet, reality often paints a different picture. Despite our sincerest intentions and methodical adherence to models, these expected capabilities can prove elusive, leaving us pondering where and how the divergence occurred. This divergence isn’t just a hiccup in the grand scheme of system design; it’s an essential reminder that architectural blueprints, foundational as they are, often lack completeness. This fundamental disconnect was one of the major drivers for me to begin to formalize the Tailor-Made Architecture Model. In this post we will embark on an exploration of this inconsistency, striving to understand why deterministic results from architectural patterns can sometimes be more of a mirage than a milestone.
This post is part of a series on Tailor-Made Software Architecture, a set of concepts, tools, models, and practices to improve fit and reduce uncertainty in the field of software architecture. Concepts are introduced sequentially and build upon one another. Think of this series as a serially published leanpub architecture book. If you find these ideas useful and want to dive deeper, join me for Next Level Software Architecture Training in Santa Clara, CA March 4-6th for an immersive, live, interactive, hands-on three-day software architecture masterclass.
In our previous post we walked through some exercises, ceremonies, and tools to extract architectural capabilities from the business, and the final artifact from that stage of the process is set of targets representing a fairly clear idea of what kind of capabilities we want out of the architecture of the system. We know what we’re aiming for, the next challenge is producing a design that exhibits capabilities closely aligned to our targets. Comparing these targets with the common architectural patterns reveal the first of two very important truths: 1. Rarely do the business problems we aim to address fit neatly into just one of the eight sizes.
The Ford/Richards scorecard gives a clear set of expectations for the capabilities of a given component pattern. As a refresher, here they are:
Overlaying the capability scores of the closest pattern against a set of targets will typically show an imperfect fit.
To lean into the tailoring metaphor for a moment, if this was a suit it would be a bit wide in the shoulders, tight in the body, long in the sleeves, and bunching in the back. As it is, this suit won’t be a great fit without some alterations.
But there is a second important truth about architecture that we must address. When I first showed the above scorecard in a Tailor-Made Architecture presentation, an attendee asked an insightful question: “Are these scores widely accepted in the industry?” I stopped and polled the audience asking “How many of you are currently using one of these patterns?” All the hands went up. I followed by saying “Take a moment and look at the capabilities of your pattern… Do these scores mirror your current experience?” Only one hand was raised. I have since repeated this survey many times at many conferences and meetups and the result was the same. The clear conclusion is our second important truth 2. The capabilities promised by architecture patterns are not absolutes.
So where, then, do these scores come from? They are the result of a lengthy analysis of a broad cross-section of projects that both Mark Richards and Neal Ford have worked on over the years and take into account experiences of their peers. I would add that the experience of Mark and Neal is reputable in creating these scores. I also think they can be useful as a starting point. The scores not only identify strengths and weaknesses but also feedback. For example, if an organization was building microservices, but found deploying their microservice was a huge headache, it’s fair to hypothesize that they are doing something wrong rather than that experience indicating an intrinsic weakness of the microservices pattern. That said, empirically it would seem that adopting any of these patterns doesn’t seem to lead to a deterministic outcome, particularly when adopting more complex patterns. Too often we build microservices expecting this:
But reality looks more like this:
In conversations with a diverse cross-section of developers and architects, the latter is typically closer to the broader experience. Interestingly, underperforming the scores was by no means a universal experience. In several cases implementations of these patterns overperformed expectations. But why? Must architecture always be a crapshoot, or can we more reliably and deterministically elicit system capabilities from architecture decisions.
In truth, breaking an application up into tiny pieces doesn’t magically make all those 5-star ratings appear, but they do come from somewhere. Determining where that “somewhere” is, and being more explicit in our architecture definitions will not only yield more deterministic results, but give us tools to surgically tailor an architecture without costly trial and error.
Where to Architecture Capabilities Come From?
Counterintuitively, architectural capabilities are elicited through constraints. We may not typically think about our designs that way, or use that precise terminology (we may call them design principles, “paved roads”, or simply “design decisions”), but they are there. The most explicit exploration of the direct relationship between constraints and capabilities was the thesis of Dr. Roy Fielding’s 2000 doctoral dissertation “Architectural Styles and the Design of Network Based Architectures (although most readers skipped to chapter 6, where he talks about REST). Fielding’s work wasn’t the only one to explore the relationship between constraints and architectural capabilities. Many scholars and practitioners in both software engineering, design, and architecture have touched upon this idea. In “Software Architecture: Perspectives on an Emerging Discipline by Mary Shaw & David Garlan delve into design by constraint and how these influence architectural styles. In “The Mythical Man-Month” and other writings, Fred Brooks touches on the idea that constraints, both in terms of software and project management, influence the architecture and design of systems. More recently, Chouki Tibermacine, Salah Sadou, Minh Tu Ton That, and Christophe Dony published their paper “Software Architecture Constraint Reuse-by-Composition which further explored how constraints are also useful for more precise definition and documentation of software architecture, and how constraints are reusable and composable architectural design elements.
Perhaps the most widely-known and tangible example are the infamous SOLID principles introduced by Robert C. Martin in his 2000 paper, “Design Principles and Design Patterns” (with the SOLID mnemonic coined by Michael Feathers). The SOLID principles are widely considered to be a set of design principles or best practices for object oriented software development. One could equally say they are design constraints and, by adhering to those constraints, certain desirable characteristics emerge in the codebase. Although SOLID is code/language level, they provide a tangible microcosm that we are already familiar with.
The Single Responsibility Principle introduces a constraint that, informally stated, constrains each class to have a single purpose. The consequence of adopting this constraint is we have code that is easier to understand and reason about with a reduced test surface-area, which also makes the code easier to maintain.
The Open/Closed Principle introduces a constraint that declares classes open for extension but closed for modification. The consequence of this constraint is code becomes more extensible while remaining easy to test.
The Liskov Substitution Principle is a constraint that formally describes the idea of “design by contract.” This constraint induces improvements in the code’s modularity and testability (among others).
The Interface Segregation Principle constrains code to prefer client-specific interfaces rather than general ones. This results in code that is more modular, decoupled, and easier to refactor, change, and redeploy.
Finally, the Dependency Inversion Principle really defines a set of constraints, namely:
- All member variables in a class must be interfaces or abstracts.
- All concrete class packages must connect only through interface or abstract class packages.
- No class should derive from a concrete class.
- No method should override an implemented method.
- All variable instantiation requires the implementation of a creational pattern such as the factory method or the factory pattern, or the use of a dependency-injection framework.
Most notably, this improves testability, extensibility, and adaptability.
By adopting these constraints in code, we can deliver the same features with significantly improved adaptability, evolvability, extensibility, maintainability, simplicity, testability, and understandability. Each constraint moves the needle on these characteristics and others. Architectural constraints mirror this methodology. In fact, many SOLID constraints are frequently applied, albeit in varied forms, to distributed architectures. Just remember every decision in architecture is a trade-off, nothing comes for free. Each constraint that strengthens one architecture capability will weaken another.
“Care must be taken to recognize when the effects of one constraint may counteract the benefits of some other constraint.”
-Dr. Roy Fielding
Through the careful and thoughtful composition of constraints, it is possible to heavily tailor and fine-tune an architecture at design-time or redesign (modernization) time. This is a powerful idea that has been overlooked for far too long.
Closing the Capability Gap
Historically many architectural constraints are implicit. Mark and Neal are seasoned architects and have worked together collaboratively for close to two decades. Achieving their level of experience would likely take multiple lifetimes for us mere mortals. They are undoubtedly very consistent with their various approaches on the projects they consult on. The wider industry, however, is a different story. Books, videos, lectures, blog posts, and implementations of various architecture patterns vary wildly. We’ve all been exposed to different sources that ultimately include implied constraints in their description and implementation. When I work with architecture teams, one exercise I often perform is to have them each define the set of constraints inherent to a given pattern. I have yet to see a single team agree universally on a full definition of an architecture pattern.
It’s no wonder we all seem to get varying results from architecture patterns, we apply the same label to a many different collections of constraints that are only superficially similar.
What’s interesting is, when we further link the constraints to the capabilities they engender we find that, generally, the union of the teams constraint delivers an optimal solution.
(Thanks to Jon Rasmussen and his excellent book “The Agile Samurai” for inspiration on these graphics.)
Through explicit specification of the underlying constraints of an otherwise overloaded and ambiguous label, we can now begin to see more deterministic and consistent results. I am certain that, given more time in those sessions discussing capability discrepancies, had we performed a similar exercise we would be able to identify the delta between the Ford/Richard scores and the developers’ observed results in the field. Additionally, almost every patten includes core, non-negotiable constraints, but without thinking explicitly in this way it can be easy to miss them. Consider the case of some of the microservice mega-disasters I have seen in my career. Many stem from absent core constraints (commonly the absence of clearly defined bounded contexts, domains, and domain aligned teams; or violation of the independent deployability constraint). By evaluating these under-performing architectures through the lens of constraints, a clear path forward is almost always obvious.
Summary - The Power of Constraints in Architecture
As Tibermacine et al show in their research “constraints can serve as a documentation to better understand an existing architecture description, or can serve as invariants that can be checked after the application of an architecture change to see whether design rules still hold.” In Dr. Fielding’s dissertation we learn that “Since properties are created by the application of architectural constraints, it is possible to evaluate and compare different architectural designs by identifying the constraints within each architecture, evaluating the set of properties induced by each constraint, and comparing the cumulative properties of the design to those properties required of the application.”
Ultimately, what we have is the foundation of an architectural model that allows for precise definition of architecture design and fine-grained control of architectural capabilities at both design-time and throughout the life of the project. Throughout the next several posts we will look at the eight architectural patterns individual, define their core constraints to achieve parity with the Ford/Richards model and explore some optional constraints that can be applied to tweak the capabilities in the base pattern. As we work through these, you’ll see that many constraints are germane to most–or sometimes all–patterns. You’ll begin to build a catalogue of reusable, composable constraints as well as your ability to fine-tune architectures for any set of capability targets. Equipped with this knowledge, you’ll be better positioned to implement the entirety of the Tailor-Made Model and Process.