Skip to main content

A Client Side Container for J2EE?

Posted by maciejz on July 8, 2003 at 7:36 AM PDT

J2EE has placed a lot of emphasis on the middle tier -- so much so, that the development of J2EE clients has gone pretty much ignored. The J2EE patterns, that to a large extent deal with the interaction between the EJB clients and the EJB tier, provide only a stop gap solution.

I guess that before getting much further into things I should explain what I mean when I use the term "EJB clients." At the simplest level, an EJB client is the layer that sits right above the EJB layer. What complicates things is the fact that this EJB client layer can be deployed either in the middle tier or in the client tier, depending on whether it is part of a web based application or a rich client one. In a web based app, the layer right above the EJB layer is found in the web container, still in the middle tier. Whereas in the case of a rich client app (think swing GUI talking to an EJB back end), the layer right above the EJB layer would be found on the client tier deployed with the GUI portion of the app.

ACID Properties of Business Transactions

So why am I bringing up the issue of a client side container? Because whether we realize it or not, most of the applications that we build need to exhibit "business transactions" with ACID properties. I've been developing enterprise level applications for quite a few years now, and while the apps that I've worked on do possess ACID properties for business transactions, I've never actually called it that until I read Martin Fowler's book "Patterns of Enterprise Application Architecture." I find this label "ACID properties of Business Transactions" very appealing since it allows me to put a familiar face on what have up till now been related but separate concepts in my mind. Lets take a look at these so called ACID properties of Business Transactions in more detail.

Take a typical order entry process as a simple example. A typical order entry process contains 3 or 4 different screens that a user must fill out. On the first screen the user may select their products and quantities; on the second screen the user may provide shipping information; on the third screen the billing information; and moving to the fourth screen may actually process the order and display an invoice.

Atomicity
We take it for granted that the entire order entry transaction is going to be atomic. If the user cancels the order on the third screen, after selecting the products and providing shipping information, we do not expect to find the items we "almost" ordered on our doorstep in a few days along with a bill. Why not? After all, we did complete the corresponding steps; we selected the items and we provided the delivery address. But we also expected that the entire order process would be atomic; ether succeeding in its entirety or not at all. In the O/R mapping world, support for atomic business transactions has been around for a while in the form of Units of Work.

Consistency
Business transactions must leave the system in a consistent state at their completion. This is another one of those properties that we take for granted no matter how many validation rules we write. And we have a lot of help from frameworks such as Struts when it comes to writing those validation rules. But one thing that has always struck me as odd about such frameworks is that they tend to place the validation logic in the presentation layer rather than the domain layer. In one of the first J2EE applications that I worked on (in the days before Struts), we also put the validation rules in the presentation layer (servlets, as this started out as a web app). The problem was that after completing the web app., management wanted a Web Services front end on the same app so that clients and partners could feed data in automatically; and then a rich client front end. As you've probably guessed, we had to rip out all those validation rules from the servlets and put them into the domain layer so that they could be reused by all the different front ends.

Anyone that has worked on applications related to the insurance industry can appreciate the fact that getting the validation rules right (ensuring that the transaction leaves the system in a consistent state) is one of the more time consuming and error prone parts of the development cycle. That's because there are four level to input validation, each one getting progressively harder to implement and maintain. The first level is syntax validation. Basically checking that a phone number has 10 digits, that a name contains only characters, etc. The next level is semantic validation; ensuring that the value of an age field (despite being syntactically correct as an integer) has a positive value; verifying that the 5 digit number is a valid zip code, etc. Then, we go beyond field level validation into object level. If checkbox A is checked then fields B and C are required, otherwise they're optional. This is where things start to eat up time. The last level is inter-object validation. It might seem like the same thing as the previous level, but it makes a big difference when it comes to implementing reusable mechanism for validation.

Isolation
Although I have never tried this, I could go to Amazon, open up two browser windows and start entering an order in each. In the first window I might order a new book for myself (I hear "Bitter EJB" is up my alley :), and in the second window I might order a book for my father (sort of a belated father's day present). In each window I specify the appropriate shipping address; Maryland for my father and Ohio for me. I would be very surprised if the book I ordered for my father wound up on my door step in a few days. That might in fact cause me to vow never to use Amazon again. But that is exactly what may happen if the two business transactions are not isolated from each other. Simple sessions will not suffice in this scenario since both browser windows share the same session. But the Unit of Work concept can provide the basis for a proper level of isolation.

Durability
There is not much to be said about durability without being trivial. Of course I expect Amazon to have my record on file for some time even after they charge my credit card. I expect this even if their server goes down. This is where business transactions reach down into the domain of system transactions. Durability as a property of transactional resources has been studied at great length and I don't really have anything new to say about it :)

Client Container Enabling ACID Properties of Business Transactions

In the above discussion, we've identified two mechanisms that would make the development of EJB clients a lot easier: Units of Work, and support for Validation Rules at the domain layer. I would also argue that there is a third mechanism that is needed at the client layer: caching.

Caching
Caching is a difficult nut to crack because there is no "one size fits all" solution. Because of this, it is difficult to make caching choices at the framework level. In order to truly capture the benefit of caching, often times caching strategies have to be tailored to the application. For this reason, a client side container should not strive to provide a specific caching implementation, rather it should provide a hook for pluggable caching strategies.

Pluggable caching strategies would allow the application deployer to fine tune the system. Concrete implementation of caching strategies could provide an application level scoped cache, or a session scoped cache, or even a request scoped cache. Further, a concrete caching strategy could be built on top of a distributed cache, thus making the age old question of cache synchronization a little easier to deal with.

Cache != State
In recent months there has been a growing sentiment against state-full services. On the whole, I agree that when it comes to multi-tier enterprise level applications, whenever we can get rid of state that is a good thing. But I feel compelled to point out that making a service stateless is not the same thing as getting rid of caching. A cache is not what makes a service state-full. Although the machanisms that make the service stateful may be very similar to the mechanisms used to implement a cache, the two are not the same. The difference comes down to this: if you loose the cache (perhaps due to a server crash) you did not actually loose any non-recoverable data; whereas if you loose the state of a state-full service, there is an unrecoverable data loss. Sure there are repercussions to loosing a cache: the performance may suffer somewhat in the short term as the new cache gets populated again, but it's nothing compared to the potentially catastrophic consequences of loosing the state. So don't shy away from using caching, it is perhaps the best weapon we have against poor performance.

Client Side Deployment Descriptors?
Deployment descriptors are not my favorite feature of J2EE, but they do serve a very useful purpose. Perhaps the most annoying thing about deployment descriptors is that their purpose is to allow customization to your specific deployment environment, yet they're packaged inside a reusable binary. So if you do want to customize a DD, you have to break apart the binary, customize, and then reassemble; or use a tool that essentially does the same thing.

At the very least, client side deployment descriptors can be used to configure the validation rules and the caching strategies. Keep in mind that I'm talking about validation rules that are at the domain object level, not at the presentation level. I'm talking about client side components that plug equally well into web applications and rich client applications.

Conclusion

Reading over this post I feel that I have not made a really compelling argument for a client side container. To make a truly strong argument would require a much more lengthy and detailed discussion. Not that I'm shying away from the challenge, on the contrary, I'll just have to approach this problem piece meal ;)

Related Topics >>