See Part 1 for an overview of how we got started. In this post, we’re going to take the modularization of our application a step further. We’re going to apply two refactorings using two different modularity patterns – AbstractModules and AcyclicRelationships. First, we’re going to separate the bill and audit functionality out into separate modules so we can manage (develop, deploy, etc.) them independently. Second, we’re going to remove the cyclic dependency between these two modules.
A Quick Review
Before we get started with this second installment, let’s review what we did in Part 1. Initially, our billing application was all packaged and deployed in a single WAR file. Since we had a conceptually layered application, it was pretty easy to separate the UI layer from the business object and data access layer. Not sure we explicitly pointed it out in Part 1, but that refactoring essentially applied the PhysicalLayers pattern. After doing this, we were left with the system at right (click to enlarge).
Recall the initial class diagram, where we had a Bill class with a bi-directional relationship to the AuditFacade class. This design has two fundamental flaws. The Bill is tightly coupled to the concrete AuditFacade class and the relationship is bi-directional. Bad all around! This can be seen in the following code snippet illustrating the Bill’s audit method.
Notice that the audit method actually creates the AuditFacade, calls the audit method, and passes a reference to Bill. Ugly. Let’s clean this up a little bit. While there are obvious technology reasons why we need to clean this up, there is also a motivating business force.
The system needs to go live with the current vendor’s auditing system, but the business has indicated that they aren’t renewing the contract with the vendor and are in ongoing negotiation with another vendor. The contract expires in 6 months, but we deliver the initial version of the system in 3 months.
So, 3 months after deployment, we know we’ll need to swap out auditing systems. What to do?
The first half of this solution can be found in the AbstractComponents project, where we apply the AbstractModules pattern which states that we should depend upon the abstract elements of a module. So we’re going to refactor the AuditFacade to an interface and create a separate AuditFacade1 implementation. This solves the first half of our problem – the tight coupling between the Bill and AuditFacade implementation. The result is the class diagram shown at right (click to enlarge). Take a look at the refactored Bill class, and you’ll see that the AuditFacade interface is now passed into the Bill, allowing us to swap out AuditFacade implementations. Now we need to decide how to modularize the system.
If we take this flexible class structure and continue to deploy in the single bill.jar module, we have the flexibility to swap out AuditFacade implementations at the class level, but we’re still required to package everything up into a single bundle and deploy it together. So what we really must do is separate the audit functionality out into separate modules from the bill functionality. Separating the AuditFacade interface and AuditFacade1 implementation out into separate modules results in the diagram at right (click to enlarge). We can also see this change reflected in the build file that packages up these modules. Here’s where the bi-directional relationship between Bill and the AuditFacade interface (and AuditFacade1 implementation) rears it’s ugly head. We have a cyclic dependency between our bill.jar and audit.jar modules. We need to fix this problem.
The second half of this solution can be found in the AcyclicRelationships project. Now, we need to remove the cyclic dependency between the modules, and we’ll apply the AcyclicRelationships pattern which states that module relationships must be acyclic. To do this, we’ll introduce an additional abstraction, called Auditable, that our Bill class implements. Upon applying this little trick, we can see that we have now removed the bi-directional relationship between Bill and the AuditFacade interface, as shown at right (click to enlarge).
Whereas previously the AuditFacade accepted a Bill to the audit method, it now accepts an Auditable type. The new AuditFacade interface can be seen in the code snippet below.
The Bill will pass itself to the AuditFacade interface as an Auditable type. The key element at this point is how we allocate these classes to their respective modules. Here’s a simple rule to abide by when determining how to allocate classes and interfaces to modules.
Interfaces should be closer to the classes that use them, and farther away from the classes that implement them.
We’ll talk more about this in the next post, because we build on this idea to address another problem that has surfaced. But applying this rule now, it’s clear that Auditable should be bundled with the AuditFacade interface, not in the same module as the Bill class.
While we haven’t talked about the new financial.jar module, we applied a similar refactoring to the Payment class by implementing a Payable interface, and breaking it out into a separate module. Allocation of classes to modules is done at build time, so after modifying our build script, the result is the modules shown at right (click to enlarge).
As I’m sure you’ve noticed at this point, we broke our rule above and bundled the AuditFacade1 implementation close to the interface it implements (btw, this is the other problem that has surfaced). If we apply our rule in this situation, we’d bundle the AuditFacade interface and Bill class in the same bundle, which certainly wouldn’t work because it would result in the cyclic dependency between the bill.jar and audit.jar modules that we’ve worked so hard to remove. Alas, this is the focus of our next refactoring, so we’re going to wait to tackle this challenge.
Wrapping Up and Getting Ready for Part 3
This post involved two refactorings. The first was to separate the bill and audit functionality out into separate modules. The second was to remove the cyclic dependency between the bill and audit modules.This offers us decent flexibility. We can introduce a new AuditFacade implementation, knowing that we’d only need to rebuild the audit.jar module. We can test the audit.jar module independent of any other module because it doesn’t have any outgoing dependencies. And if we wanted to, we could deploy the audit functionality separately (ie. we can reuse it elsewhere). So overall, some decent progress.
But some problems remain. If we really want the ability to swap out Audit systems, bundling the AuditFacade interface and AuditFacade1 implementation into the same module doesn’t give us the flexbility we need. While we can easily create a new AuditFacade implementation and allocate it to the audit.jar, this requires us to redeploy audit.jar unnecessarily. In the next post, we’ll explore how we can change the module structure to allow a new AuditFacade implementation while also allowing the existing AuditFacade1 implementation to be removed from the system.