Large software systems are inherently more complex to develop and maintain than smaller systems. Modularity involves breaking a large system into separate physical entities that ultimately 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. Reuse and maintainability are important factors 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 other modules desire. 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 module’s reuse potential. Many of these principles would not be possible without the principles and patterns of object-oriented design. As you’ll discover, the physical design decisions you make to modularize the system will often dictate the logical design decisions.
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. Although these principles can be applied to any other unit, such as packages, they shine when using them to design JAR files.
The OSGi Service Platform is the dynamic module system for Java. In OSGi parlance, a module is known as a bundle. OSGi 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.
You’ll find examples leveraging OSGi throughout this book. However, OSGi is not a prerequisite for using the modularity patterns. OSGi simply provides a runtime environment that enables and enforces modularity on the Java platform. OSGi offers the following capabilities:
- Modularity: Enables and enforces a modular approach to architecture on the Java platform.
- Versioning: Supports multiple versions of the same software module deployed within the same Java Virtual Machine (JVM) instance.
- Hot deployments: Permits modules to be deployed and updated within a running system without restarting the application or the JVM.
- Encapsulation: Allows modules to hide their implementation details from consuming modules.
- Service orientation: Encourages service-oriented design principles in a more granular level within the JVM. To accomplish this, OSGi uses μServices.
- Dependency management: Requires explicit declaration of dependencies between modules.