- Fundamental Organization
- Of a System
- Embodied in Its Components
- Their Relationships to Each Other
- Their Relationships to the Environment
- Principles Governing Its Design
- And Evolution
- Summary
Their Relationships to Each Other
As programmers, we tend to place an emphasis on components over connections. The components feel material—as much as anything in software ever does—because they consist of the code we write. Components can be compiled, packaged, distributed, and delivered. They feel almost tangible.
But components aren’t interesting on their own. Software comes to life when those components are connected to each other in meaningful ways. How and which connections are formed should therefore be intentional, not happenstance.
Some well-known architectures place relationships front and center. The Unix shell architecture, for example, consists of two primitives: programs and streams. Streams are directional; they have an input side and an output side. Programs read zero or more inputs and write zero or more outputs. The shell’s job is to link outputs and inputs, from one program to the next, forming pipelines through which data flows.
The Unix architecture doesn’t have much to say about how these programs operate. They can be written in different languages, handle binary data or textual data, and so on. Most of the programs are relatively small, focused on a single job. (The emphasis on small, narrow programs is a Unix architectural principle—more on principles in a moment.)
The relationships between the programs get more attention. By default, every program has one input stream (stdin) and two output streams. The output streams are divided into the “standard” output (stdout) and a special stream for errors (stderr). It’s possible but somewhat tedious to deal with more streams, whether for input or output.
The beauty of this approach is that it’s simple yet powerful. Programs written at different times by different authors can be readily combined by the user to achieve new and unexpected outcomes. And it’s possible not because of constraints on the programs, but rather because of their relationships with each other.
This result, in which components are combined post-development to achieve a new result, is an example of network effects. Network effects are exciting because they produce a combinatorial explosion of value for linear inputs. And while this book isn’t really about platforms and network effects, there is a deep connection between platforms, network effects, and architecture.
However, the combinatorial magic of connections can also work against an architecture. In the Unix model, programs can be combined—but they do not intrinsically depend on each other. When a system contains many interconnected components that depend on each other, these relationships become a hindrance, not a help.
When relationships are not governed, the dependency count tends to grow. And while these dependencies may be introduced one at a time, the complexity of the resulting system can grow much more quickly. It can quickly grow beyond anyone’s ability to comprehend, let alone manage.
If you have ever worked on a system that had components that couldn’t be touched for fear of breaking some other component, then you’ve worked on a system where the relationships between components have become too entangled. These scenarios demonstrate how managing relationships is just as fundamental as managing the components themselves.