Let's introduce a few business scenarios that need attention while architecting and designing a solution to accommodate continuous changes in business:
One thing is clear: requirements change very fast as business and technology change. But with every change, big or small, do we need to throw away the complete system and start over? Not necessarily--a little thought, a good strategy, and best practices during architecting and designing a new solution could adapt the existing architecture to those changes without much hassle.
In object-oriented programming and distributed object technology, a component is a set of classes and interfaces to fulfill requirements (functional and non-functional) with a reusable external API. Components should be able to run in a distributed network environment to form a network application. Component-based design and development is not a new topic at all to professionals who are following Object-oriented analysis and design (OOAD) methodology.
The goal of this article is to arrive at a common conceptual framework to develop a Java component step by step, following Java best design practices, and starting from scratch. The expected audience of this article needs to have prior knowledge of Java and UML, and Java/J2EE design patterns. The key areas to be addressed in the this article are:
To meet the definition, a component must satisfy the following requirements:
Whenever we write 100 to 1000 lines of code in a single class or number of classes, ultimately the work product (the class or the combination of classes) provides some basic high-level services. Thinking backwards, we can identify those basic high-level services we are trying to achieve, even before implementing them.
Let's give an example from the insurance domain, where an underwriter does the following tasks in his day-to-day activities:
So now if we try to write an Underwriter business component, we will have to have a service interface and its implementation, as seen in Figure 1:
Figure 1. Underwriter service interface
When another component requests a service from the Underwriter component, it doesn't need to worry about what is happening inside of the component. Encapsulating its business logic within itself makes the component more maintainable and extensible.
The Service component will have one main implementation service class (an implementation of the Service interface) and this class may make use of helper classes that are part of this component, and perhaps use other components, too.
In product development, we may have many components providing different types of services. For example, in the insurance field, we could have a "claim-processing component," a "policy-holder service component," and more. So we must have a strategy to register those service components in the enterprise solution, in order to be able to enable or disable those services according to the specific needs of the enterprise.
Here is an example XML structure, which can handle this service registration process automatically.
<Services> <Service> <Serviceid>S001</Serviceid> <ServiceName>UnderwriterService</ServiceName> <ServiceImplClass> com.org.service.UnderWriterServiceImpl </ServiceImplClass> </Service> <Service> <ServiceId>S002</ServiceId> <Servicename>PolicyHolderService</ServiceName> <ServiceImplClass> com.org.service.PolicyHolderServiceImpl </ServiceImplClass> </Service> </Services>
The component also needs a built-in, visible, independent mechanism in its
life cycle so it can be started and stopped as needed. This
a singleton, since only one instance is needed. This factory is responsible
for creating instances of a class for different providers based on the configuration
ComponentControllerFactory plays a dual role: first it manages
the component life cycle with its
methods (which is why it's a "controller"), and secondly it instantiates the
class based on the parameter passed to it (which is why it's a "factory").
Figure 2 shows its methods.
Figure 2. Component controller factory
The component life cycles methods are:
doStart(): Starts the component
doInitialize(): Helps to read from the
Configurationobject created from XML configuration file and is responsible of creating an instance of the appropriate class.
doStop(): Stops the component
reload(): If changes occur in configuration XML file when the component has been started already, this method will read the configuration XML file once again and restart the component.
getInstance(): Returns the instance of the
Generally, every component will have its own configurable parameters, which
are not required to change often. For example, suppose we need to write a
which needs to be refreshed every hour to load some semi-static data from a
database. The value of the refresh time should come from a configuration
file, so that the value of that the parameter can be changed without touching the
Here is one example of a configuration XML file for a logger component, which takes care of logging throughout all the layers of an enterprise.
<LoggingServiceProvider> <Provider> <ProviderName>Apache</ProviderName> <AdapterImpl>com.org.integration.adapter.Log4jAdapter </AdapterImpl> <Enable>true</Enable> </Provider> <Provider> <ProviderName>WebLogic</ProviderName> <AdapterImpl>com.org.integration.adapter.WebLogicAdapter </AdapterImpl> <Enable>false</Enable> </Provider> </LoggingServiceProvider>
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
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.
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:
If application will run in a single JVM, things are simpler. As we know,
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:
Figure 4 shows the methods of the
ConfigManager class acts as an
when it's notified by the
will be called. The
update() method will call the
SingletonControllerFactory so that the newly created Java
object will reload its configuration information.
ConfigurationChangeNotifier acts as an
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.
In a multiple JVM scenario, things are not so simple. We have to have:
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
will do the following tasks:
Serializableobject from the XML file. Serialization and de-serialization can be done by different components.
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:
ConfigManager in a multiple-JVM scenario
ConfigManagerMultipleJVM class acts as a
When it is notified by the Observable, its
update method will
be called. Within the
update() method, the
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
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:
SingletonControllerFactoryinitializes 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.
onMessage()and loads the changed configuration Java object.
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.
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.
To summarize, here are the basic steps to put it together:
ConfigManagerwill 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.
ConfigManagerwill read the changed XML and rebind the configuration object.
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 BEA Certified Enterprise Architect, IBM Cerified OOAD & SOA Soln Designer and has more than 11 years of software architecting, designing, management experience working with the global fortune 100 companies focusing on providing business and technology solutions across diverse range of technologies.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.