Anyone who has experienced software development in large-scale will be very familiar with the following scenario:
A base software module was developed to offer some services for a specific business case. After sometime, a new requirement emerges which requires the additions of a new feature to a base service. A software developer, who in most cases does not have the direct assistance from the original developer – e.g. because that developer already left the company, has now to read the sources, tries to understand the original logic, and to modify the original code in order to insert his new implementation within the right context.
Without sufficient knowledge about the base module, the modified sources are inherently error-prone. With each modification and the quick bug-fixes afterwards the software becomes increasingly dirtier and more difficult for further extensions. Moreover, an increasing number of tests fail with each new feature. Finally, in case the lifespan of a feature expires, it is also very difficult to delete it from the software module.
Very soon, the management will face the question of whether it is necessary to re-implement the module from scratch – with the associated sunk costs, new investment and service outages.
The problem is the inherent cyclic dependencies resulted from embedding the new feature into its base module, as depicted in Figure 1.
Figure 1: Cyclic Depdendency in Software Evolution
In one direction, we have the direct dependency from the base module to its extension, at least for the feature invocation. In the other direction we have the implicit dependency from the new feature on the base, as this faeture has to operate in the context defined by the base module. Such a cycle results in a tight coupling and the overlapping of the domains of concerns or responsibilities, and therefore the error-proness, inflexibility and complexity in the software evolution process.
One possible solution to this problem is to eliminate or invert the invocation dependency as in Figure 1. In this way, it is no longer necessary to directly modify the source codes in the base module. All the test cases for the base module continue to be valid. The base module and the new feature can be maintained, developed or tested separately. Concerns, focuses and responsibilities of the different developers are clearly separated. Introduction or deletion of features can be easily implemented by adding or deleting the resources (e.g. the corresponding artifact) for the new feature.
The concept of Dependency inversion (DI) is frequently used in the context of Object-Oriented frameworks, referring to the utilization of interfaces or abstract classes for separating the client and service implementations. In our scenario, the new features could be realized as new services or new implementations of the existing services, e.g. by wrapping the previous service interfaces or modify the existing implementations. Due to associated restrictions (e.g. only the external interface can be wrapped) or overheads (e.g. due to the necessity to modify legacy codes), the OO-based dependency inversion framework is in-sufficient to provide a general solution for the aforementioned problem.
The following blogs present a simple, easily manageable/maintainable solution for the agile software development as discussed in the previous scenario, with emphases on the simplicity in coding and analyses, and on minimal runtime overhead. This framework utilizes java annotation framework as specified in JSR-175/250/269. The inverted dependencies from new, extended feature to their base classes are specified by indentifying the corresponding service extension points and the corresponding extension types in the annotations attached to the new feature methods.
The extension method calls will be inserted via a specific annotation processor into the byte codes for the extended classes. The insertion will happen after the compilation phase and ahead of the deployment phase.
This presentation is based on the result of a prototyping project.
Next Page - Dependency Inversion Based on Java Annotations