In any significant software project, complexity happens. The question is what are you going to do about it? In Software Architect Bootcamp (2nd Edition), Ralphael Malveau and Thomas J. Mowbray, Ph.D. write about five ways of dealing with software complexity.
Why Complexity Happens
Malveau and Mowbray write the following:
Many software projects fail to manage complexity because they do not consider control of complexity to be part of architecture. System-level design details are often delegated to multiple developers, who readily produce unique, uncoordinated designs. Other projects inherit excess complexity from the architecture of a proprietary product. Vendor architectures emphasize flexibility to satisfy the widest possible consumer market. For vendors, management of complexity has low priority, implicitly delegated to application developers.
Five Ways to Manage Complexity
Malveau and Mowbray identify five ways to manage complexity:
- Sweep it under a rug (encapsulation)
- Hide it in a crowd (repository architecture)
- Ignore it (horizontal architecture)
- Slice it (layered architecture)
- Dice it (vertical architecture)
Sweep it Under a Rug
Malveau and Mowbray write about using encapsulating to deal with complexity:
Encapsulation is an obvious way to hide implementation details behind an interface. As one of the fundamental properties of object-oriented environments, encapsulation unifies the software’s data model and procedural model into object abstractions. Encapsulation using language-specific mechanisms is not always as effective as might be hoped. When implementing changes, there are unforeseen impacts on related objects, which must also be modified.
Hide It In a Crowd
Malveau and Mowbray write about using a repository architecture:
One of the most effective ways to manage complexity is to use a repository architecture. In most cases, the repository is a database, but there are other forms such as a blackboard. Repository architecture is a design pattern that is highly applicable to system-level architecture with documented benefits and consequences. It is interesting that many software architects and developers fail to utilize this pattern when appropriate, exposing large numbers of fine-grained object instances across system-level boundaries.
A repository architecture manages complexity by consolidating access to many objects through query languages or accessor methods. One query-language can consolidate messaging to thousands of objects. An object or relational repository schema provides a common language model and access protocol for management of large numbers of objects.
Malveau and Mowbray write about using horizontal architecture to ignore complexity:
By ignoring nonessential differences among complex objects, the common interface abstractions that provide many benefits (e.g., interoperability, adaptability, substitutability, and isolation) can be defined. The concept of “common interface” has many synonyms in the software literature: design reuse, variation-centered design, standards and so forth. As one of the first design pattern book quipped, “The structure of most design patterns is similar.” A metapattern for this similar structure is the common interface.
Malveau and Mowbray write about slicing complexity:
A layered architecture defines levels of abstraction in a system, allowing application software to be isolated from low-level details. Layering defines sets of horizontal services with common interface abstractions. Mulitple application objects and higher-level service objects reuse these services. Layering, is a basic form of software reuse, provides interoperability and portability benefits, in addition to managing complexity. Layering is a flexible concept that takes many forms. Layering is frequently applied in object wrapping, operating systems, networking, frameworks, standards profiling, and application architectures.
Malveau and Mowbray write about dicing complexity:
Layering defines horizontal interfaces and partitions that manage complexity. Definition of vertical partitions is also useful. Vertical partitions can isolate complexity into independent subdomains. Each subdomain can support unique vertical frameworks. Vertical dependencies can be limited to objects in the vertical partition. Cross-domain dependencies (such as interoperability) should be handled through horizontal interfaces. In practice, most systems contain many unique vertical interfaces. Good architecture has a healthy balance between horizontal and vertical interfaces. Without horizontal interfaces, vertical partitioning is ineffective. Horizontal interfaces enable vertical partitions to interoperate without unecessary dependencies.