Modularization is one of the most important and unfortunately one of the most often ignored features of software development. Most softwares are developed from functional requirements and non functional requirements. Since modularization is more like a longevity requirement (which comes under the banner of non functional requirements), there is seldom a clearly laid out requirement to promote modularized software development. So like unit testing, which until recently shared its fate, modularization gets laid on the wayside. More "tangible" features instead get prioritized over modularization. This problem results in very badly structured code without any coherent theme binding it all together. More often than not, code is scattered across multiple configuration files and classes. Certain "master" configuration files need to be tweaked for adding or taking away a module. Surprisingly, this trend pervades even to well established frameworks that are otherwise well engineered. In the worst instance, some frameworks only support one configuration file which should be changed as new modules emerge or go away. The most glaring example of this is the J2EE Servlet Specification which takes all its configurations from one solitary web.xml file that resides in a well known location. This means that introducing any new module for the application would have to change the web.xml file. No wonder, most frameworks stay away from this file and have just one well known servlet defined there. Modularization is seen as a very important requirement especially in products. A product would be constituted of multiple modules. Each may be priced separately. So imagine if all modules need to be shipped together to make the application work! This is not a desirable situation as the product manufacturer would like to ship out just the modules that the customer has purchased. This post talks about modularization and how it typically works. Some perceived requirements for modularization would be mentioned and summarized at the end of the post.
So what is a module?
A module is a collection of code and artifacts that exhibit a high degree of cohesiveness and implement the same concerns. In the context of software, modules can be said to implement a set of functional requirements. DRY (Don't Repeat Yourself) stipulates that the same requirement or concern cannot be implemented in multiple places. Hence a module's functionality should be unique within the program i.e. no other module must implement the same functional requirements.
How about requirements that need to be implemented across multiple modules?
If a requirement needs to be implemented across multiple modules, it would come under the banner of horizontal requirements. Horizontal requirements, according to me, have two parts to them. One is the functional part which actually implements the requirement. Log4J (or its incarnations for various languages) is an excellent example of this. The process of logging is systematically implemented using a great, albeit a very inextensible, framework.(BTW this is my biggest grouse with this framework. More on it in another post) But the actual process of integrating logging to the end application constitutes the horizontal part of the logging non functional requirement. The horizontal part is usually the more challenging since it has to somehow creep into all the modules. Well known patterns such as the interceptor pattern or the proxy pattern or the decorator pattern can be used to implement this process of creeping into the modules. AOP strategies use a combination of these patterns. Hence a modularization strategy has to combine the modular behavior with certain unifying behavior that cuts across all the modules. In essence, this is an application wide behavior that needs to be integrated across modules.
Modules collaborate to make the entire application. This collaboration leads to dependencies between them. These dependencies are essential but should be kept minimal in the interest of re-use. Loose coupling between modules must be fostered by the use of strategies such as the IOC pattern and the observer pattern. How does that work? And oh by the way, circular dependencies are a strict NO NO. Many build tools would break with circular dependencies. They just would not know which one to build first!!
Soft dependencies vs. Hard Dependencies
Dependencies can be hard or soft. Consider the following:
// Some method in some class ...
// Example of a hard dependency.
FooService fooService = new FooServiceImpl();
// Example of a soft dependency
FooService fooService = Class.forName("FooServiceImpl").newInstance();
If this code exists in a class in a particular module and FooServiceImpl resides in a second module, then the first module would have a hard dependency on the second module in the first instance and a soft dependency in the second instance. Soft dependency means that the two modules should exist in the runtime class path. Hard dependency means that the two modules must exist together even during compile time. This may not seem like a big deal at first sight. But a soft dependency promotes great extremely flexible deployment for the application. How so? Think about it and let me know.
Modules work best if they are discovered automatically by the application and integrated into the fabric of the application. Auto discovery strategies are prevalent in some frameworks (ex: the Spring Framework which is capable of discovering its bean xml files from the class path using wild cards) work beautifully with modularization.
This post must have raised a lot more questions than it attempted to address. Depending on the interest of my readers, I would attempt to address these questions in future posts. The following points summarize the requirements demanded by modularization:
- A module implements one or more UNIQUE functional requirements.
- Modules in an application must somehow integrate with application wide horizontal requirements.
- Modules should depend on each other. But their inter dependencies must be minimal and never circular
- Soft dependencies(runtime) between modules are preferable to hard (compile time) dependencies and lead to flexible runtime application packaging.
- Auto discovery fosters modularization.