Chapter 1 – Introduction

In 1995, design patterns were all the rage. Today, I find the exact opposite. Patterns have become commonplace, and most developers use patterns on a daily basis without giving it much thought. New patterns rarely emerge today that have the same impact of the GOF patterns.  In fact, the industry has largely moved past the patterns movement. Patterns are no longer fashionable. They are simply part of a developer’s arsenal of tools that help them design software systems.

But the role design patterns have played over the last decade should not be diminished. They were a catalyst that propelled object oriented development into the mainstream. They helped legions of developers understand the real value of inheritance and how to use it effectively. Patterns provided insight to how to construct flexible and resilient software systems. With nuggets of wisdom, such as “Favor object composition over class inheritance” and “Program to an interface, not an implementation”, patterns helped a generation of software developers adopt a new programming paradigm.

Patterns are still widely used today, but for many developers, they are instinctive. No longer do developers debate the merits of using the Strategy pattern. Nor must they constantly reference the GOF book to identify which pattern might best fit their current need. Instead, good developers now instinctively design object oriented software systems.

Many patterns are also timeless. That is, they are not tied to a specific platform, programming language, nor era of programming. With some slight modification and attention to detail, a pattern is molded to a form appropriate given the context. Many things dictate context, including platform, language, and the intricacies of the problem you’re trying to solve. As we learn more about patterns, we offer samples that show how to use patterns in a specific language. We call these idioms.

I’d like to think the modularity patterns in this book are also timeless. They are not tied to a specific platform or language. Whether you’re using Java or .NET, OSGi or Jigsaw, or simply want to build more modular software, the patterns in this book will help you do that. I’d also like to think that over time, we’ll see idioms emerge that illustrate how to apply these patterns on platforms that support modularity, and that tools will emerge that help us refactor our software systems using these patterns. Out of this, I’m hopeful that tools will continue to evolve that help aid the development of modular software. For example, tools that provide important refactoring capabilities to help improve modularity. Time will tell.

Over the past several years, there have also been a number of object oriented design principles that have emerged. Many of these design principles are embodied within design patterns. The SOLID design principles espoused by Uncle Bob are prime examples. Further analysis of the GOF patterns reveals that many of them adhere to these principles.

But for all the knowledge shared and advancements made that help guide object oriented development, creating very large software systems is still inherently difficult.  These large systems are still difficult to maintain, extend, and manage. The current principles and patterns of object oriented development fail in helping manage the complexity of large software systems because they address a different problem. They help address problems related to logical design, but do not help address the challenges of physical design.

1.1 – Logical vs Physical Design

Almost all principles and patterns that aid in software design and architecture address logical design. Logical design pertains to language constructs such as classes, operators, methods, and packages. Identifying the methods of a class, relationships between classes, and a system package structure are all logical design issues.

It’s no surprise that since most principles and patterns emphasize logical design, the majority of developers spend their time dealing with logical design issues. When designing classes and their methods, you are defining the system’s logical design. Deciding if a class should be a Singleton is a logical design issue. So to is determining if an operation should be abstract, or deciding if you should inherit from a class versus contain it. Developers live in the code, and are constantly dealing with logical design issues.

Making good use of object oriented design principles and patterns is important. Accommodating the complex behaviors required by most business applications is a challenging task, and failing to create a flexible class structure can have a negative impact on future growth and extensibility. But logical design is not the focus of this book. There are numerous other books and articles that provide the guiding wisdom necessary to create good logical designs. However, logical design is just one piece of the software design and architecture challenge. The other piece of the challenge is physical design. In fact, if you don’t consider the physical design of your system, then your logical design doesn’t really matter all that much.

Physical design represents the physical entities of your software system. Determining how a software system is packaged into its deployable units is a physical design issue. Determining which classes belong in which deployable units is also a physical design issue. Managing the relationships between the physical entities is also a physical design issue. Physical design is equally as, if not more important than, logical design.

For example, defining an interface to decouple clients from all classes implementing the interface is a logical design issue. Decoupling in this fashion certainly allows you to create new implementations of the interface without impacting clients. However, the allocation of the interface and its implementing classes to their containing modules is a physical design issue. If the interface has three different implementations, and each of those implementation classes has underlying dependencies, the placement of the interface and implementation impacts the ease surrounding future reusability. Placing the interface and implementation in the same module introduces the risk of undesirable deployment dependencies. If one of the implementations is dependent upon a complex underlying structure, then you’ll be forced to include this dependent structure in all deployments, regardless of which implementation you choose to use.

Unfortunately, while many teams spend a good share of time on logical design, few teams spend time on physical design. Physical design is about how we partition the software system into a system of modules. Physical design is about software modularity.

1.2 – Modularity

Large software systems are inherently more complex to develop and maintain than smaller systems. Modularity involves breaking a large system into separate parts makes the system easier to understand. By understanding the behaviors contained within a module, and the dependencies that exist between modules, it’s easier to identify and assess the ramification of change.

For instance, software modules with few incoming dependencies are easier to change than software modules with many incoming dependencies.  Likewise, software modules with few outgoing dependencies are much easier to reuse than software modules with many outgoing dependencies. This tension between reuse and maintainability is an important factor to consider when designing software modules, and dependencies play an important factor. But dependencies aren’t the only factor.

Module cohesion also plays an important role in designing high quality software modules. A module with too little behavior doesn’t do enough to be useful to other modules using it, and therefore provides minimal value. Contrarily, a module that does too much is difficult to reuse because it provides more behavior than is desired. When designing modules, identifying the right level of granularity is important. Modules that are too fine-grained provide minimal value and may also require other modules to be useful. Modules that are too coarse-grained are difficult to reuse.

The principles in this book provide guidance on designing modular software. They examine ways that you can minimize dependencies between modules while maximizing a modules reuse potential. Many of these principles would not be possible without the principles and patterns of object-oriented design. However, as you’ll discover, the physical design implications that affect modularity often dictate the logical design decisions.

1.2.1 – Unit of Modularity – The JAR File

Physical design on the Java platform is done by carefully designing the relationships and behavior of Java JAR files. On the Java platform, the unit of modularity is the JAR file. While these principles can be applied to any other unit, such as packages, they shine when applied to designing JAR files. Many of the examples in this book use OSGi. However, OSGi is not a prerequisite for using the principles of modularity. OSGi simply provides a runtime environment that brings modularization to the Java platform.

1.2.2 – OSGi

OSGi Service Platform is the dynamic module system for Java. It provides a framework for managing bundles that are packaged as regular Java JAR files with an accompanying manifest. The manifest contains important metadata that describes the bundles and its dependencies to the OSGi framework. IThe OSGi framework offers the following capabilities:

  • Modularity: Enables a modular approach to architecture on the Java platform.
  • Versioning: Supports multiple versions of the same software module deployed within the same JVM instance.
  • Hot deployments: Permits modules to be deployed and updated within a running system without restarting the application or the JVM.
  • Service orientation: Encourages service-oriented design principles in a more granular level within the JVM.
  • Dependency management: Requires explicit declaration of dependencies between modules.

1.3 – Chapter Synopsis

Here is a breakdown of the remaining chapters in this book. This book does not need to be read in sequential order. It has been structured so that you can read individual chapters. Chapter 2 introduces OSGi through a general discussion and some simple examples. If you’re familiar with OSGi, you can skip this chapter. If  you’re new to OSGi, I suggest you start here, though the modularity principles don’t require OSGi to be effective.

For an overview of logical design and the SOLID design principles, start with Chapter 4. Most of the material in this chapter is not new, but is provided for reference material that’s referenced when discussing the key principles of modularity. For an overview of physical design, start with Chapter 5. This discusses some of the key elements behind physical design, and many of the challenges we try to solve. Chapter 6 presents a reference implementation that can be used to show many of the key principles.

Chapter 7 begins our discussion of the principles of modularity, and talks about principles that help in managing dependencies between modules. Chapter 8 talks about principles related to the usability of modules, and Chapter 9 examines techniques that can be used to create more extensible modules. The material in chapter 9 makes heavy use of the SOLID principles.

In general, the book is divided into three main parts. Chapters 2 and 3 introduce OSGi. Chapter 4, 5, & 6 discuss logical and physical design. The remaining chapters explore the principles of modularity.

1.3.1 – Part 1 (The Case for Modularity)

Chapter 2 – Module Defined
Chapter 3 – Two Facets of Modularity
Chapter 4 – Architecture and Modularity
Chapter 5 – Taming the Beast
Chapter 6 – Realizing Reuse
Chapter 7 – Modularity and SOA
Chapter 8 – Reference Implementation

It’s important to provide some decent samples that illustrate some of the concepts discussed. The reference implementation serves two purposes. First, it ties together the material in the first five chapters so you can see how these concepts are applied. Second, it lays the foundation for many of the heuristics presented in chapters 7 – 12. The reference implementation is an example of the heuristics applied in conjunction with other, and this chapter refers to many of these heuristics at the points where they are applied.

1.3.2 – Part 2 (Pattern Catalog)

Module Base Patterns

The base patterns are the fundamental elements upon which many of the other patterns exist. They establish the conscientious thought process that go into designing systems with a modular architecture. They focus on modules as the unit of reuse, dependency management, and cohesion. Each are important elements of well designed modular software systems.

Module Dependency Patterns

I’ve found it personally fascinating that development teams spend so much time designing class relationships, but yet spend so little time creating a supporting physical structure. Here, you’ll find some heuristics that will help you create a supporting  physical structure emphasizing the coupling between components. You’ll also find some discussion exploring how component design impacts deployment.

Module Usability Patterns

While coupling is an important measurement, cohesion is equally important. It’s easy to create and manage component dependencies if I throw all of my classes in a couple of files. But in doing so, I’ve introduced a maintenance nightmare. In this chapter, I introduce some heuristics that will help ensure our components are cohesive units. It’s interesting that you’ll find some contention between the heuristics in chapter 7 and those in chapter 6. I’m going to talk about this contention, what you can do to manage it, and when you want to do it.

Module Extensibility Patterns

A goal in design software systems is the ability to extend the system without making modifications to the existing codebase. Abstraction plays a central role in accomplishing this goal, but simply adding new functionality to an existing system is only part of the battle. We also want to be able to deploy those new additions with redeploying the entire application. The Extensibility Patterns focus on helping us do this.

Module Utility Patterns

The utility patterns aid modular development. Unlike the other patterns, they don’t emphasize reuse, extensibility, or usability. Instead, the base patterns discuss ways that modularity can be enforced and that help address quality related issues.

1.4 – Pattern Form

Each patterns is consistent in structure to help maximize it’s readability.  Each is also accompanied by an example illustrating how the underlying principle it captures is misapplied, followed by a more appropriate application. Not all sections will appear for all principles. In some cases, I’ll suppress certain sections when a previous discussion can be referenced. The general structure of each principle is as follows:

  • Pattern Name
: The name of the principles. The name is important, since it helps establish a common vocabulary among developers.
  • Pattern Statement: 
A summary describing the principle. This statement helps establish the intent of the principle.
  • Sketch: 
A visual representation showing the general structure of the principle applied. Usually, UML is used here.
  • Description
: A more detailed explanation describing the problem the principle solves. The description establishes the motivation behind the principle.
  • Implementation Variations
: As with any principle or pattern, there are subtle implementation details that quickly arise when applying the pattern to a real world problem. Implementation Variations discusses some of the more significant alternatives you should consider when applying the principle.
  • Consequences
: All design decisions have advantages and disadvantages, and like most advice on software design, the use of these principles must be judicious. While they offer a great deal of flexibility, that flexibility comes with a price. The Consequences section discusses some of the interesting things you’ll likely encounter when applying the principle, as well as some of the probable outcomes should you decide to ignore the principle. After reading through the consequences, you should have a better idea of when you’ll want to apply the principle, and when you may want to consider using an alternative approach. Boiled down, this section represents the advantages and disadvantages of using the principle, the price you’ll pay, and the benefits you should realize.
  • Sample: It’s usually easier to understand pattern when you can see a focused example. In this section, we’ll walk through a sample that illustrates how the pattern can be applied. Sometimes, we’ll work some code and other times some simple visuals conveys the message clearly. Most important though is that the sample won’t exist in a vacuum. When we apply patterns in the real world, patterns are often used in conjunction with each other to create a more flexible tailored solution. In cases where it makes sense, the sample will build on previous samples illustrated in other patterns. The result will be insight to how you can pragmatically apply the pattern in your work.
  • Related Patterns: 
Most of the principles are fundamental aspects of object orientation and design patterns that are used daily. In this section, I’ll examine patterns similar to the one presented, and explore the similarities and differences.

1.5 – Pattern Catalog

Below are the modularity patterns.

1.5.1 – Base Patterns

  • ManageRelationships – Design Module Relationships.
  • ModuleReuse – Emphasize reusability at the module level.
  • CohesiveModules – Create cohesive modules.
  • ClassReuse – Classes not reused together belong in separate components.

1.5.2 – Dependency Patterns

  • AcyclicRelationships – Module relationships must be acyclic.
  • LevelizeModules – Module relationships should be levelized.
  • PhysicalLayers – Module relationships should not violate the conceptual layers.
  • ContainerIndependence – Consider your modules container dependencies.
  • IndependentDeployment – Modules should be independently deployable units.

1.5.3 – Usability Patterns

  • PublishedInterface – Make a modules published interface well known.
  • ExternalConfiguration – Modules should be externally configurable.
  • ModuleFacade – Create a facade serving as a coarse-grained entry point to the modules underlying implementation.

1.5.4 – Extensibility Patterns

  • StableModules – Modules heavily depended upon should be stable.
  • AbstractModules – Depend upon the abstract elements of a module.
  • ImplementationFactory – Use factories to create a modules implementation classes.
  • SeparateAbstractions – Separate abstractions from the classes that realize them.

1.5.5 – Utility Patterns

  • LevelizedBuild – Execute the build in accordance with module levelization
  • TestComponent – For each module, create a corresponding test component that validates it’s behavior and illustrates it’s usage.