The Java Framework Landscape
Dependencies and Classpath Conflicts
A common difficulty when combining multiple projects is a clash or conflict in the dependencies of the projects. Cocoon and Struts are good examples: Both projects depend on another Apache project from the Jakarta Commons group of projects, called "commons collections." Ordinarily, the commons collections package is bundled into a binary .jar file, and placed in the appropriate directory (WEB-INF/lib) where the remainder of the package's classes can find and utilize it. Struts, however, needs a different (newer) version of this jar file than does Cocoon. Attempting to run Cocoon with the newer file, in an effort to satisfy Struts' requirements, causes errors in Cocoon. The reverse is true if we use the older file: Struts won't operate correctly.
There are two solutions to this problem: the first is to modify and recompile either Cocoon to use the newer library, or Struts to use the older one. The problem with this solution is that it results in an "orphan" customized version of either Struts or Cocoon. You can no longer just download new versions of the two frameworks; you must concern yourself with the version of a particular library they are using—and each framework uses dozens of libraries.
The solution to this is communication: Rather than bundling the two frameworks or projects together, let them remain separate, but provide for a means for them to communicate. If they can transfer information and method requests between themselves, you can achieve the desired integration without conflict. This is exactly the technique that the Keel framework does; it provides a communication layer between frameworks (depending on configuration, JMS is the default choice for this layer, but Axis and several others are possible). This layer has the added advantage of providing a basis for distributed systems and clustering, and allows the overall application to scale to handle large user loads easily.
A seemingly minor problem can crop up when combining projects: Each project reads its configuration information in a different way, from a different source. In the case of a Turbine service, for example, configuration is supplied by means of a Configuration object. When you use Turbine's cache service via, say, Avalon, you must deal with the fact that Avalon uses a different mechanism for providing its configuration to components (a life-cycle method, specifically). Because of the component-oriented design of both frameworks, however, the Configuration object passed to the service is independent from the method used to populate the object. It is therefore a simple matter to "map" from one configuration object to another, allowing you to plug the Turbine service into Avalon with very little work. This is a good example of separation of concerns—the actual configuration information is read in one component, and used in another.
For some less flexible projects, the code that supplies a service directly accesses configuration information from the source (reading it from a file or a resource on the classpath, for instance), making it difficult to "uncouple" the service from its configuration source. The solution in this case is either to supply the configuration the way the underlying service requires it, or to re-factor the service to read its configuration from a generic object (like Configuration) that can be populated elsewhere.
Separation of Concerns
The concept of separation of concerns provides many of the attributes you need when integrating multiple projects. If your application code concentrates on the business logic at hand, it is easier to integrate with multiple service implementations. If your services concentrate only on their own service, they are more easily re-used in other projects. The principal of "do one thing and do it well" is not a new one, but before the advent of frameworks and component development, it was hard to achieve in software engineering. If an application wanted a particular service, it would either build it in directly, or create its own API library for the service. Re-use was rare, and it was common for service functionality to be interlaced with application logic.
Choosing a Framework
As I've touched on above, choosing a framework is an important choice, and has long-term ramifications for your development project. It often is beneficial to choose a framework with an active and diverse community using it because this community becomes an important element of ongoing support.
Certain frameworks have unique advantages in special situations, though, and must not be discarded just because they are not widely popular. For example, Echo is ideal for Swing-based application developers who need to write a Web-based UI to look and work as much like their desktop application as possible.
The availability of professional services and consulting can also be an important element to look for.
Picking a framework to build on may be the most important step of your next application project—choose well!
About the Author
Michael Nash is the president of JGlobal Limited, a software development, consulting, training and support company specializing in open source Java technologies. He is also a core developer of the Keel meta-framework, the author of two books and a number of articles and papers about next-generation web-application development with Java, and a member of the JSR-127 (JavaServer Faces) Expert Group. He can be reached at firstname.lastname@example.org.
Page 2 of 2