5.4. The Solution

The proposed solution, the CORE system, used a fairly standard multitier distributed architecture. One interesting twist is that the network switches, essentially dedicated mainframes specifically deployed to perform voice-network routing, were modeled through an additional back-end tier, the process tier. Rather than represent data storage, this tier represented the actual physical network modeled in the CORE system.

5.4.1. Architecture Overview

Figure 5.1 illustrates the architecture of the CORE system.

Figure 5.1. CORE System Architecture


The tiers of this architecture consist of client tier, Web-server tier, application-server tier, database tier, and process tier.

  • Client Tier

    The CORE system was provided to clients through two user interfaces. One was a stand-alone Java application called the CORE desktop, which provided full application functionality. The other provided a subset of the desktop functionality through a Web-based thin client generated using Java Servlet technology. Both are discussed in more detail later in this section.

  • Web-Server Tier

    The Web-server tier was used to serve up the Web-based thin-client application and to run the servlets that provided dynamic interaction in the Web client.

  • Application-Server Tier

    The application-server tier consists of heterogeneous and homogeneous clusters of application servers and services, supporting load balancing and failover safety. One application server is installed on each node. Each node potentially has a different configuration. For example, the Borland AppServer allows some services to be run on a single machine to reduce interprocess communication in which one service is heavily utilized by another. Some services run outside the server process, allowing them to be quickly started and stopped without disruption to the rest of the system.

    Some nodes run multiple instances of services, such as the EJB container service. This allows load balancing and failover support through clustering, which could be turned on or off, without code or design changes—the implementation is totally transparent. This feature is also unaffected by physical changes to the network.

  • Database Tier

    The database tier consists of an Oracle database, running on the Sun Solaris platform. Since this was a new project, and no information system was present, there were no constraints in the design of the system, which allowed us to design and tune the database around the access and update mechanisms in the object model.

  • Process Tier

    The process tier consists of CORBA servers that can be activated on demand in a couple of ways: through an Object Activation Daemon or through active processes. CORBA servers are used to communicate with the switch devices, extracting network routing information and executing instructions for rerouting voice traffic.

    CORBA interaction was used extensively in the process tier because of the unusual device interface requirements and the need for a design feature like Active Objects. CORBA servers allowed the implementation of singletons, which could interface to I/O systems using different protocols, and which could live past a client invocation through thread management. This responsibility is placed in the CORBA server tier because of EJB restrictions in the application-server tier with respect to I/O and thread management.

5.4.2. Client Tier

The CORE desktop and its client-side framework are among the many innovations coming out of the CORE project. The success of the project can largely be attributed to the performance of the application, its ease of use, and the physical appeal of its user interface. These features are the result of a framework that enables objects and their properties, as well as actions and sub-parts to be displayed consistently throughout the whole desktop. This means that users can quickly become familiar and comfortable in the desktop surroundings. Architectural choices on the server side made more time available for developing a desktop of world-class quality. Because the CORE system improved the speed of developing new business components and their integration into the desktop, the system started to take on a bigger role within the company. As a result, the meaning of CORE was changed to Common Object Repository Environment, representing this new, expanded role of the framework. The architecture and the CORE desktop were considered to be redeployable as solutions in their own right and as references for other projects in the development pipeline.

The CORE desktop was built to bring the benefits of object-oriented technology to the user interface. The design goal was to build a framework similar to the JavaBeans technology used to build windows and components for user objects. The desktop application would need to interface with only one type of object, although there would be many types of objects in the system. Every user object would provide a mechanism to inspect its properties—how to display it in terms of name, icon and type, what actions could be executed, and any other objects (children) it contained. On top of this, each object would integrate into a client-side security framework. One goal of the framework was to reduce the amount of code needed to build these aspects into objects. Another goal was to avoid altering the user objects to reflect this requirement. The programming benefits of this framework include

  • Reduction in code

  • Faster and easier creation of frames

  • Greater maintainability

  • Focus on user-objects interaction

  • Increased extensibility and clean integration

  • Self-documentation of user objects

In terms of user interface design, the benefits include

  • Consistent appearance of user objects across windows

  • Inherent support for direct manipulation techniques

  • Integration of a fine-grain security model

The following example shows the typical code previously used for a user interface with a window containing a table. For every user object type displayed in the table, similar code is written. The result is a lot of type-specific, repetitive code. This code maybe be duplicated for the same class in different frames, which potentially leads to more bugs.

public Object getValueAt(int row, int col) {
CustomerDetails c = (CustomerDetails)_list.get(row);
switch(col) {
   case 0: return c.getFirstName();
   case 1: return c.getLastName();
   case 2: return c.getCompany();
   ...
 }
}

One solution to this problem to have all user objects support methods that let visual components access specific properties without having to know the method for each. This may be done by placing all the methods directly into each user-type object through inheritance from a base abstract object or from an interface. Either way, the user object is required to implement all the defined methods. This clutters up the user object with methods related to the windows environment, and forces development of more code.

The better approach taken in the client-side framework uses an interface developed specifically for maker purposes. This technique is similar to the java.io.Serializable interface. The interface Describeable indicates objects with a corresponding Descriptor object. On the client side, a DescriptorManager object, when given an object implementing the Describeable interface, returns an instance of a Descriptor. The descriptors are serialized from the server side, for reasons that become evident in looking at the Descriptor object and how it is created.

Here's a listing of the Descriptor interface as defined in the framework

public interface Descriptor extends java.io.Serializable, Securable {
 public String getName(Object obj);
 public String getType(Object obj);
 public String getIcon(Object obj);
 public List getActionDescriptors();
 public Visitor getActionDescriptors(Visitor v);
 public List getPropertyDescriptors();
 public Visitor getPropertyDescriptors(Visitor v);
 public PropertyDescriptor getPropertyDescriptor(String name);
 public SecurityDescriptor getSecurityDescriptor();
 public boolean allowsChildren();
 public List getChildren(Object obj);
 public Key getKey();
}

The aim of the descriptors is to provide a bridge between the graphical representation requirements and the user object, without the need to alter the user object's interface. Each class has an instance of a Descriptor, and when a user object needs to be displayed, the appropriate Descriptor is looked up. The client application uses the Descriptor, along with the user object, to effectively render the object-oriented user interface. For example, the client can request the icon and name for an object by passing the object to the Descriptor. The Descriptor can then determine the icon and name by examining the state of the object. In the case of the icon property, the Descriptor might just return a value for all instances of this type, or it might inspect the object's state for further refinement of the icon. This is the case with the trunk object in the CORE system, which can have state indicating whether it is leased or owned, each represented by a distinct icon in the user interface. User interface descriptors are mostly used by Swing user interface renderers, such as TableCellRenderer, TreeCellRenderer and ListCellRenderer.

To enforce security, the client also uses sub-components of a Descriptor, such as ObjectDescriptor, PropertyDescriptor, ActionDescriptor, and FolderDescriptor. One important requirement for this system is to provide a fine-grained security system, which does not get in the way of the user experience. While the current EJB specification deals with authorization at the server-side, it does not give any guidance about how to integrate this into a client application that relies heavily on context-sensitive menus. Security is enforced when a method is sent across the wire and reaches an object. The container that holds the object can then check if the user is authorized for this method call before dispatching. If the user is not authorized to execute the method, an exception is thrown. This reactive design allows the user to initiate operations that can't be completed. This can lead to frustration and violates good design principles, since a user interface should always try to protect the user from making such mistakes. To get around this problem, the CORE system implements a proactive security system on top of the reactive EJB security system.

Descriptors can enforce security at the object, property, and action levels. The level of access can be set to restricted, read-only, read-write. With restricted access, the user does not see the user object, property, folder, or action in the interface. With read-only access, the user is able to read a user object but not alter it, view an action but not execute it, view a property but not edit it. With read-write, the user can alter the state of a user object, execute an action from some menu-like control, or edit a property using some visual control.

This leaves one problem: how to generate the descriptors. If we created a separate class for each user type of object, the implementation still requires a lot of repetitive code. This repetition was key to the solution, since the reason for the repetition is to interface with a particular user object type and other associated objects, such as actions. By generalizing the code for a Descriptor and extracting the methods, classes, and caption names into an XML document, the CORE system can create a single implementation with different instances and different states relating to different user objects in the system.

Descriptors are created and initialized with values extracted from a single XML file. A DescriptorLookup session bean uses the XML file stored in a JDataStore database (Borland Java technology object-relational database) or some specified directory system to build objects that reflect the XML element contents. These objects use initialized values extracted from the XML file and the Java reflection API to work their magic. When the client logs on, these objects are streamed in bulk to the client side for caching and local execution.

Here is an extract from the XML for the user object ContactPersonDetails.

<object-descriptor>
  <class-name>ContactPersonDetails</class-name>
  <name>class:ContactPersonName</name>
  <type>string:Contact Person</type>
  <icon>string:contactperson</icon>
  <property-descriptor width="200">
    <name>Company</name>
    <method>getCompany</method>
  </property-descriptor>
  <property-descriptor>
     <name>First Name</name>
     <method>getFirstName</method>
  </property-descriptor>

  ...
  <action-descriptor>
    <name>Properties</name>
    <icon>property.editor</icon>
    <class-name>PropertyAction</class-name>
    <parameter>
      <name>home</name>
      <value>core/contactpersonmaintenance</value>
    </parameter>
  </action-descriptor>
  <security-descriptor>
    <security-role>
      <name>Traffic-Manager</name>
    </security-role>
  </security-descriptor>
</object-descriptor>

5.4.3. Web-Server Tier

The Web server tier provides a mechanism that simplifies application deployment while enabling a more flexible application update mechanism.

Version Control

The CORE desktop uses the Web server to access application versioning information. An XML file stored on the Web server contains information regarding the latest version of the desktop application. The application checks versioning information on startup and periodically thereafter. If versioning differences are detected, it triggers installation of new and modified jar files across the network. Jar file locations are specified in the XML file, which also contains information about the new components in the release stored on the server. The user is informed of the update through visual feedback listing each component added or changed.

Because most of the desktop application is configured through XML properties files, it was easy during the development phases to issue new releases that migrated applications to different CORBA domains, naming service instances, or different entry points in the naming service. Even JNDI names for bean homes could be configured in this way. By reducing the amount of time for checking a new version, the development team could react quickly to network and software changes during the early beta program.

The software included functionality to inform the development team of errors as they were detected. The information was immediately available to the development team, allowing “Internet time” modification and release of software. Turnaround was fast because of the programming language, architecture, client and server design, and application-server technology. This automatic update feature, similar to Sun's recently announced Java WebStart technology, was the only real infrastructure functionality built by the development team. The rest of the implementation used the J2EE architecture and the implementation provided by the Borland AppServer.

5.4.4. Application-Server Tier

The Borland AppServer provided a quality implementation of the J2EE architecture. The implementation excelled in the areas of persistence, container scalability, data integration, transactions, session management, load balancing, fault tolerance, and management.

Persistence

Persistence is an important part of any business system today and has great importance to the CORE system. Many object-relational mapping systems have been implemented, with different levels of success, by other vendors and within corporate IT departments. Most such systems don't allow easy object mapping across different database systems or between a system's different database tables. Sometimes, the integration of the mapping goes counter to the component technology used for the business object—resulting in an “impedance mismatch” within a system.

The container-managed persistence feature in the Enterprise JavaBeans 1.1 specification provides a greater level of independence between bean implementations and the persistence mechanism. Choosing container-managed persistence for all entity beans in the system was in keeping with the rule “aim to do nothing.” In this context, no SQL code needed to be implemented in entity beans—all persistence requirements are delegated to the container's persistence engine. This leads to fewer maintenance issues, easier migration, database knowledge encapsulation, and faster development, and also provides major performance improvements. In addition, it means that not every bean developer has to become an expert in SQL or the JDBC 2.0 API.

Runtime performance improvements come from many optimizations specific to the Borland AppServer's persistence engine. In fact, the EJB 2.0 specification includes similar features, to ensure greater portability across vendor implementations.

The optimizations and features provided by the Borland AppServer persistence engine include

  • Automatic Read-Only Detection. Within a transaction, the container can detect read-only access to an entity bean by inspecting its state using the Java reflection API. When no bean state changes, expensive SQL UPDATE calls aren't necessary. This improves performance in typical business systems, where read-only access volumes are much higher than read-writes or writes. With increasing sophistication, execution optimizations, and resource utilization management built into applications servers, the limiting factor on scalability is the amount of I/O performed. By reducing the amount of traffic to and from the database, read-only detection helps overcome this remaining scalability limitation.

  • Tuned Writes. When a container detects modifications to data, only modified fields need to be written to the database. This is especially important when entity beans have large numbers of fields or fields containing large amounts of data that aren't modified frequently.

  • Bulk Loading of Finder Methods. As implemented by some containers, Container-Managed Persistence can adversely affect the performance of the system when it's handling large numbers of SQL calls. For example, executing a finder method initially involves one SQL SELECT statement. This returns the primary keys of beans meeting the criteria, and generates a separate SELECT statement for each of these beans. If there are N primary keys returned, the container generates 1 + N SELECT statements. The Borland AppServer optimizes performance by loading all state for the selected beans on the initial query, reducing the number of SQL calls to 1.

  • Caching Prepared Statement and Connection Pooling. Application servers typically provide JDBC connection pooling in one of two ways, either through an internal adaptor mechanism that wraps around a JDBC connection, or through direct support within the JDBC driver. Creating connections is very expensive and needs to be offset over many invocations to maximize performance. Borland AppServer pools JDBC connections and further optimizes performance by reusing prepared statements across transactions.

  • Batch Updates. Sending multiple updates to a database for batch execution is generally more efficient than sending separate update statements. Significant performance improvements come from reducing the number of remote calls through batch-data transfer. The Borland AppServer performs batch updates if the underlying JDBC driver supports this feature.

  • Object-Relational Mapping. The Borland AppServer's persistence engine facilitates a variety of object relational mapping strategies. The most basic mapping is one entity bean to one database table. The engine also handles mapping of one-to-many and many-to-many relationships. It doesn't limit primary and foreign key values to single column values; instead, they can be composites of more than one column value. The engine also supports the use of composite keys in the finder methods. The persistence engine even supports mapping from one entity bean to multiple tables, with tables in either a single database or in separate databases.

  • Primary-Key Generation. Many databases have built-in mechanisms to provide an appropriate primary-key value. Unfortunately, the way entity beans are typically used, it is hard to use these built-in mechanisms without introducing a fair amount of complexity into either the beans or the calling client implementations. Borland AppServer's container-managed persistence engine provides a set of facilities that can be used to automatically fill some or all of the primary-key values. There are two ways to configure a primary-key generator. One relies on a primary-key generating class, which the user implements. The other, which relies on database facilities, is built into the CMP engine.

    To support primary-key generation using database-specific features, three entity properties are provided: getPrimaryKeyBeforeInsertSql, getPrimaryKeyAfterInsertSql, and ignoreColumnsOnInsert. The first property would typically be used in conjunction with Oracle Sequences, the second and third would both be needed to support SQL Server's Identity columns. These capabilities are general enough so that it should be straightforward to support key-generation techniques provided by any DBMS (see Section 4.6).

Container Scalability

Careful management of resources is key to increasing scalability of a system.

  • Memory Management. The choice of the Java programming language makes memory management easier from the outset. Finding memory leaks in other languages consumes an enormous amount of development time. With the garbage collection mechanism in the Java programming language, the development team could concentrate on the real issues involved in the writing of business logic.

    For a system to scale, it must manage memory to avoid leaks and reduce process size. The CORE system needs to provide configuration information on the whole voice network. This involves thousands of objects at any point, and it is typical for many users to be looking at different periods of network use at the same time. This complexity requires efficient resource management in the server to avoid performance effects. The EJB specification allows memory management on this scale through its well-defined sequence of bean lifecycle events. By separating the client interface to a bean from the actual bean implementation, the EJB specification makes it possible to implement sophisticated object pooling, object activation (late binding), and bean state management mechanisms.

    In the Borland AppServer, session beans can scale to a very large number of objects through instance pool management and careful activation and passivation of beans. As volume fluctuates, the server can periodically evict instances from memory and write their state to secondary storage. When a request comes in for an instance not in memory, it can be resurrected from storage. The server also continuously removes instances from secondary storage that have reached a specified expiration time.

    Design investigations revealed that the pool size setting for each bean type had an impact on performance figures. For some beans, the default pool size of 1,000 was too low, so the figure was adjusted to reflect peak usage. This reduced the amount of work for the garbage collector, the overhead of object creation, and the cache miss rate for beans with state cache across transactions.

    In addition to memory management for a single container, other approaches include replicating services across servers and load balancing to achieve better scalability and performance. This approach was also used by the CORE system.

  • Thread Management. Developing multithreaded servers is a complex, challenging task. The Enterprise JavaBeans architecture makes it easy to write business applications that benefit from multithreading, without the complexity. The bean provider isn't required to understand or write code to handle low-level thread management. Since most business applications are I/O bound with regard to database access, there are considerable performance gains from multithreading. With automatic thread pooling, incoming requests can be delivered to waiting threads via a queue or a notification. By limiting the EJB developer's ability to do thread management directly, the specification's thread management constraints actually enable implementation of sophisticated thread management features in EJB servers and containers.

    While restrictions on creating threads within the EJB container have frustrated other developers, the CORE team found this a useful decision by the EJB architects. Many design approaches that initially looked like they needed to manage threads directly turned out not to. The two design issues in which direct thread management was useful in CORE included messaging with JMS and interaction with CORBA servers. Enterprise JavaBeans aren't designed for active processes or objects that control their own execution. Beans are driven by external invocations, while a server is constantly responding to both internal and external events, which requires the ability to adjust their execution based on internal criteria.

  • Connection Management. Connection management was important to the CORE system, not because of a large user population, but because of the highly multithreaded nature of the client application. Multiple client requests needed to be sent across the same connection, rather than consume one connection each. The Borland AppServer is built on top of VisiBroker, which minimizes the number of client connections by using one connection per server. All client requests to the server are multiplexed over this single connection.

Data Integration

The JDBC 2.0 API is an important part of any system based on the J2EE architecture. JDBC aids container-managed persistence by providing a uniform data access API to all database management systems. Because of the architecture and extensive vendor support for JDBC, the core team was able to build and run the system on different database systems with different driver implementations, without any change in code. This was very important during the development phase, when demonstrations were done for users in remote locations. It was relatively easy for the team to switch the data sources pointed to by entity beans from Oracle production database to a local JDataStore demo database. Configuration mechanisms in the EJB specification for managing data sources as resource references made it easy to move among instances of different systems.

One feature in the Borland AppServer the development team liked was the ability to run the entity beans against a database without the need to predefine the corresponding tables. Where it doesn't detect the appropriate tables in the database, the container generates an SQL CREATE TABLE statement in the appropriate dialect for the target database. The Borland AppServer also supports generating entity beans from an existing database schema. By taking advantage of these benefits of container managed entity beans, the development team was able to achieve significant development time savings.

Resource management is also provided in the J2EE architecture through container resource pooling. This allows database connections to be shared across many transactions. Some vendors, including Borland, have added enhancements to this JDBC connection management, such as prepared-statement caching.

Transactions

Transaction management is a key technology in most business computing, central to building reliable large-scale systems. Transaction management is still an area of intensive research, and successfully understanding and implementing transactional systems requires significant experience. The Enterprise JavaBeans architecture effectively conceals the complexities of transaction management, allowing mere mortals to build such transactional systems quickly. The EJB architecture fully supports distributed transactions—that is, transactions involving access and update of data in multiple databases running on disparate computing nodes. The Enterprise JavaBeans architecture's declarative transaction feature greatly simplifies programming the CORE system, giving bean providers on the team freedom from dealing with the complexities of multiuser access and failure recovery.

Though automated transaction support in the J2EE architecture greatly assisted the development of this system, the one area in which developers felt they needed to take particular care was in transaction demarcation. Incorrect use of transaction attributes can lead to inconsistent application logic and can dramatically affect system performance. Transactions are simpler with component-based transaction management, but still require some careful choices by the developer.

Session Management

The J2EE architecture allows session management to be distributed across many tiers, including the Web tier using the Servlet Session Tracking API and the Application Server tier using Session Beans.

In most cases, the design of the session-management features for the CORE system placed responsibility in the application server tier. This decision was based on the desire to reuse business logic as much as possible. In order to best support both the Java application and browser-based HTML clients, it seemed obvious to place as much session management in session beans. This allowed various types of clients to be simplified and reduced in footprint as much as possible. Another factor in this decision was that clients typically needed to display only small sections of a complex object graph. In some cases, the graph didn't need to be refreshed frequently because data didn't need to be updated often, and users didn't need a real-time view of the model.

While small amounts of data passed between server and client, there were large calculation costs spanning many entity beans due to the resulting changes. By providing session management through session beans rather than in the client or Web-server tiers, it was possible to perform some hidden caching optimizations. The two clients were implemented to perform a small amount of session management specific to their behavior and environment.

Load Balancing

Load balancing is the mechanism for distributing client requests over a number of nodes and processes. Many implementations of the J2EE architecture provide this added value though clustering technology. Typically, load balancing is implemented at the point of binding to a JNDI service or a lookup on a JNDI entry. These two techniques are used in the deployed CORE system.

Load balancing across naming service instances enables the team to implement client-container affinity. This improved performance in cases in which caching takes place across transactions. It also makes it possible to bind clients to a particular naming service for various reasons, such as providing preferential treatment for better performance and enabling updates to the system in a controlled manner to selective users before complete rollout to the whole user base. Load balancing at the point of name lookup in the JNDI service allows easy configuration of workload distribution across similar nodes. One important result of this is that it is possible to have a test model to help correctly evaluate certain partition configurations.

Fault Tolerance (Availability)

Load balancing and fault tolerance go hand in hand, since both are implemented through clustering technology. The general approach to fault tolerance is to use redundancy and data replication, and to move processing from one node to another. To achieve a high degree of availability required of the system, multiple application servers and containers are run on the network. The Enterprise JavaBeans packaged jar files are grouped based on usage and intercommunication and deployed simultaneously to different nodes within the running container. If a bean's container fails during client invocation, the underlying infrastructure transparently fails over to another container containing the same bean deployment.

Failover support is provided for session beans (both stateful and stateless) and entity beans. The failover of a stateful object requires moving data between containers. The techniques generally used are replication and switchover. Replication means that several containers keep copies of data. Switchover involves containers having shared access to some stateful storage. Borland AppServer provides a switchover technique based on a stateful session storage service. Entity beans use their underlying persistence database. For failover of stateful session beans, containers use the callback methods ejbActivate and ejbPassivate in the bean implementation. This mechanism is defined in the EJB specification and the J2EE vendor's implementation of distributed shared session storage. It is important to note that during code development, these concerns didn't need to be considered by the bean provider. Instead, they were left to the configurations department to tune transparently to the CORE system.

Note that this support was useful for both unplanned outages and planned outages during and after development. Servers could be brought down without knowing which clients were connected or asking users to restart their desktop and attach to another process once service was restored.

Management

Maintaining any distributed system can be incredibly hard. System management is a critical element in enterprise systems. As distributed processing becomes more prevalent in systems, the need for tools to manage this complexity becomes increasingly important. In keeping with the focus here on software rather than hardware, the objective of application management is to enhance performance and availability of the whole system. The system management tools include the Borland AppServer console and AppCenter.

The console included with the Borland AppServer allows viewing of all application servers and services throughout a CORBA domain or particular naming service. From this console, it is possible to start, stop, restart, and ping all services, including containers, JNDI services, and JMS services. It is also possible to edit the configuration properties of each service within each server. These features, along with the ability to view event and error logs within one tool, allows for easy management of the system by both development and IT operations staff.

Such tools became very significant as the team moved the system between the different server environments for development and production. Because the EJB specification defines roles in the development and deployment process and the J2EE platform enables tool support for such distinctions, it was easy to hand over the final development build to operations, with no need to change the code to reflect the new environment. Differences between deployment environments could include different database instances, different database structures, different naming service structures, and so on. Since each could potentially be configured without requiring code changes, the team and the company have come to highly value the flexibility of the J2EE architecture.

Design Patterns and Guidelines

In designing the beans tier, a number of design patterns and guidelines were identified. The patterns aren't unique to Enterprise Java Beans; some of them have their origins in general high-performance distributed computing applications.

  • Business Components. An important point during early development was that bean development didn't mean that one single bean class would service all the functionality required by a specific business interface. A bean can delegate all or part of a request to helper classes. A component can be made up of other components and classes. For example, one session bean can use the services of other session beans and helper classes to service a request.

  • Policy-Based Design. In keeping with the first point, any logic likely to require future changes or cause complexity in bean development can be moved to a helper class. An interface can then be defined, which the default bean class implements. During deployment, an environment property for the bean can be specified to provide the name of an alternate class implementing the interface. When servicing requests, the bean looks in its environment context (“java:comp/env/”) for the name of the alternate class. If the context specifies an alternate class, the alternate is dynamically loaded.

  • Logical and Physical Design. Conceptual issues tend to dominate design analysis. However, ignoring physical implementation issues can lead to performance problems that aren't easy to fix. Performance should always be considered when factoring components for granularity and for interface consistency and clarity.

  • Puts Abstractions in Code, Details in Metadata. Another important pattern is to write code that executes the general case, then place specifics of each execution outside the code base. The CORE desktop uses this approach in the descriptors framework. It also uses it in loading reference data objects from database tables. Reference objects in the system consist of at least two fields: an identifier and a description. A reference object might be a Country type, with an identifier and description. The description is used in the user interface. Other fields in the Country type could include the International Standards Organization code. Because these objects are fine grained, the team decided to map them to a Java helper class rather than to an entity bean. Because of the large number of reference type objects in the system, the code for loading particular instances is isolated from the specifics, such as table and column names.

  • Objects that Play Together Should Stay Together. Many application servers built for Enterprise JavaBeans contain optimizations related to local invocations. Deploying beans that communicate frequently in a single jar improves performance by minimizing network latency and marshalling cost through remote invocations. This design principal minimizes traffic in the business logic tier.

  • Perform Useful Work. The overhead costs of remote invocations are high, so it is important to maximize the ratio of useful work to each invocation. A typical invocation on an Enterprise JavaBean involves at least two remotes calls: client-to-server and server-to-database. It is important that the overhead is offset by a large amount of server work before returning to the client.

    The general design guideline is to hide entity beans behind a session bean. The session bean provides lightweight Java objects to the client. The Java objects can contain state from one or more entity beans, including the results of calculations performed on other data. Data from multiple sources can be executed in one transaction.

  • Caching. When data is repeatedly requested remotely, a dynamic cache should retain a copy closer to the client. Caching can greatly improve system performance at all levels. Caching is easy to implement for reference data. It is provided in the CORE system through a CORBA server behind a stateless session bean. When data is updated, a simple timeout mechanism or a cache coherence protocol can be used.

  • Data Reduction. What is the quickest way to send 5,000 objects to a client? Not to send them at all. Sending large numbers of objects from database to the middle tier to the client creates performance problems at all levels. The database has to perform more work with regard to transactions spanning such a large number of objects, then transfer them to the middle tier. The container has to extract the data and place it into a similar large number of beans. Another bean then has to extract the data from those beans and transport it to the client. Finally, the client has to display all this data to a user, whose brain (short-term memory) isn't equipped to deal with such volumes. This kind of design is typical of business applications in which proper task analysis hasn't been performed. The design time and effort is instead spent on solving the problem from an engineering perspective. The real solution lay in a user-centered approach—that is, consider that users do not generally ask for 5,000 objects at once.

At the outset of the design work on the CORE system, a task analysis and data reduction diagram was produced, showing the expected data-traffic behavior (Figure 5.2).

Figure 5.2. Expected Data-Traffic Behavior of the Core System


This diagram shows some of the techniques used in data reduction. For example, task analysis might have determined that for the user to perform his function, he requires the system to isolate some objects based on some standard criteria, such as bank accounts overdrawn for two months, in which the amount is greater then 1,000 Euro. A session bean is provided that accepts two parameters: the number of months and the amount. The Account home has a finder method that retrieves a collection of Accounts based on months overdrawn and overdrawn amount. This would have resulted in the following SQL statement executed against the database.

SELECT * FROM ACCOUNT WHERE (MONTHS_OVERDRAWN >= ?) AND (AMOUNT <= (-1*?))

Knowing this, the database administrator would have created indices to support such queries. Further reduction is possible by designing the session bean so that it provides only the fields from the Account entity bean that the user requires at that time.

5.4.5. Process Tier

The CORBA servers used to communicate with the switching devices had timing mechanisms that were controllable through their IDL interface. The IDL interface allowed operations people to change the timing of events, such as synchronization of routing information between the switch and an external database. These features aren't provided for in the current Enterprise JavaBeans specification. However, most enterprise systems require these abilities. The servers also benefited from load balancing and fault tolerance from the underlying CORBA implementation.

5.4.6. Database Tier

The database tier handles a variety of tasks.

  • Object Identification. An important design decision in terms of performance and ease of integration with Enterprise JavaBeans was that beans don't use natural keys as primary keys. Instead, each object has an object ID mapped to a Long numeric object in the Java code. This helps improve performance of the system and simplifies some update logic. With a single numeric primary key representing every bean, it is easier to implement a general caching mechanism at each tier in the system, and to generalize other parts of the code.

  • Finders and Query Tuning. The design choice to use container-managed persistence for all entity beans made it possible to extract all SQL select statements in the system and present them to the database performance tuning experts for review and possible improvements. This could be done without searching through reams of printed code. During the early development phase, suggestions were easily incorporated because persistence configuration was separated from the bean implementation and placed in an XML file. It also made it easy to edit the SQL statements, redeploy bean jars into containers, then monitor performance through EJB test clients. Because persistence was an important aspect of the system, improvements in the query performance through query rewriting, providing hints, and creating indices was very noticeable. Best of all, these performance improvements came about with the need to change code and possibly introduce a bug.

  • Optimistic Concurrency. Optimistic concurrency means executing transactions without setting a lock, and checking transaction validity at commit time (note that most systems do not provide true isolation). Optimistic concurrency overcomes the performance impact of having a high degree of isolation. Lower levels of transaction isolation reduce the amount of data that is locked and the duration of each lock, increasing the chances for concurrent execution of different transactions. This comes at the cost of possible inconsistent reads and writes.

    The Borland AppServer provides some mechanisms to counter potential inconsistencies, without greatly affecting performance. The container persistence engine provides an entity bean flag, ejb.cmp.optimisticConcurrencyBehavior, with the following values:

    1. UpdateModifiedFields issues an update only on the fields that were modified, or suppresses the update altogether if the bean was not modified. This can give a significant performance boost.

    2. UpdateAllFields issues an update on all fields, regardless of whether they were modified or not.

    3. VerifyModifiedFields issues a tuned update, while verifying that the fields it is updating are consistent with the values that were previously read in.

    4. VerifyAllFields similar to VerifyModifiedFields, except that all fields are verified.

  • State Versioning. The optimistic concurrency mechanisms provided by the Borland AppServer are only relevant to concurrent transactions. The mechanism is implemented by comparing the state loaded from the database with the state in the database at the time of update. Though very useful, this design falls short when we look at the current way of designing of business processes and user interfaces. What it doesn't address is the fact that an entity bean's state can be displayed to a user using one transaction and updated by the same user in another transaction. During the time between the read and write transaction, another user could have committed an update. The optimistic concurrency mechanism does not work in this situation, since the state loaded at the start of the write transaction already reflects the update done by another user. A mechanism is needed to track the versioning of an entity bean's state across transactions. There are a number of ways to approach this, including:

    1. Use a stateful session bean to keep a copy of the image sent to the user for editing. When the session bean performs the update, it compares the state of the entity bean with the state it stored in the previous transaction. In the event of conflicts, it throws an exception back to the client.

    2. Use a stateless session bean that has two parameters: the state read and the state edited. The comparison process is exactly the same for the stateful session bean. Here the client is required to keep track of the state read in the first transaction.

    3. Similar to the previous technique, a version field in the entity bean can be used to track changes. When executing the update, the client sends the edited state object with the version number initially read. Before the session bean performs the update, the version number in the entity bean is compared with the version read in the first transaction. If the version number is older then the current one, an exception is thrown. This has better performance than the previous technique, but still allows for sophisticated resolution where the state is subsequently compared. The only drawback is that the database schema must support this new persistent field.

    4. Use a client-demarcated transaction that spans the read, edit, and update steps. This has the potential to hold expensive and precious resources for a considerable amount of time, during which the resources are under-utilized.

      The approach taken for the entity beans requiring such support in the CORE system was the third option.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset