An architect’s job is to manage complexity, not increase it. Yet the developer life is filled with jargon, acronyms, and seemingly infinite choices. So how do we know when complexity makes sense? And what is complexity in the first place?
For the sake of conversation, let’s consider the following as potential complexities:
- Coding to an interface instead of an implementation
- Service-Oriented Architecture
- Domain-Driven Design
- Layered architecture
- Custom Data Access Layer
Now before you grab the pitchforks, let me be clear: Complexity is neither good nor bad. These are potentially valuable patterns and practices, but nearly all best practices have a corresponding context. Thus, complexities must be justified. Yes, even near universal best practices like coding to an interface rather than an implementation and logical layering have contexts where they do and don’t make sense.
“Any intelligent fool can make things bigger, more complex, and more violent. It takes a touch of genius – and a lot of courage to move in the opposite direction.”
Consider coding to an interface: Long ago in the Gang of Four design patterns book it was declared that we should program to an interface rather than an implementation. Interfaces are indeed useful for volatile dependencies – in other words, those that are likely to change or those that we desire to substitute to support unit testing. For example, coding to an interface is useful when interacting with a data access or service layer so that the implementation can be substituted for an in-memory version to support fast and reliable unit tests or offline development. However, coding to an interface for native library types like .NET’s StringBuilder often adds no value. If you can’t envision a need to substitute the concrete type, then there’s nothing wrong with instantiating a type directly. Coding to an interface is just noise in such a case. It’s unnecessary complexity.
Or consider something as apparently universal as the idea of layering an application. Even the esteemed Martin Fowler acknowledges in “Patterns of Enterprise Application Architecture” that for smaller applications, functions are all the layering you need. Wait, you mean I can’t just blindly create n-tier applications that always contain separate discrete layers? Yes that’s exactly what he’s saying. Consider the context. For that matter, consider why you’re creating layers in the first place.
Yet understandably software architects feel it’s their duty to design the “perfect” or “best practice” solution up front. After all, isn’t that what architects are for? But software architecture isn’t like building a house. Software is malleable. So the cost of making rather radical changes later, as complexity justifies, is comparably quite low. Adding a basement to a house later is highly impractical. But refactoring data access to a dedicated persistence layer is a feasible and straightforward task that involves cutting, pasting, and defining interfaces. Though a significant architectural change, it’s trivial in comparison.
Thus one should strive to avoid the mindset of the “ivory tower blueprint hander-outer ™”. Ultimately, one simple way to be a lean software architect is to utilize this simple two step process:
- Build the simplest thing that could possibly work
- Focus on the pain
In part two of this three part series , we consider the argument for building the simplest thing that could possibly work. And in part three we’ll discuss how focusing on the pain can help us iterate and minimize complexity along the way.
This discussion just scratches the surface of pragmatic architectural concerns. For greater detail, check out “Architecting Applications for the Real World in .NET” on Pluralsight