In Part 1, we introduced the system, and broke each layer out into separate modules. In Part 2, we applied two refactorings using two different modularity patterns – AbstractModules and AcyclicRelationships. But we were still left with a problem, which we’ll solve here.
After the third refactoring, we were left with the modules shown at right (click to enlarge). But this doesn’t entirely solve our problem of how we deploy a new AuditFacade implementation, especially if we want to avoid redeploying the audit.jar module and everything in it. Additionally, because the two implementations of AuditFacade live in the same module, we can’t rip one out (ie. uninstall) when we add the other. In other words, because they are in the same module, they can only be managed together.
To solve this tricky little challenge, we’re going to apply the SeparateAbstractions pattern, which states that we should separate abstractions from the classes that realize them. If you’ll recall from part 2, we applied this pattern when allocating the Auditable interface to the audit.jar module, but we didn’t apply it to the AuditFacade1 class. So we’ll apply it to the AuditFacade1 implementation, as well as a new AuditFacade2 implementation. First, we have to answer the following question.
Where do we put the AuditFacade interface so that new implementations of the interface can be managed separately from the interface and other implementations?
Our answer can be found by looking at the module structure shown at right (click to enlarge). Note that I’ve also included the financial.jar module in this diagram, which was actually introduced in Part 2 when we introduced the Payable interface. We’ll use this soon, but I digress.
Anyway, we separate the AuditFacade interface out into its own module – auditspec.jar. Each of the implementations (AuditFacade1 and AuditFacade2) are also placed in their own modules. The code for this solution can be found in the SeparateAbstractions project. No real coding changes are necessary, other than we’ve added a new class – the AuditFacade2 implementation. Note that we’ve also separated each implementation out into different packages to avoid splitting packages across modules. After adding this new class, we modified our build script to allocate the classes to the appropriate modules.
A Note on the Benefit of OSGi
Now, we have an overall increase in architectural flexibility. Our AuditFacade implementation classes are allocated to separate modules, allowing us to manage the two independently. From a maintenance perspective, we can work on each of the implementations separately, resting assured that changes to one implementation won’t negatively impact changes to the other. We can also test each independently and reuse each independent of the other.
This change also increases the resiliency of the bill.jar module since it’s no longer tightly coupled to any AuditFacade implementation. We can test the bill.jar module using only the auditspec.jar module by creating a mock implementation of AuditFacade for testing purposes. While the billpay.jar module is still dependent on the financial.jar module, we’re going to solve that problem in the next refactoring. In general, we have completely eliminated the dependencies between the bill.jar module and the AuditFacade implementations in the audit1.jar and audit2.jar modules. Remember, at the beginning of Part 2, these modules containing the bill and audit behavior had a cyclic relationship.
While this example doesn’t leverage OSGi, it is important to point out the benefit that OSGi can provide here. Because we don’t have a runtime module system we still need to deploy these modules within the WAR file. The presence of OSGi, however, brings the same degree of flexibility to the runtime that we have at development time (for more information, see The Two Faces of Modularity & OSGi). With OSGi, I would be able to install and uninstall the audit1.jar and audit2.jar modules without redeploying the entire system. When we’re finished with all of our refactorings, we’ll “osgi-ify” the system, deploy it to Tomcat, and realize this benefit.
Wrapping Up and Getting Ready for Part 4
So we’ve made quite a bit of progress from our initial version of the system that completely lacked modularity. By breaking the system out into modules, we ease the maintenance effort and increase overall system flexibility. In part 4, we’re going to to turn our attention to the financial.jar module that focuses on payment. Recall that in Part 2 (when we applied the AcyclicRelationships pattern to the AcyclicRelationships version of the project) , we separated the Payment class out into a separate module and decoupled Bill and Payment through the Payable interface. Part 4 will examine how we can decouple the bill.jar module from the financial.jar module so bill.jar can be managed, tested, and deployed independent of financial.jar. Stay tuned!