Feb. 16, 2011, 1:02 p.m.
posted by oxy
Item 9: Restrict EJB to transactional processing
This flies in the face of over four years of advice and suggestions from almost the entirety of the Java and J2EE community, but the fact is that EJB is not the core of J2EE. It's not even the "first among equals" in the J2EE community, despite the large number of articles, books, and conference presentations on it. EJB is simply another way to go about building back-end infrastructure and should be seen in the same light as anything else: use it only if you need it.
EJB has been the de facto solution for anything in the J2EE space for several years now. Thanks to a combination of marketing hype and some really cool-sounding features, EJB became the latest technology to join the ranks of the "over-hyped and under-delivered," right alongside client/server, distributed objects, "push" communications, and UML modeling tools. Java programmers looked at using EJB on a project as a perk, something "cool" to put on their résumés, a mark of respect among other developers. Just as many years ago it was commonly held that "Nobody ever got fired for buying IBM," nobody ever got fired for suggesting EJB. After all, if performance on the project wasn't meeting your standards, your current EJB vendor was to blame, and you could just "swap out" the vendor for a better implementation (presumably a more expensive and thus obviously better one) that would solve all of your performance and/or scalability issues. What could be better, right?
The problem is that EJB has been sold on a promise that it simply can't deliver—it can't magically keep developers from making stupid mistakes or from having to think about underlying issues like ClassLoader delegation trees, excessive network traffic, or lock contention. To believe that your EJB container will somehow provide all this magic functionality is just wishful thinking.
Let's go back for just a moment to reexamine what EJB was supposed to deliver. In concrete terms, EJB was supposed to make building distributed systems simpler and easier, by creating a common set of services that could be used by components (written to follow a strict regimen of rules and restrictions), allowing developers to focus on the "core business" of the problem at hand. Containers were supposed to take on crosscutting concerns that show up in every enterprise software project: remoting, concurrency, persistence, object lifecycle management, transaction support, and so on. Couple this with the container vendors' promises to make it all perform well, scale out as far as you could ever want, provide fault tolerance and failover so developers could relax knowing the system had everything under control, and allow you to plug-and-play any vendor container into your back end based on your desired price-to-performance ratio (what's sometimes called "best of breed" purchasing), and you have a very high set of expectations. (It's a floor wax, and look! It's a dessert topping, too!)
To say that EJB failed at these expectations is somewhat anticlimactic. In particular, it failed on several points.
Part of the stated goal of the EJB Specification is to make building distributed systems simpler; this has not happened. If anything, EJB has created an entirely new branch of complexity that stretches far beyond anything else the Java community has produced this far. Complex things are still complex: witness the "ease" by which we can establish a simple one-to-n relationship between two entity beans using container-managed persistence. Or look at how "easy" it is to retrieve those bean instances—it involves the use of EJBQL, a language that's amazingly similar to but substantially less powerful than the language it's supposed to replace, SQL.
Worse yet, however, EJB makes simple things complex, too. Consider what has to be one of the simplest acts in an enterprise system: consulting a read-only table. Without EJB, the solution would be to create a simple class that scans the database for the data at startup and holds that data internally, never going back to the database again. Under EJB, however, there is no easy solution for this problem. If we create a CMP entity bean to hold the read-only data, the container will generate spurious and unnecessary calls back to the database to retrieve the data that hasn't changed (under transactions, no less). If we create a Bean-Managed Persistence (BMP) entity bean instead, the container will still make spurious and unnecessary calls to the bean's ejbLoad and ejbStore methods, except now we can trap those and do nothing. But you're back to writing SQL. To avoid the multiple store/load calls and/or trips to the database, we could read the data into a stateless session bean in its ejbCreate method and hand the data back when requested. It means holding multiple copies of the data, however, up to one per client (since session beans are tied to client identity—see Item 5). Ouch. As a result, J2EE "best practices" recommend going either directly against the database for read-only (or read-mostly) data, or else use BMP entity beans; either way, you're back to writing SQL by hand, which is pretty much what entity beans are trying to prevent, as described in Item 40.
It gets worse. Because an EJB component requires an EJB container to execute within (as do servlets), testing an EJB implementation is difficult to say the least. Where a plain old Java object can be tested in one of three different ways—via a unit-testing framework like JUnit, via an embedded main method, or via a simple test program that exercises some of the basic functionality of the component—an EJB component can be tested in a container only from outside the container, and as of this writing there are no "mock object" EJB containers that an EJB component can be tested within, as there are for servlets (like Cactus). This means that writing bulletproof EJB code, although certainly not difficult, is just that much harder.
Part of that difficulty comes because EJB deployment itself is an order of magnitude more difficult than anything else yet developed for Java. When deployed, an EJB container has a tremendous amount of work to do, and this means some of that work will bleed back onto you, the deployer. This means your production deployment (see Item 14) will be more complicated, plus your normal compile-test cycle suddenly got longer—now it's compile-deploy-test, and if that deployment step requires human intervention, it's going to be really hard to get developers to actually test their work. "Hey, if it compiles, it must be good, right? Besides, I hate deploying that thing. . . ."
EJB isn't the enterprise developer's dream come true; it is, however, a useful technology when viewed in a more realistic light. The strength of the EJB Specification comes from its deep integration with the concept of transactional processing, a role historically played by transaction-processing systems like Tuxedo. As a result, for doing transaction-processing work, EJB is pretty hard to beat; in particular, because the EJB container enjoys a tight relationship with distributed transaction controllers (DTCs), EJB is by far the best choice when working with multiple transactional resource managers, like multiple databases, JMS messaging systems, and/or Connector resource adapters.
With the 2.0 release, EJB also makes it much simpler to do asynchronous JMS message processing with the addition of the message-driven bean. While it's not all that difficult to create a JMS processing host (no more difficult than it is to create an RMI object host, in fact), using EJB to host message-driven beans, particularly if those beans want to execute under transactional semantics, makes a lot of sense. More importantly, however, writing a message-driven bean is quite probably the simplest part of the EJB Specification: write a single class that implements the MessageDrivenBean interface, with no "remote" or "local" interfaces to worry about, just the deployment descriptor to describe the JMS Destination this bean should listen to, and the transactional affinity of the onMessage method (the sole non-EJB required method you have to implement). Coupled with the inherent flexibility messaging provides, an EJB container hosting nothing but message-driven beans makes a lot of sense and is scalable to boot.
In addition, many EJB vendor implementations offer extensions to make it simple to host RMI and/or CORBA object implementations, so while you may forgo the use of session, message-driven, and/or entity beans, don't necessarily throw the vendor's CD out the window just yet.
Again, the point here is that EJB provides a specific answer to a specific problem: providing transactionally aware middleware capabilities. To be more accurate, EJB provides distributed transactionally aware middleware, and if you're not going against multiple transactionally sensitive resource managers, it becomes difficult to see the real value proposition in using it.