Most Read This Week
. . . to build flexible, technology-independent enterprise systems
By: Rost Vashevnik
Dec. 8, 2004 12:00 AM
A challenge of software architecture is to create software that can grow with the business and withstand changes to the technology with minimal redevelopment costs.
Business growth usually means increased loads on enterprise computer systems. As more customers and staff come on board, they put more transactions through, and may also require more complex security and access control requirements. To meet these demands, the well-architected enterprise system should be able to morph and scale as much as possible without a major redevelopment effort. Therefore it seems a good idea to design flexible and scalable multitier enterprise systems from the start. However, at the beginning, projects are often neither able nor willing to invest in the extra complexities required to implement such systems. All too often enterprise systems start life as a simple two-tier JSP + database solution and are later reengineered or completely rewritten as a three-tier or even four-tier architecture at great additional cost.
This article describes a simple approach to enterprise systems design and shows how the simple customization of the Java Naming and Directory Interface (JNDI) mechanism can be used to build highly adaptable and flexible applications where system features such as logging, caching, distribution, transaction management, asynchronous or synchronous invocations, and access control are bound into the application after it was built without the need to reengineer or even rebuild the application code.
The approach described in this article is an integral part of the MetaBoss Target Software Model and it has been used very successfully by the users of the MetaBoss. MetaBoss is an integrated suite of tools for the design, development, and management of software systems through modeling. It utilizes Model Driven Architecture concepts and is primarily oriented toward enterprises using Java-based tools and technologies. MetaBoss is a part of the growing family of Professional Open Source products. It is dual licensed and can be used under the Open Source GPL license or MetaBoss Commercial license. More details are available from www.metaboss.com.
Danger of Mixing Technology and Business Code Together
This impact can be observed in practically all areas where technology touches the application code. Here are some examples offered by remote invocation technologies.
The typical requirement is to use prescribed super interfaces to represent remote objects. Most of the technologies require that Java classes and interfaces representing remote objects implement certain interfaces or extend particular abstract classes. In CORBA we must use org.omg.CORBA.Object as a super interface of the remote service object. In J2EE we must use various super interfaces to build enterprise beans. The Web service interface too (at least when using JAX-RPC) requires you to extend java.rmi.Remote. I do acknowledge that most of these prescribed interfaces are very simple to implement, and typically there is absolutely nothing to do except specify that the class implements the interface. However, a side effect is that a number of technology-specific classes, interfaces, and their methods are visible to the business programmer.
The typical requirement is to use prescribed value types to represent remote call parameters. Most of the technologies document a list of supported Java value types that can be used as parameters passed in and out of the remote services. These lists are typically very rich and most of the commonly used types are supported. Each technology, however, still limits the number of Java types that can be used "as is" and leaves it up to the application code to deal with the others. This leads to technology limitations creeping up into the remote services interface design.
The typical requirement is to catch and process special exception types. Most of the technologies communicate network failures to the application layer via special types of exceptions specific to the particular technology. This means the application code may need to catch these technology-specific exceptions.
The typical requirement is to use prescribed coding patterns. Most of the technologies require the use of very specific coding patterns, especially when it comes to connecting to or disconnecting from the remote service, using pervasive services, etc. For example, to make a remote call to the J2EE enterprise bean, the client needs to obtain an instance of the bean home interface via JNDI, then use it to obtain the instance of the bean remote interface and finally make the remote call. For a CORBA client to do the same remote call, it has to use the ORB singleton to connect to the naming service, then use the naming service to obtain a remote object reference, then use the Helper class to narrow the reference, and finally make a call.
To illustrate what may happen when these technologies are not insulated from the application code, consider the following examples.
An application programmer of a CORBA-based system has decided to use an array of org.omg.CORBA.Object elements to keep or pass around the list of previously called business services. This somewhat short-sighted decision was made because org.omg.CORBA.Object was a very convenient common super interface for all of the services. This decision entrenches CORBA technology into the business code and makes it more difficult to move this implementation to any other technology.
An application programmer of a J2EE-based system needs to create an entity bean dealing with value types from the java.awt.geom package. The types in this package are value objects that help to represent geometric shapes such as Arc, Rectangle, etc. However, for a reason unknown to us they don't implement a Serializable interface (at least as of JDK 1.4.2). This means the application programmer will probably have to make the entity bean accept individual attributes comprising these values (i.e., separate X, Y, Height, and Width attributes instead of a Rectangle2D instance). The net effect is that the business application component design is impacted by the deficiencies of the technology.
An application programmer of a simple single-tier system has not given any attention to the future scalability and distribution requirements and has created a simple application using plain Java classes, without any logical layering or split of responsibilities between them. This decision makes it harder to split the system into the separate distributed components at a later stage.
Looking for a Better Approach
To achieve these goals we designed the programming model based on technology-independent business components and the use of the JNDI mechanism for application assembly (i.e., connecting components with their clients).
JNDI is an abstract framework that provides naming and directory functionality for Java programs. We chose JNDI because it has a number of advantages:
From the component user point of view, the programming model has the following features.
Application code is split into components. Each component has a certain well-defined set of responsibilities. This division is driven entirely by the business logic, business needs, and logical layering of the application. Most important, it's not limited or dictated by technology issues. As such, the decision to have an entity-like component or service-like component can be made in any application, not necessarily a J2EE one. This means the component boundaries are the only places where platform mechanisms, such as remote invocation or caching, can be plugged in.
Each component is exposed to its clients via the component interface - the plain Java interface that's not required to have any properties beyond those required by business logic. Methods in these interfaces are able to use any Java class as a parameter and are able to declare and throw any number of any kind of exceptions. As before, there are no technology-motivated requirements to include anything in the input or output parameter list or to throw any particular kind of exception.
Each component interface is identified by the unique identifier - we call it the component URL. The component URL is a string that's formed as follows:
component:/<fully qualified name of component interface>
Each component interface exposes a string constant named COMPONENT_URL that contains the unique identifier of the interface. Listing 1 contains the sample of the typical component interface with COMPONENT_URL string constant. Apart from the prefix, COMPONENT_URL is simply the fully qualified name of the Java interface exposed by this component.
To obtain an instance of the component, client code has to do a simple JNDI lookup (see Listing 2, lines 10 and 11). Note, the client code only needs to import and be aware of the component interface and nothing else. The actual instantiation or lookup of the particular component implementation occurs at runtime.
It may appear that this pattern looks similar to J2EE. It does; however, there are several key differences. MetaBoss components expose pure business logic interfaces, which are not polluted by technology-related "small print" as occurs with enterprise beans. Moreover, the designer of these components is not concerned about which remote invocation mechanism, if any, will be used. This means application components promise to perform logical operations without disclosing where the operation will be executed and how request and response signals will be transmitted (if indeed such a need to transmit exists).
From the component implementer point of view, the programming model has the following features:
Having created an application that follows this programming model, what can now be done with it, and how can the promised flexibility be used in practice?
Figure 1 shows a simple way to deploy our application where the component client and the component implementation code run in the same JVM without any additional mechanisms plugged in. It works well for the initial deployment of a simple application, perhaps a basic JSP application deployed entirely in a Java servlet engine such as Tomcat. Our experience has shown that this configuration is favored by business application developers because it offers them a way to test the business logic without the complexities of a full distributed deployment. In this scenario, JNDI is configured to return the actual component implementation as a result of the component lookup operation.
Figure 2 shows the introduction of the "invisible" architectural feature through the use of a special proxy. It works well for pluggable security, logging, or caching mechanisms. JNDI is configured to return an instance of the special proxy instead of the actual component implementation. Once the special proxy is invoked, it can do anything it likes before and/or after invoking the actual underlying implementation. The process of invoking the underlying implementation from the special proxy implementation is also based on JNDI lookup. Thanks to this use of the JNDI lookup pattern inside proxies, chains of proxies can be built. This means that the proxies created are simple, single purpose classes.
Figure 3 shows the introduction of the "invisible" remote invocation mechanism, again through use of a special proxy. In this case the special proxy consists of two parts: client and server. At the client side JNDI is configured to return the instance of a remote proxy client instead of the actual component implementation. This proxy client makes remote calls to the proxy server, which in turn obtains the underlying actual implementation, again via JNDI. Similar to the previous example, this offers an opportunity to build chains of proxies. As an example, the logging proxy could be configured to run on the client side, server side, or even both sides of the remote invocation proxy.
A key point to notice is that in all the above examples the original component implementation code and the component client code were not modified or rebuilt in any way.
Under the Hood
This is why MetaBoss includes a special JNDI service provider implementation. This implementation is packaged in a single MetaBossComponentNamingProvider Java archive, available from www.metaboss.com. It can be used as a standalone library, totally separate from the rest of the MetaBoss suite. It has the following features:
The Last Piece of the Puzzle - The Implementation Mapping
The mapping entry is a key=value pair where the key describes the lookup operation (in terms of who is looking up what) in the form:
com.metaboss.naming.component.<interface match expression>[/<client match expression>]
and the value describes what has to be returned from the lookup in the form:
<implementation match expression>[(<implementation match expression >[?])]
In more detail:
A Few Words About Aspect-Oriented Programming
AOP is a great approach and it can be used to separate the business code from technology. However, my experience is that it has a few weak points.
First, the AOP paradigm is not native to the Java language. Most of the AOP frameworks include the need to have an XML document or Javadoc tags that define the join points in the main code and the need to post process the Java byte code in order to "implant" the callbacks from the main code to the secondary code. Some other frameworks take a different approach and extend the Java language, which adds quite a bit of complexity and requires a special compiler. The bottom line is that AOP is not native to Java.
Another important weakness is that the main body of code has no idea or control over where join points will be located. Most of the AOP frameworks allow you to place join points almost anywhere without any limitations, such as entry to or exit from any method or access to any variable. This approach can present a problem if unsuspecting main code is impacted by something occurring in the advice code or visa versa. Examples of this are multithreading or locking, long execution time, and unexpected exceptions.
To illustrate why this lack of control may be dangerous, imagine my car's owner's manual. When talking about changing flat tires, it says "Be sure to use designated jacking positions provided on the car." When talking about towing it says "Vehicles fitted with IRS (Independent Rear Suspension) should always be tray towed." The manufacturer of my car has provided certain, well-defined join points for the lifting device and has not provided join points for tow cables simply because this particular car cannot be pulled. No doubt, attempts to ignore these original design limitations will be very damaging to my car.
Reader Feedback: Page 1 of 1
Subscribe to the World's Most Powerful Newsletters
Today's Top Reads