[ Team LiB ] Previous Section Next Section

18.2 Performance-Optimizing Design Patterns

This is not a book on design patterns. However, because of their importance to EJB design, this section lists design patterns that are particularly relevant. You can find articles that detail these performance-optimizing design patterns at http://www.JavaPerformanceTuning.com/tips/patterns.shtml.

18.2.1 Reducing the Number of Network Trips: The Value Object Pattern

A Value Object encapsulates a set of data values. Use a Value Object to encapsulate all of a business object's data attributes and access the Value Object remotely rather than accessing individual data attributes one at a time. The Value Object sends all data in one network transfer. Section 12.2 shows how to use the Value Object pattern to reduce the number of network transfers required to access multiple data attributes. The Value Object pattern can be used bidirectionally to improve performance—i.e., to minimize the number of network transfers to transfer data to the server as well as from the server. One variation, the Value Object Assembler pattern, uses a Session EJB to aggregate all required data from different EJBs as various types of Value Objects.

Using a Value Object may result in very large objects being transferred if too many data attributes are combined into one Value Object. A large Value Object may still be more efficient than separate multiple remote requests, but typically, only a subset of the data held by a large Value Object is needed, in which case the large Value Object should be broken down into multiple smaller Value Objects, each holding the data subset required to satisfy its remote request. This last approach minimizes both the number of network transfers and the amount of transferred data.

Once transferred, the Value Object's data is no longer necessarily up to date. So if you use the Value Object to hold the data locally for a period of time (as a locally cached object), the data could be stale and you might need to refresh it according to your application's requirements.

18.2.2 Optimizing Database Access: The Data Access Object Pattern and the Fast Lane Reader Pattern

Use Data Access Objects to decouple business logic from data-access logic, allowing decoupling of data-access optimizations from other types of optimizations. Data Access Objects usually perform complex JDBC operations behind a simplified interface, providing a platform for optimizing those operations. Data Access Objects allow optimizations in bulk access and update for multiple EJBs, and also allow specialized optimizations by using database-specific optimized access features while keeping complexity low.

For read-only access to a set of data that does not change rapidly, use the Fast Lane Reader pattern, which bypasses the EJBs and uses a (possibly nontransactional) Data Access Object that encapsulates access to the data. The Data Access Object in the Fast Lane Reader pattern accesses the database to get all the required read-only data efficiently, avoiding the overhead of multiple EJB accesses to the database. The resulting data is transferred to the client using a Value Object. The Value Object can also be cached on the server for repeated use, improving performance even further. This means that the Fast Lane Reader pattern efficiently reads unchanging (or slowly changing) data from the server and displays all of the data in one transfer.

18.2.3 Efficiently Transferring Large Datasets: The Page-by-Page Iterator Pattern and the ValueListHandler Pattern

If long lists of data are returned by queries, use the Page-by-Page Iterator pattern. This pattern is used when the result set is large and the client may not need all of the results. It consists of a server-side object that holds data on the server and supplies batches of results to the client. When the client makes a request, the results of the request are held in a stream-like object on the server, and only the first "pageful" of results is returned. The client can control the page size, and when data from the next page needs to be viewed, the whole page is sent. Section 12.7 shows how to use a Page-by-Page Iterator pattern to reduce the amount of transferred data and improve client display time.

Note that the Page-by-Page Iterator pattern actually increases the number of transfers made. However, it is an essential pattern for any server handling multiple requests that may return large amounts of data to clients. When implementing the Page-by-Page Iterator pattern, you should try to avoid making copies of the data on the server. If the underlying collection data is concurrently altered, care should be taken to ensure the client gets consistent pages. There is no upper limit to the size of a result set that this pattern can handle.

The ValueListHandler pattern combines the Page-by-Page Iterator pattern with the Fast Lane Reader pattern. The ValueListHandler pattern avoids using multiple Entity beans to access the database. Instead, it uses Data Access Objects that explicitly query the database and return the data to the client in batches rather than in one big chunk, as in the Page-by-Page Iterator pattern.

18.2.4 Caching Services: The Service Locator, Verified Service Locator, and EJBHomeFactory Patterns

The Service Locator pattern improves performance by caching service objects with a high lookup cost. For example, EJBHome objects and other JNDI lookups are often costly, but need to be performed regularly. However, many such objects are infrequently changed and thus ideal for caching. The Service Locator pattern simply interposes a Service Locator between the object initiating the lookup and the actual lookup. The Service Locator caches any looked-up object and returns the cached object where possible.

The Verified Service Locator pattern anticipates that objects in the Service Locator cache occasionally become stale and need to be refreshed. The Verified Service Locator periodically and asynchronously tests the cache elements to identify and refresh stale objects. An asynchronous periodic test minimizes the impact of stale objects to callers of the service, which would otherwise require a time-consuming synchronous call to obtain a refreshed service object. The Verified Service Locator pattern is just one variety of cache-element management among many, such as least-recently-used, element timed expiration, etc. The Verified Service Locator pattern element management is appropriate for JNDI lookups, when cache elements need to be refreshed only when the JNDI server is restarted, which should be infrequently.

The EJBHomeFactory pattern is simply a ServiceLocator dedicated to EJBHome objects. It is such a frequently mentioned optimization that it was given its own name.

18.2.5 Combining EJBs: The Session Façade and CompositeEntity Patterns

Use a Session Façade to provide a simple interface to a complex subsystem of enterprise beans and to reduce network communication requirements. The Session Façade is normally a session bean that encapsulates the interfaces needed to work efficiently with a set of EJBs. The client communicates efficiently with the session bean, which in turn manages all the EJB calls necessary to complete the operation represented by the Session Façade. The Session Façade can communicate with the EJBs by using local calls rather than remote calls, potentially making the whole operation much more efficient. Communication between the client and the Session Façade is often best handled using Value Objects so that EJB remote interfaces are not transferred across the network. The façade can also handle security and logging more efficiently than multiple EJBs, which would each separately require security checks and logging output.

The CompositeEntity pattern reduces the number of actual entity beans by wrapping multiple Java objects (which would each otherwise be an entity bean) into one entity bean. It is used less frequently than the Session Façade pattern.

18.2.6 Reusing Objects: The Factory and Builder Patterns

The Factory pattern allows optimizations to occur at the object-creation stage by redirecting object-creation calls to a factory object. Section 13.4.4 discusses this pattern.

Use the Builder pattern to break the construction of complex objects into a series of simpler Builder objects. A Director object combines the Builders to form a complex object. You can then use Recycler (a type of Director) to replace only the broken parts of the complex object, reducing the number of objects that need to be re-created.

18.2.7 Reducing Locking Conflicts: The Optimistic Locking Pattern

The Optimistic Locking pattern checks for data integrity only at update time and uses no locks. This feature increases the scalability of an application compared to pessimistic locking, since lock contention is avoided. The Optimistic Locking pattern is appropriate when concurrent access predominates over concurrent update (i.e., most sessions spend most of their time reading data, and very little time writing data). If sessions are transactional, transactions should be short.

Write-write conflicts with optimistic transactions can be detected using:

Timestamps

The updated row contains a timestamp field that should not be newer than when the row was accessed or the transaction started.

Version counters

A simple version counter is maintained and checked to ensure that it matches the version at transaction beginning.

State comparisons

At update time, all relevant database data is checked to ensure that it matches the "old" data.

Optimistic locking has high rollback costs when conflicts are detected, so it should not be used when conflicts are frequent.

18.2.8 Load Balancing: The Reactor and Front Controller Patterns

The Reactor pattern demultiplexes events and dispatches them to registered object handlers. It is similar to the Observer pattern (not described here), but the Observer handles only a single source of events, whereas the Reactor pattern handles multiple event sources. The Reactor pattern enables efficient load-balancing servers with multiplexing communications. The multiplexing of network I/O using NIO Selectors is an excellent example of the Reactor pattern. See Section 8.7.4.

The Front Controller pattern centralizes incoming client requests, channeling all client requests through a single decision point that lets you balance the application at runtime (see also Section 15.6). This pattern also allows optimizations in aggregating the resulting view.

18.2.9 Optimized Message Handling: The Proxy and Decorator Patterns

Proxy and Decorator objects let you redirect, batch, multiplex, and delay method invocations. They enable application partitioning by intelligently caching data or forwarding method invocations. See Section 12.4 and Section 12.6, which show how to use proxies to improve the efficiency of a distributed application. The Proxy pattern differentiates by Proxies often instantiating their real objects, while the Decorator pattern rarely does. A Proxy object is usually created as a wrapper on the "real" object, and other objects only ever get to handle the Proxy. The Decorator is more typically given the "real" object to wrap, allowing access to both the Decorator and the "real" object. Synchronized wrappers are an example of the Decorator pattern: you can pass the original collection object to the wrapper factory and access both the original collection and the wrapped collection.

18.2.10 Optimizing CPU Usage: The Message Façade Pattern

The Message Façade pattern encapsulates a method call into an object that can be executed asynchronously, allowing process flow to continue without blocking. This pattern is ideal for remotely invoked methods that don't need to return a value; remotely invoked methods that do return values can also be accommodated by storing the result for later retrieval.

    Previous Section Next Section