Software Architecture
An effective software architecture practice helps product development organizations produce better software faster. But before we can discuss an effective practice, we need an understanding of software architecture. It’s a term that’s used frequently in the software industry, and often with a certain laxness. Tightening up our definition is important, as the practices in this book are closely aligned with a strict and complete definition of architecture.
Software architecture is often equated with software design, but the two are actually quite distinct. A design is a specific, point-in-time arrangement of software components that, collectively, form a software system. As we develop each release of that system and determine how that release will function, we are designing that release.
What happens when we create the next iteration of that same system? We’ll revise its design, of course: That’s how we introduce the changes that distinguish one release from the next. But we won’t throw it out and start over, either; each subsequent design is related to the one that came before.
An architecture is a template for the iterative creation of a set of related designs. Architectures are also designed, but they are more than a design. Thus, an effective software architecture practice doesn’t just create one good design; it lays the foundation for creating tens, hundreds, and thousands of designs. That’s the potential of software architecture, and that’s the promise of an effective software architecture practice.
What, then, constitutes an architecture? Standards play an important part in architecture, so it feels appropriate to start our discussion of architecture with a definition from an IEEE standard:
[An architecture is] the fundamental organization of a system, embodied in its components, their relationships to each other and the environment, and the principles governing its design and evolution. [1]
Let’s take this definition apart, piece by piece, to understand it thoroughly.
Fundamental Organization
Imagine that you have a software product consisting of a hundred components. The precise nature of these components doesn’t matter; they could be services, libraries, containers, functions, or plugins. The point is that your product is composed of these components, and the interactions between these components realize the features and functions of the product.
Now imagine that for each component, you used a random number generator to determine its type (service, library, etc.) and its method of communication. Often these are linked—for example, a code library is designed to be invoked via a local procedure call, and a service is not. That’s okay; we’ll pick, randomly, from the methods that reasonably apply to each component.
You’ve probably already realized that getting these components to work together will be a challenge. Different components require different implementation technologies, tooling, and deployment. We’ll have lots of mismatches when we try to wire components up, and we’ll have to translate between local calls and remote calls and message-passing and function invocation. In starting with randomness, we’ve constructed a system that lacks any fundamental organization. Fortunately, this system exists only in our imagination.
No one works this way, and every real system has some fundamental organization to it. The fundamental organization of a system often is, to some extent, imposed by external factors. For example, if you’re building a mobile application, your elements will primarily be libraries and they’ll mostly communicate via local procedure calls. In contrast, if you’re building a cloud-based product, you might organize your system around services.
When speaking of the architecture of a system, however, we are generally referring to a fundamental organization that goes beyond these external constraints. For example, cloud services necessarily communicate via the network. Is that communication organized as request-response or message-passing? Any system that chooses one method over the other is fundamentally organized around that specific approach.
Figure 1.1 illustrates the impact of each approach to establishing a system’s fundamental organization. As illustrated in the leftmost diagram, a randomized system consists of different types of components (indicated by different shapes) communicating via different mechanisms (indicated by different line types), and with arbitrary relationships. External constraints tend to dictate component types and communication mechanisms, as indicated by the uniformity of shapes and line types in the middle diagram. However, external constraints rarely impose organization on relationships within the system. The final diagram, on the right side of Figure 1.1, illustrates a system with a clear fundamental organization. It uses a consistent component type, consistent communication mechanism, and has structured relationships.
Figure 1.1 Levels of fundamental organization. A random system (left) has a mix of component and relationship types. Most systems are subject to at least some external constraints (middle) on those types. Organized systems (right) bring additional consistency through further constraints.