Java Component Development: A Conceptual Framework
Only One Instance of the Component Should Run in the Enterprise
A component should have just one and only one instance running at a time, so the Singleton design pattern, which ensures having only instance in a JVM,
is an appropriate choice. But while this works in a single-JVM scenario, it's
a problem when multiple JVMs are used. If this component needs in multiple
nodes of a cluster, each node will have its own instance. But it is still acceptable
if the configuration information, loaded at component startup, doesn't need
to change and it deals with completely static information.
If we assume that the component will be running in a single JVM, then ComponentControllerFactory will
look like Figure 3:
Figure 3. Component controller factory in a single JVM
The methods provided by the singleton controller factory are:
-
getXXXService(): This method returns the service provider
implementation class defined by the XML file.
-
getXXXAdapter(): This method returns the adapter implementation
class defined by the XML file.
Changes in the Configuration Should Be Dynamic
If a component is immutable, each cluster node will have an identical
copy of the singleton instance; but if it is not immutable and the configuration
information needs to be changed dynamically, we need something different.
There are two probable scenarios when dynamic configuration change may
happen:
- A single-JVM case
- A multiple-JVM case
A Single-JVM Case
If application will run in a single JVM, things are simpler. As we know, SingletonControllerFactory will
always have a single instance in the JVM, so whenever any changes are made
to the configuration file, the factory object will need to be reloaded,
based on some notification mechanism, which will load the Java serializable
configuration object in turn.
Here is the ConfigManager class. It is based on the Observer-Observable
pattern and performs two activities:
- It reads and processes the configuration XML file with the help of
XMLizer (a separate component) and loads the configuration Java object.
- It monitors the XML configuration file for possible changes.
Figure 4 shows the methods of the ConfigManager.
Figure 4. ConfigManager
The ConfigManager class acts as an Observer;
when it's notified by the Observable, its update method
will be called. The update() method will call the reload() method
of SingletonControllerFactory so that the newly created Java
object will reload its configuration information.
ConfigurationChangeNotifier acts as an Observable and
starts a thread, which notifies the ConfigManager if any change
occurs in the timestamp of the configuration XML file, which may indicate
a change in its contents. Figure 5 shows this relationship.
Figure 5. ConfigurationChangeNotifier
A Multiple-JVM Case
In a multiple JVM scenario, things are not so simple. We have to have:
- A strategy for reloading the changed configuration XML file dynamically
on the fly without bringing down the whole enterprise application.
- A strategy to have just one single instance running at a time throughout
the cluster.
Using JNDI combined with RMI is one option to ensure having
one and only one instance running in a particular node (JVM) of multiple
nodes of a clustered environment. A RMI service needs to be written and
a RMI stub would be generated out of this RMI service. This generated RMI
stub needs to be bound in the JNDI tree of the application server. This object
will sit on one container, which will make the object available throughout
the cluster.
To deal with this situation, we need to introduce ConfigManager, which
will do the following tasks:
- Create a configuration XML file that needs to be changed dynamically.
- Create a Java
Serializable object from the XML file.
Serialization and de-serialization can be done by different components.
- Create a RMI service, register the RMI stub generated from the RMI
service, and load the serializable configuration object within the RMI
service.
- Bind this RMI stub object to the JNDI tree of any node of the clustered
environment.
- Create a notification system that will rebind the RMI service and
reload the object in the tree whenever that configuration XML file appears
to have changed in the file system.
Figure 6 shows the RMI service interface and its implementation.
Figure 6. RMI service
In the multiple-JVM scenario, the ConfigManager will look
like Figure 7:
Figure 7. ConfigManager in a multiple-JVM scenario
The ConfigManagerMultipleJVM class acts as a Observer.
When it is notified by the Observable, its update method will
be called. Within the update() method, the rebindRMIService() method
will be called so that the newly created object (with the latest configuration
information) will be reloaded.
SingletonControllerFactory will act as a wrapper for the
RMI service, returning the appropriate, configured object.
The problem with this approach is that since there will be just one instance,
it has a single point of failure. The ConfigManager component
needs to be more robust to handle the failover.
But there is also another approach that will synchronize a cached configuration
object in a different node of a cluster, with the help of MDB and JMS.
In this case, RMI service is not required. Here are the steps to implement
this approach:
- The
SingletonControllerFactory initializes and starts
up the component with the configuration object.
-
ConfigManager's Observer-Observable model tracks any
change in the configuration of the XML file with its notification mechanism. If a change is found, it publishes the message to the JMS topic.
- An MDB running in each cluster of clustered environment triggers its
own
onMessage() and loads the changed configuration Java
object.
A Component Should Have a Proper Third-Party Integration Mechanism
If the component depends on third-party integration to get a service,
the third-party API should not be directly used in the implementation class.
The best strategy would be to develop an adapter and isolate third-party
calls to the adapter implementations.
Figure 8 shows an example adapter used by the logger component, which
shows how it can adapt to different third-party APIs easily.
Figure 8. Application logger interface
The advantage of using this adapter pattern is to incorporate different
third-party APIs easily. Furthermore, if those APIs change, the adapter
implementation will need to be changed, but the service that uses the
adapter interface will not need to change.
Choosing from several adapters is facilitated with a configuration XML
file, as described in the "A Component Should Be Configurable" section above.
A Component Should Have a Proper Error-Handling Mechanism
Each component will have its own exception-handling class, which will
help to catch the proper exceptions. It's assumed that we will have a separate
component, specific to the business at hand, to handle exceptions. This
component-specific exception class (Underwriter exception) will take the
required service out of the exception-handling component.
Figure 9. Component exception handling
This exception class is very specific to the Underwriter service and it extends
the enterprise base exception class. Its job is to wrap the exception that
occurred in this service class and re-throw it.
Conclusion
To summarize, here are the basic steps to put it together:
- As part of the application startup process,
ConfigManager will
read the different configuration XML files for different components with
the help of XMLizer (a separate component used for XML-to-Java object
conversion) and bind the configuration Java object with the JNDI tree
of an application server node.
- As part of the component startup process, the configuration object will
be read and accordingly, the relevant provider/adapter/service classes
will be instantiated.
- If changes happen in configuration,
ConfigManager will
read the changed XML and rebind the configuration object.
- The component will reload the configuration object and reinitialize with
its latest changes.
Coming back to where we started: a component framework
can be effective in adapting to changes in business and technology when you are
planning to develop a robust system. The best part of this conceptual framework
is that it completely isolates the component management/life cycle process
from the business logic and different third-party APIs, by introducing
the concept of different plug-and-play service providers. Even when
changes are made, you do not have to worry about the rest of the code, other
than changing/replacing the service provider. This in turn makes
the application more maintainable, adaptable, and robust.
Palash Ghosh
is a software architect with six years of expertise architecting, designing, and developing
Java/J2EE solutions.
Return to ONJava.com.