Recently, I posted an entry introducing 19 patterns for modularity. The first of these patterns was ManageRelationships. ManageRelationships is a simple pattern stating that we should design module relationships. I categorize this as a base pattern, meaning it’s a prerequisite pattern for many of the other patterns in the list. For example, the Dependency Patterns listed demand that we manage the relationships between modules. It’s a simple pattern, but important. The discussion below isn’t exhaustive, but it does offer a bit more insight to ManageRelationships.
A relationship between two modules exists when a class within one module depends upon a class within another module. In other words,
If changing the contents of a module, M2, may impact the contents of another module, M1, we can say that M1 has a dependency on M2.
Dependencies can manifest themselves in different ways. The most straightforward is a direct dependency, where a client module depends directly on a service module (shown at left in the diagram). Indirect, or transitive, dependencies involve at least three modules, where one service module is also a client module. Also shown at left, the service module is a client module of the subsystem module and a service module to the client module. Here, the client module has an indirect relationship to the subsystem module, and if something changes in the subsystem module, it may ripple through the other modules.
Understanding the relationships between modules makes it easier to isolate the impact of change to a specific set of modules, which is not something easily done at the class level. I provided an example of this in a previous post. In the example above, changes to the client module clearly indicate that the impact of change is isolated only to the client module. Likewise, changes to the service module indicate the impact of change could spread to other classes within the service module as well as classes within the client module. Without designing and understanding module relationships, it’s difficult to understand the impact of change.
There are a lot of things to consider when designing module relationships. In general, a module has incoming dependencies, outgoing dependencies, or a combination of each. Different forces affect modules depending on the types of dependencies they possess.
Modules with a lot of incoming dependencies are more difficult to change because they are being reused by more client modules. Because of this, it’s imperative that they undergo more thorough and rigid testing, and steps should be taken to minimize changes to these modules (like using AbstractModules).
Modules with a lot of outgoing dependencies are easier to change because they are reused by fewer modules. Unfortunately, these modules are more difficult to test in isolation because of their dependencies on other modules.
There are ways to alleviate each of these challenges. Designing abstract modules, ensuring module relationships are acyclic, and separating abstractions from the classes that realize them are each examples.
When designing module relationships, there are some important implementation details to consider. For example, a service module must be included in the build classpath of the client module. Failing to do so will result in a compile error. If the client module references a class in the service module at runtime, then the service module must also be included in the runtime classpath, as well. Failing to do so will result in a ClassNotFoundException.
Modules with excessive incoming and outgoing dependencies are the most difficult to manage because they are widely reused but also difficult to change and test. Because of this, it’s ideal if modules are either heavily depended upon or heavily depend upon another module. Unfortunately, this isn’t always possible. In these cases, other modularity patterns can help ease the tension when modules have a lot of incoming and outgoing dependencies.
Identifying the modules is the first step in designing modular software systems. Shortly following, however, is managing the relationships between those modules. It’s these relationships between modules that I refer to as the joints of the system, and it’s these areas of the system that demand the most nurturing to ensure a flexible system design. If care isn’t given to managing the relationships between modules, separating out the system into a set of modules isn’t going to provide significant advantages.
More on some of the other modularity patterns coming. In the meantime, your feedback is appreciated.