ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Java Component Development: A Conceptual Framework

by Palash Ghosh
03/23/2005

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:

Basic Properties of a Component

To meet the definition, a component must satisfy the following requirements:

How to Achieve Basic Component Properties

A Component Must Have a Service Interface (API)

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
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>
    

A Component Should Have a Proper Life Cycle Mechanism

The component also needs a built-in, visible, independent mechanism in its life cycle so it can be started and stopped as needed. This ComponentControllerFactory is 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 information. ComponentControllerFactory plays a dual role: first it manages the component life cycle with its init(), reload(), etc. 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
Figure 2. Component controller factory

The component life cycles methods are:

A Component Should Be Configurable

Generally, every component will have its own configurable parameters, which are not required to change often. For example, suppose we need to write a Cache component, 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 source code.

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>
    

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
Figure 3. Component controller factory in a single JVM

The methods provided by the singleton controller factory are:

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

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:

Figure 4 shows the methods of the ConfigManager.

Figure 4
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
Figure 5. ConfigurationChangeNotifier

A Multiple-JVM Case

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 ConfigManager, which will do the following tasks:

Figure 6 shows the RMI service interface and its implementation.

Figure 6
Figure 6. RMI service

In the multiple-JVM scenario, the ConfigManager will look like Figure 7:

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:

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
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
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.

Related Reading

Head First EJB
Passing the Sun Certified Business Component Developer Exam
By Kathy Sierra, Bert Bates

Conclusion

To summarize, here are the basic steps to put it together:

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.