J2EE Service Wrapper Pattern Candidate September 9th, 2003
Recently, I was tasked with converting a J2EE application's EJB references from remotes to locals, as part of the run-up to initial launch deployment on a single VM. The application features 24 entity beans and 50 session beans, all hand-coded without the use of code generation tools like xdoclet. The exercise made for one of the worst weeks of my working career.
It wasn't the pain of bringing unmaintained local interfaces up to speed with changes made only to the remote interfaces and beans. Nor was it the gruelling effort of tidying up imports and catch clauses for unthrown exceptions after the scripted regular expression edits on client code. It wasn't even the changes that couldn't be tested at compile time, like replacing casts. No, all of this was bearable.
The real soul sucker was knowing that I wasn't making it any easier to handle the undoing of the changes one day when the application outgrows a single VM.
This seems to be a problem that isn't addressed by a published J2EE design pattern. We have the Service Locator pattern, which encapsulates the details of acquiring a reference to a service in a distributed environment. What's missing is a Service Wrapper, that encapsulates the locality of a service.
I think this is a problem to which we should apply the OO design mantra, "Encapsulate the concept that varies." What varies between the use of local and remote references to EJB homes and EJB objects?
-
The methods on an EJB remote interface must throw
javax.rmi.RemoteException, but the methods on an EJB local interface must not throw it. This means that you cannot derive both interfaces from a common ancestor, by which clients refer to either one. So the client is exposed to the locality of EJB object references. When you change from using local interfaces to remote interfaces (or vice versa), you have to add (or remove) exception handling from the client code. -
Certain methods on EJB local homes must return EJB local interfaces, while those on EJB remote homes must return EJB remote interfaces. Since client code must know the locality these returned interfaces (as explained above), it must know the locality of the EJB homes as well.
Were it not for these differences, client code would be able to interact with remote and local interfaces interchangeably without modification. While type-based conditional behavior could be introduced into the client, this would result in serious code duplication and degradation of business focus in the client code. So that's not the answer. But these differences can be encapsulated, and I'm suggesting that, in sufficiently large systems, they should be.
Right, enough nancying about.
What locality-related implementation specifics are we trying to isolate client code from?
-
Variant return types. We can address variant return types by introducing wrappers around locality-biased types and returning those wrappers instead.
-
variant exception handling. We can address variant exception throwing by handling the deviant exception,
RemoteException, in the wrappers.
This kind of wrapping is a realization of the Proxy pattern. What proxying wrappers must be introduced to guarantee isolation of client code from locality-related implementation specifics?
-
ObjectWrapper. This class encapsulates the locality of an EJB interface. It contains either a remote or local EJB interface, and proxies all method requests to that interface. The implementation of this wrapper is necessarily ugly, but protects client code from type-based conditional behavior. In particular, when a remote EJB interface is used, the wrapper must rethrow
RemoteExceptionas an application exception. -
HomeWrapper. This class encapsulates the locality of an EJB home. It contains either a remote or local EJB home, and proxies all method requests to that home. The implementation of this wrapper is also ugly. Not only must it rethrow
RemoteException, but it must also wrap in ObjectWrappers, any EJB interfaces returned by the methods on the contained EJB home, and then return those ObjectWrappers instead.
What's are our points of entry?
-
We use the Service Locator as a single point of entry into this layer of encapsulation.
-
The Service Locator is adjusted to return HomeWrappers instead of EJB remote or local homes. Whether the EJB home contained within any given HomeWrapper should be remote or local can be hardcoded into the Service Locator, or determined from a locality map of arbitrary complexity (e.g. properties file, LDAP tree).
-
The HomeWrappers return ObjectWrappers instead of EJB remote or local interfaces. The encapsulated locality of the HomeWrapper determines the locality of the EJB interface to be burried within an ObjectWrapper.
-
Both wrappers isolate client code from
RemoteException. -
Client code is adjusted to work with the wrappers instead of remote and/or local homes.
-
The only parts of the system to which EJB remote or local homes and EJB remote or local interfaces are exposed, are the Service Locator and the Service Wrapper layer, consisting of the HomeWrappers and ObjectWrappers.
Note that two very different animals are being proxied here; EJB homes and EJB interfaces. But for each one, the same concept is being encapsulated: locality. So even though I'm proposing the introduction of a layer of indirection over two things, they form a single conceptual layer, and together comprise the Service Wrapper. Indeed, introducing one wrapper without the other doesn't solve the problem at all.
A potential point of controversy is the encapsulation of exception handling. I don't see this as a problem for two reasons:
-
Based on my project exposure and the code excerpts I've seen from other projects and literature, it seems that most EJB applications don't respond to
RemoteExceptionwith any degree of intelligence. They either allow the exception to percolate up to the client unmolested, or wrap and rethrow them in a manner that contributes nothing to the cause of failure recovery. At best, they wrap and rethrow in a way that makes it easier for developers to see the context in which failure occurred. -
Encapsulation of exception handling within the Service Wrapper layer can actually have failure recovery benefits. This is because the layer introduces a convenient place to put failure recovery code that would otherwise need to be scattered throughout client code. For example, the Service Wrapper layer could respond to transient network partitioning by implementing a retry algorithm, which could completely isolate clients from a significant subset of such errors, such as rebooted routing devices and remote application servers.
The class diagram below shows the introduction of one HomeWrapper and one ObjectWrapper for every EJB located through the Service Locator. This significantly increases the amount of grunt work required to write participating EJBs by hand. However, code generation tools such as xdoclet have gained tremendous support, and it wouldn't take much effort to reduce the labor to a few xdoclet tags. In fact, in the project that sparked off this line of thinking, I'm waiting for The Great XDocletization subproject to complete before applying this approach. It's just too much work to hand-code for over 70 EJBs.
In the interests of simplicity, I've only shown delegation of a single home method, create(). The important features are:
-
ServiceLocator creates instances of HomeWrapper, populating either localHome or remoteHome and setting isLocal accordingly.
-
HomeWrapper delegates to localHome or remoteHome based on the value of isLocal. This is ugly, and may benefit from application of the State pattern.
-
HomeWrapper creates instances of ObjectWrapper, populating either localObject or remoteObject and setting isLocal accordingly.
-
ObjectWrapper delegates to localObject or remoteObject based on the value of isLocal. This is ugly, and may benefit from application of the State pattern.
Here's a sequence diagram showing a simple interaction, in which the client requests a HomeWrapper, uses it to create a new EJB object, and accesses that object through an ObjectWrapper.
This example is simple. Wrapped finders are a little less simple, because they can return collections of EJB interfaces. In such cases, the proxying method on the HomeWrapper must return a new collection containing each EJB interface wrapped in an ObjectWrapper. Note that the code fragments in the class diagram only hint at conditional handling of locality; they don't show wrappers encapsulating variant return types.
The introduction of a Service Wrapper layer is a non-trivial exercise for large, existing applications. I expect that it's infeasible for most such applications whose EJBs are hand-coded. Mind you, if I'd thought this through properly before I did my painful remote-to-local sweep, I'd have done this as a precursor to the sweep.
Like many J2EE solutions, this is a solution to a complex problem and comes with its own complications. As always, a decision must be made as to whether the application is likely to scale to the point where this solution's additional baggage begins to pay for itself. If that point is likely to be reached, then the Service Wrapper layer is something you want in place long before you get there; it's an expensive retrofit.
I'm very interested in hearing feedback on this idea. A colleague suggested using Dynamic Proxy Classes to reduce the amount of code required, and I'll certainly be looking into that. When I've applied this solution to the project that gave birth to it, I plan to publish a follow-up, complete with doclets and sample code, a reference to the wheel I've reinvented or an admission of abject failure. I'll include any interesting feedback in that follow-up. If you send me stuff but don't want your name associated with, please mention that explicitly. I tend to assume that people want credit where it's due.


hi there, useful post. what tool did you use to generate the UML diagrams ? thanks, BR, ~A
I used Poseidon UML, Community Edition for the UML diagrams.
I'm curious, don't most containers automatically use a local look up if they can even if defined as remote lookup? Seems like you can almost be safe using remote lookups which be optimized by local lookups by the container.
I'm thrilled to hear that modern EJB containers do that. I wrote the article in 2003, and at that time, I was confident that JBoss offered no such optimization. These days, I'd be surprised to hear that people are opting for remotes instead of RESTful web services.
Could you please explain what needs to be done so that the container automatically use the local look up if they can even if defined as remote lookup? does weblogic 8.1 offer such optimization?
Sorry, I can't help with that. I've managed to stay away from Java for a couple of years, now. "rickcr" suggested that EJB containers might implement my pattern for you. Because he said "most", I assumed he'd run into at least one that does. Sadly, Rick left no forwarding address.
Your blog is interesting! Keep up the good work!