JaVa
   

Deciding When to Use EJB

One of the most important architectural decisions – with implications for deployment, testing, tool requirements and support – is whether to use EJB. In this section we'll examine some of the decisive considerations.

Using EJB to Implement a Distributed Architecture

Use of EJB may be dictated by the choice between a distributed and collocated architecture.

Important 

If we want a distributed app based on RMI/IIOP, EJB is the perfect J2EE technology to help us implement it.

By using EJBs with remote interfaces we let the J2EE server handle the details of remote access. The alternative of writing our own RMI infrastructure is far inferior: nonstandard, unnecessarily hard to implement, and likely to prove hard to maintain.

Transaction Management

Even if we don't require EJB to implement a distributed architecture, there may still be reasons to use EJB. In , I noted that Container-Managed Transactions (CMT) were probably the most compelling reason for using EJB. Let's examine J2EE transaction management in general, and how using EJB can help to simplify transactional apps.

Transaction Management in J2EE apps

A J2EE server must provide the following transaction services:

A J2EE server must be able to handle low-level transaction protocols such as X/Open XA that are understood by transactional resource managers used by J2EE apps. A resource manager is an EIS tier resource such as a database. Resource managers control persistent state, and are usually transactional. A J2EE server's transaction manager is responsible for transaction propagation between J2EE components, whether collocated or in separate JVMs, and from the J2EE server to transactional resource managers. For example, if a method on an EJB is invoked by a web tier object, any transaction context must be propagated. Transaction contexts are associated with threads within a JVM. A container must be able to use the transactional capabilities of IIOP to handle the transactional requirements of distributed apps by propagating transaction contexts between transaction managers. As IIOP is vendor-independent, this means that transaction propagation works across J2EE servers, even when they are supplied by different vendors. Part of the J2EE promise is that this low-level detail is completely hidden from developers. Developers can assume that any transactional resource supported by their app server will be managed appropriately if they use standard features of the J2EE platform: declarative transaction management (available in EJBs), or programmatic transaction management using JTA (available in all server-side J2EE components). This is an important boon, as the mechanics of ensuring transaction integrity across disparate resources is complex. For example, the server should support two-phase commit to achieve correct behavior for transactions involving multiple resource managers or multiple EJB containers. Two-phase commit ensures that even in a distributed scenario, all resources enlisted for a transaction either commit or roll back. This is achieved by handling a distributed transaction in two phases. The first issues a prepare request to all participating resource managers. Only when each resource manager has indicated that it is ready to commit its part of the combined operation, will a commit request be sent to all resource managers. Two-phase commit can fail in some scenarios. For example, if a resource manager indicates that it is ready to commit, but subsequently fails to commit on a commit request, integrity may be lost. However, two-phase commit is considered a very reliable strategy for distributed transactions. The J2EE specification does not currently require an app server to provide full two-phase commit support. However, it does hint at requiring this in future (refer to documentation supplied with app servers for the degree of two-phase commit support they provide). Any class running within a J2EE server, but outside the EJB container, can perform programmatic transaction management. This requires obtaining the UserTransaction interface from JNDI and using JTA directly. Any J2EE component may itself manage transactions on a transactional resource such as an RDBMS using an API specific to that resource – in the case of an RDBMS, the JDBC API, or perhaps the JDO API – rather than the integrated JTA. However, this is a poor choice unless J2EE does not offer transactional support for the resource in question. Such transactions are referred to as local transactions. They are "local" in being isolated from the integrated transaction management of the J2EE platform. The J2EE infrastructure, which does not know about local transactions, cannot automatically rollback local transactions if a component marks a J2EE transaction for rollback only. The developer would be left to rollback and commit in code, resulting in unduly complex code and failing to leverage a valuable service of the app server. Local transactions must be created, committed, and rolled back using resource-specific APIs, making apps inconsistent in their approach to transactions.

Important 

Don't use local transactions (such as JDBC transactions) in J2EE apps, unless the transactional resource in question isn't supported by the server's integrated transaction management. Instead, rely on the transaction management offered by the EJB container, or use JTA to manage transactions. By using local transactions, you deprive the container of the ability to integrate transaction management across transactional resources.

Transaction Management and EJB

EJBs are the J2EE components best equipped to manage transactions. EJBs benefit from both declarative and programmatic transaction management. In the declarative model, the developer does not need to write any JTA code, ensuring that business logic is not complicated by lower-level transaction management operations. Typically, session EJBs are used to manage transactions, with each method on a session bean delimiting one transaction. For beans choosing to use Container-Managed Transactions (CMT), the container declaratively sets transactional boundaries at method level, eliminating the need to write transaction management code in user apps. Since app developers decide what each method does, this neatly integrates transaction management into the Java language. With Bean-Managed Transactions (BMT), the developer writes code in the EJB itself to begin, commit or roll back transactions programmatically using JTA.

Bean-Managed Transactions

Only session beans and message-driven beans may use BMT. Entity beans must use CMT. Using BMT is an all or nothing choice. The choice of BMT or CMT is made at bean level, not at method level; so, if only one method of a bean has unusual transactional requirements that seem to require the use of BMT, all methods of the bean will be forced to manage their own transactions (refactoring the design to split the bean may be more appropriate). BMT has a different meaning for stateless and stateful session beans. In the case of stateless session beans, the container must detect the situation in which a stateless bean method has returned after starting a transaction, but without committing it or rolling it back. Since a stateless session bean cannot hold state for a particular client between method calls, each method call must complete a unit of work, so this is clearly a coding error. On detecting this situation, the container must react in the same way as to an uncaught throwable from an EJB method, rolling the transaction back and discarding the offending EJB instance. For similar reasons, the same rule applies to message-driven beans, which must conclude any open transaction before the onMessage() method returns. In the case of stateful session beans, however, the container is not entitled to view a method leaving a transaction open as a coding error. It is possible that several method calls of the stateless bean make up a transaction, and that further method calls are required to complete the unit of work. However, the danger of this scenario is obvious. The client might allow user wait time between calls to the methods in the single transaction. This will waste valuable transactional resources, and potentially lock out other users of resource managers such as relational databases. Worse still, the client might never call the method that ends the transaction. EJBs using BMT must not use the setRollbackOnly() method on the javax.ejb.EJBContext interface. This method is intended to allow easy transaction management for beans using CMT.

Important 

If an EJB is using BMT, it gains little advantage from the EJB container. The same transaction management approach will work without using EJB; the only plus in using EJB is that any transaction will be rolled back automatically in the event of an uncaught throwable. However, a finally block will take care of this situation in cleanly written code; alternatively, a framework class may be used to take care of cleanup in the case of errors.

Container-Managed Transactions

Using CMT is much simpler than using BMT. We don't need to write any transaction management code in our EJBs. We merely need to:

EJBs with CMT can also normally also use declarative control of transaction isolation level, although the EJB specification does not mandate how this should be achieved. Transaction isolation refers to the level of separation between concurrent transactions. We'll discuss this in the next chapter. The J2EE specification doesn't mandate how transaction isolation levels are set. Typically, a container will use an additional proprietary deployment descriptor such as WebLogic's weblogic-ejb-jar.xml EJB deployment descriptor to provide this information.

Important 

EJBs with CMT offer a simple transaction management model that meets most requirements without the need to write any transaction management code. Declarative transaction management with CMT is a strong argument for using EJB.

EJBs with BMT offer a poor-value combination of the complexity of EJB and the need to write custom transaction management code. Use EJBs with BMT only in the rare cases when CMT cannot be used to achieve the same result.

Transaction Management in the Sample app

The sample app is partly transactional. Therefore, the use of an EJB with a local interface is appropriate for the tutorialing process. This will enable us to use CMT and avoid direct use of JTA.

EJB and Authorization

Most J2EE components have programmatic access to the user's security principal to establish which roles the user is in. In the web container, this can be obtained from the javax.servlet.http.HttpServletRequest.getUserPrincipal() method; in the EJB container, from the javax.ejb.EJBContext.getCallerPrincipal() method. The javax.ejb.EJBContext.isCallerInRole() method provides a convenient way to check whether a user is in a particular role. Only asynchronous invocations received via JMS are not associated with principal information. However, EJBs have the alternative of declarative restriction of user principals. We can use the EJB deployment descriptor (ejb-jar.xml) to specify which methods are accessible to which user roles without needing to write a line of code. It's also possible to modify these restrictions without changing app code by redeploying the EJB. The web container offers declarative restriction of app URLs, but this delivers less fine-grained control. It also only protects business objects from unauthorized access via the web interface. In a collocated web app architecture, this is probably all the protection that business objects need, but it's insufficient for distributed apps. Even if we use EJB authorization, we will also normally implement web tier authorization – we don't want to permit users to attempt operations for which they lack authorization.

Declarative authorization is a valuable service which can indicate the use of EJB. However, it's less critical in collocated web apps, in which there is no risk of unauthorized access to EJBs.

EJB and Multi-threading

EJBs are written as though they are single-threaded. The EJB container performs the necessary synchronization behind the scenes. Sometimes this can greatly simplify app code, but, as I observed in , it is probably overrated as an argument for using EJB. Often EJB won't solve all our threading problems. Even in large apps, there is no requirement to write really complex concurrent code. Not to mention that EJB is not the only good solution for multi-threading. The sample app illustrates this. We don't need to use EJB to simplify threading. Simply by avoiding instance data, we can ensure that servlets and other presentation tier classes are threadsafe. The same approach can easily be extended to our stateless business objects. Threading issues are likely to be encountered in this app only where data caching is concerned. EJB won't help us with this problem, due to the EJB coding restrictions (discussed later in the chapter) and because we really want to cache data in the web tier, not the EJB tier, to maximize performance. Thus we'll address any threading issues we may encounter using third-party, non-EJB libraries such as Doug Lea's util.concurrent. This means that we don't need to write our own threadsafe caches from scratch, but don't need to rely on EJB for concurrent access.

Of course, where we have already decided to use EJB (in the tutorialing process), we will also take advantage of EJB's concurrency control.

Declarative Configuration Management

Another potential reason to use EJB is the mechanism it offers to manage environment variables and app "wiring" through the standard ejb-jar.xml deployment descriptor, enabling EJBs to be reconfigured without the need to modify Java code.

Such externalization of configuration is good practice. However, it is available to all J2EE components to some degree (for example, in the specification of named DataSource objects) and there are simpler ways, such as using ordinary JavaBeans, to separate configuration from code throughout J2EE apps (EJB deployment descriptors are verbose and complex, and only allow XML-based definitions). Hence this is not a strong argument for using EJB.

The Downside of EJB

Before we commit to using EJB, we must consider its negatives as well as positives.

So Much Infrastructure

All EJBs except message-driven beans require at least three Java classes to be implemented by the developer:

Each group of EJBs deployed in an EJB JAR deployment unit will need one or more XML deployment descriptors, which may be complex. At deployment time, the EJB container is responsible for generating the missing pieces of the EJB jigsaw: stubs and skeletons handling remote invocation, and the implementation of the local or remote home, and the local or remote interface. Code using EJBs is also more complex than code that uses ordinary Java objects. Callers must perform JNDI lookups to obtain references to EJB component interfaces and must deal with several types of exception. Why is all this necessary? To deliver services such as transparent transaction management, the container must have complete control over EJB instances at runtime. The use of container-generated classes enables the container to intercept calls to EJBs to manage instance lifecycles, threading, security, transaction demarcation and clustering. When EJB is used appropriately, this is a good tradeoff, enabling the container to take care of issues that would otherwise need to be addressed in user code. In such cases, "simple" non-EJB architectures may turn into a morass of DIY solutions to problems that have already been well addressed by EJB containers. However, if the payoff introduced by the EJB indirection isn't sufficient, we'll end up with a lot of infrastructure we don't really want. The orthodox J2EE view is that this doesn't matter: tools will synchronize all these classes. However, this is unrealistic. It's better to avoid complexity than to rely on tools to manage it.

Note 

The introduction of Java 1.3 dynamic proxies – which several EJB containers now use instead of container-generated classes – arguably makes the EJB APIs look dated. Using dynamic proxies, it would be possible to deliver the same container services with fewer special requirements of app code.

I'll spare you the EJB lifecycle diagrams. However, it is important to understand them, and if you're unfamiliar with them, I suggest that you refer to the relevant sections of the EJB 2.0 specification (http://java.oracle.com/products/ejb/docs.html).

Programming Restrictions Applying to EJBs

In we discussed the practical problems that EJBs can produce: essentially, greater complexity in development, deployment, and testing. There's another important problem area that must be considered before using EJB. Hidden away in section 24.1.2 of the EJB 2.0 Specification (page 494) is a section describing "the coding restrictions that a Bean Provider must follow to ensure that the enterprise bean is portable and can be deployed in any compliant EJB 2.0 Container". This section of the specification is essential reading for all developers working with EJB. Not only does awareness of the restrictions enable us to avoid violating them; it also helps towards understanding the EJB model. Here we discuss some of the most important parts of those restrictions and the rationale behind them. The individual restrictions can be attributed to three main purposes:

The following table summarises the most important restrictions and their implications for EJB developers:

Restriction

Restriction category

Notes

Implications for J2EE developers

EJBs should not use read/write static fields.

Clustering

May cause incorrect behavior in a cluster. There is no guarantee that the different JVMs in the cluster will have the same values for the static data.

This restriction makes it difficult to implement the Singleton design pattern in the EJB tier. We discuss this issue later. This restriction does not affect constants.

An EJB must not use thread synchronization.

Thread management Clustering

It is the responsibility of the container, not the bean developer, to perform thread management. Synchronization introduces the risk of deadlock. Synchronization cannot be guaranteed to work as expected if the EJB container distributes instances of a particular EJB across multiple JVMs in a cluster.

This restriction causes few problems in apps that use EJB appropriately, and don't attempt to solve every problem with EJB. One of the main purposes of using EJB is to avoid the need to implement multi-threaded code. The use of synchronization is not necessary, as the container manages concurrent access to the EJB tier.

An EJB must not manipulate threads. This includes starting, stopping, suspending or resuming a thread, or changing a thread's priority or name.

Thread management

It is the responsibility of the container to manage threads. If EJB instances manage threads, the container cannot control crucial reliability and performance issues such as the total number of threads in the server instance. Allowing EJB instances to create threads would also make it impossible to guarantee that an EJB method's work is done in a particular transaction context.

Asynchronous activity can be achieved using JMS messaging. The container can ensure efficient load balancing of asynchronous activity initiated using JMS. If JMS is too heavyweight an approach to achieving the required asynchronous functionality, use of EJB may be inappropriate.

An EJB must not attempt to output information to a GUI display or accept keyboard input.

Miscellaneous

EJBs are not presentational components, so there is no reason to violate this restriction. An EJB container may run on a server without a display (in "headless" mode).

None

An EJB must not access the local file system (for example, using the java.io package).

Clustering

If EJBs rely on the file system, they may behave unpredictably in a cluster in which different servers may hold the same file with different content. There's no guarantee that an EJB container will even have access to a file system in all servers.

As EJBs are usually transactional components, it doesn't make sense to write data to non-transactional storage such as a file system. If files such as properties files are needed for read-only access, they can be included in an EJB JAR file and loaded via the class loader. Alternatively, read-only data can be placed in the bean's JNDI environment. EJBs are primarily intended to work with enterprise data stores such as databases.

An EJB must not accept connections on a socket or use a socket for multicast.

Miscellaneous

EJBs are intended to serve local or remote clients or consume messages. There is no reason for an EJB to be a network server.

None

An EJB should not attempt to use reflection to subvert Java visibility restrictions.

Security

For example, an EJB should not attempt to access a class or field that is not visible to it because it's private or package visible.

There's no restriction on the use of reflection unless it subverts security. Avoid methods such as java.lang.class.getDeclaredMethods() and java.lang.reflect.setAccessible(), but there's no problem with using java.lang.class.getMethods() (which returns public methods). Anything allowable without granting java.lang.reflect.ReflectPermission is acceptable.

An EJB must not attempt to create a classloader or obtain the current classloader.

Security

 

This prevents use of J2SE 1.3 dynamic proxies in EJB tier app code (as opposed to EJB container implementations), as dynamic proxy can't be constructed without access to the current classloader. However, there are few other reasons we'd want to obtain the current classloader in an EJB.

An EJB must not attempt to load a native library.

Security

If an EJB can load a native library, the sandbox is meaningless, as the EJB container cannot apply security restrictions to the native code. Allowing EJBs to invoke native code may also compromise the stability of the EJB container.

When it's necessary to access native code, EJB is not the correct approach.

An EJB must not attempt to modify security configuration objects (Policy, Security, Provider, Signer and Identity).

Security

 

When we choose to use EJB, we choose to leave the handling of security to the EJB container, so this restriction isn't a problem if we're using EJB appropriately.

An EJB must not attempt to use the subclass and object substitution features of the Java Serialization protocol.

Security

 

Doesn't affect normal use of serialization. Only those operations that require the granting of java.io.SerializablePermission are forbidden.

An EJB must not attempt to pass this as an argument or method result through its component interface.

Miscellaneous

Returning this prevents the container from intercepting EJB method invocations, defeating the purpose of modeling the object as an EJB.

There's a simple workaround to achieve the intended result. Obtain a reference to the EJB's remote or local component interface from the bean's EJBContext object.

Most of these restrictions aren't problematic in practice. Remember that we choose to use EJB if it helps to avoid the need to write low-level code, such as code that handles concurrent read-write access: it makes little sense to choose to use EJB and then battle against self-imposed restrictions. However, these restrictions do make some common problems hard to solve simply in the EJB tier. Let's consider some of the implications of these restrictions. How seriously should we take the EJB restriction on synchronization? The EJB specification is clear that EJBs shouldn't use synchronization in their own business methods, and (as we've seen) there is good reason for this. However, the specification doesn't make it clear whether it violates the specification if an EJB uses a helper class that uses synchronization. I think we need to be pragmatic here. Attempting business operations in EJBs that depend on synchronization (rather than EJB container thread management) reflects use of EJB where it simply doesn't fit, or a lack of understanding of EJB container services. However, some classes that we wish to use as helpers may perform some synchronization – or we may not even have the source code, and be unable to verify that they don't use synchronization. Logging packages and pre-Java 1.2 collection classes such as java.util.Vector and java.util.HashTable are common examples of packages that use synchronization. If we worry too much about this, we risk spending more time concerned with violating the EJB specification than implementing business logic.

We should satisfy ourselves that such classes don't use synchronization in a way that will break clustering and that their use of synchronization couldn't conflict with EJB container thread management (which might be the case if synchronization is used heavily).

The Singleton Problem in EJB

Developers often find that they want to use the Singleton design pattern – or a more object-oriented alternative that achieves the same ends, such as those we've discussed in – in the EJB tier. A common reason is to cache data, especially if it does not come directly from a persistent data store and entity beans are not appropriate. Unfortunately, singleton functionality is a poor fit with the EJB model. In this section, we consider some of the difficulties and potential workarounds.

Note 

Note that here I assume that the proposed use of a singleton is logical, even in a cluster. If the reason a singleton is inappropriate is because of clustering, EJB isn't imposing any unnecessary restrictions.

Java Singletons

Ordinary Java singletons – in which the Singleton pattern is enforced by a private constructor and the singleton instance held in a static member variable – can be used in the EJB tier, but are subject to limitations that seriously restrict their use. Since the EJB specification prevents the use of read-write static variables and synchronization, it's impossible to use singletons in many typical ways. This is an instance of the use of synchronization that violates both the spirit and letter of the EJB specification, as it involves app business logic. It is possible to avoid violating the restriction on read-write static data by initializing the singleton instance in a static initializer, as in the following example, which makes the static member final:

public class MySingleton {
 static final MySingleton instance;
 static {
 instance = new MySingleton();
 }
 public static MySingleton getInstance() {
 return instance;
 }
 private MySingleton() {
 }
}


Initializing the singleton in a static block is good practice outside the EJB tier, too. The common practice of instantiating the instance only if it's null in the static getInstance() method is susceptible to race conditions. Note that the static initializer shouldn't attempt to use JNDI, as there's no guarantee when the EJB container will instantiate the class. In the above example, the singleton instance could have been created when the instance variable was declared. However, using a static initializer is the only option if instantiating the singleton can result in an exception. It allows us to catch any exception and rethrow it in the getInstance() method. However, we're still likely to encounter problems, depending on what the singleton does. The likeliest problem is a race condition. As we can't use synchronization in the singleton's business methods and all EJBs running in the container will share the same singleton instance, we have no way of limiting concurrent access. This restricts the use of singletons for data caches: only if race conditions are acceptable can we cache data in a singleton. This may limit caching to read-only data. Use ordinary Java singletons in the EJB container only under the following circumstances:

RMI Objects

Given these restrictions, it seems a good idea to get singletons out of the EJB sandbox. One commonly suggested way of doing this is to make the singletons remote objects, and to access them from EJB code via RMI. However, implementing and accessing singletons as RMI objects is complex and error prone, and I don't recommend it. The singleton isn't managed by the EJB container, and will become a single point of failure. If the RMI server fails, the EJB container will have no way of dealing with the ensuing problems. Remote method invocation between EJBs and RMI object may also limit performance. And we certainly won't want to introduce RMI for this reason if we're using only EJBs with local interfaces.

Stateless Session Bean Pseudo-cache

Sometimes when something is particularly difficult to implement, it's worth taking a step back to look at what we're trying to achieve. What we're trying to achieve with singletons may be achievable with a different implementation strategy. Stateless session beans are created and destroyed infrequently by an EJB container. Each instance will normally service many clients in its lifetime. Thus a helper object maintained in a stateless session bean can be used to provide some of the caching services of a singleton: data held in it (and built up as an app runs) will be available through ordinary Java method invocation. The advantages of this approach are that it doesn't violate the EJB coding restrictions and is easy to implement. The issue of synchronization won't arise, as each SLSB instance will behave as if it is single threaded. The disadvantage is that we will end up with one "singleton" instance per session bean instance. In the case of stateless session beans, there will be at most tens of instances, as the stateless session bean lifecycle is independent of any individual client. However, the object duplication will be a problem if it's vital that all session bean instances hold the same cached data (which may be the point of using the singleton pattern).

This approach is of little value when using stateful session beans and of no value to entity beans, as these objects may change identity frequently. However, I can think of few legitimate reasons that an entity bean would need to use a singleton.

"Read-only" Entity Beans

If your EJB container supports "read-only" entity beans, it may be possible to use them as singletons. However, this approach isn't portable, as the EJB specification doesn't currently define read-only entities. The performance implications may be unclear, as locking strategies for read-only entities vary. I don't recommend this approach.

JMX or Startup Class

Another approach is to bind the singleton in JNDI using either proprietary app server functionality (such as WebLogic's "startup classes") or JMX. JBoss provides a particularly elegant way of binding JMX MBeans to a bean's environment. EJBs can then look it up at runtime. However, these approaches arguably remove the singleton from the EJB sandbox in appearance rather than reality. Synchronization, for example, will still violate the EJB coding restrictions. The Singleton design pattern is a problematic area in EJB. We've considered a number of workarounds, none of which is perfect.

Important 

When using a collocated architecture, the best solution for singleton functionality is usually to sidestep the EJB-singleton minefield and implement such functionality in the web container. This is consistent with a strategy of using EJB where it provides value; where it makes things harder, it is clearly not providing value.

When using a distributed architecture with remote interfaces exposing core business logic, singleton functionality supporting this business logic will need to stay in the EJB container. In this case we will probably need to choose the lesser of the evils discussed above in the context of the current app.

Timer Functionality

Another piece of commonly required functionality that is problematic to implement in the EJB container is timer or scheduling functionality. Imagine that we want an object in the EJB tier to be prompted regularly to perform a certain operation. Outside the EJB tier, we could simply create a background thread to do this, or use the java.util.Timer convenience class introduced in J2SE 1.3. However, both these approaches violate the EJB specification's restrictions on creating threads. There is no satisfactory standard solution to this longstanding problem in EJB 2.0, although EJB 2.1 introduces one. We will usually need to resort to server-specific solutions. JBoss and other servers provide components that address this issue.

EJBs in the Sample app

The sample app's reservation and tutorialing (confirmation) process is transactional. So by using EJB with CMT we can simplify app code by removing the need to use JTA directly. We don't require EJB thread management services; although we will probably need to implement some multi-threaded code in the web tier (see Caching later in the chapter), there's no obvious requirement for really complex multithreaded code.

A choice of not using EJB here would be quite defensible, as the app does not need EJBs to support remote clients – the one service that can not rationally be delivered without using EJB. While using JTA directly might be more error-prone (although not unduly complex), using a framework that offers declarative transaction management without the overhead of EJB would be a reasonable implementation choice.

JaVa
   
Comments