DekGenius.com
[ Team LiB ] Previous Section Next Section

9.2 Accessing Remote Services

Business delegates are great for providing local code with access to business resources, whether the resources themselves are local or not. For local resources the data lives on the same server—maybe even in the same JVM—as the business delegate. In these situations, connecting to the data is not a huge concern. J2EE, however, is designed to handle much more complicated scenarios. An enterprise J2EE deployment typically involves resources that are distributed between multiple servers. To fulfill requests, the servers hosting the presentation tier may need to communicate with application servers, messaging middleware servers, database servers, and legacy IS systems. Finding all these servers and maintaining the various connections can quickly complicate otherwise simple business delegates.

Since the work of connecting is not central to the business delegate, it is often beneficial to build a separate object that the delegates can use to access remote services. The Service Adapter and Service Locator patterns describe ways to do this, the former for non-J2EE business services and the latter primarily for EJBs. Figure 9-5 shows how the service locator and service adapter act as intermediaries between the business delegates and business services.

Figure 9-5. Using service adapters and locators
figs/j2ee_0905.gif

9.2.1 The Service Adapter Pattern

A business delegate might make use of several services to fulfill a particular use case. In order to keep the business logic code understandable and maintainable, we want to use the simplest interfaces possible. A service adapter encapsulates interactions with a remote service, hiding details of the implementation when they aren't central to fulfilling the business need.

The Service Adapter pattern is a variation on the Gang of Four Adapter pattern. The Adapter pattern wraps a class in a superclass that implements a standard interface. The Service Adapter pattern goes one step further by wrapping a remote service in a standard Java object that can be used without knowledge of the underlying access mechanism. The Java wrapper is responsible for:

  • Locating the Remote Service.

  • Translating Java method calls into a format that the remote service understands.

  • Accessing remote functionality.

  • Translating results into a pure Java representation.

The Service Adapter pattern is overkill for EJBs, which already map business methods to Java methods. The translation activities are not required, and the directory services abstracted by a service adapter can be better addressed in the EJB context with the Service Locator pattern, discussed later in this chapter.

However, if the native interface to the external service is not Java, a service adapter can bridge the gap. A good example is an application that needs to access a SOAP-based web service. The service might be delivered by SOAP over HTTP, SOAP over SMTP, or via specialized, routed protocols like ebXML. The service adapter hides the complexities of creating a SOAP or ebXML message, allowing the developer to substitute different transport mechanisms as the needs of the application evolve. A SOAP-based service adapter in a prototype system can be replaced with a JMS-based adapter before going to production. Figure 9-6 shows a service adapter for a SOAP web service.

Figure 9-6. Service adapter for a SOAP web service
figs/j2ee_0906.gif

In J2EE 1.4 or earlier versions with the appropriate add-on modules, the JAX-RPC API and support tools can be used to build service adapters for web services. This largely eliminates the need to hand-code service adapters for web services.

The most recent Java IDEs support building service adapters directly from WSDL definition files. Some tools do this in a more platform-independent way than others, so it's worth watching for portability issues when using such tools. However, most of the generated code is either already reusable or can be easily modified to be so. Let's look at how this might work with a simple web service. Sticking with our health care theme, imagine a web service that returns a patient's medical records. Since complex data is more useful than simple data (and because we don't want to have to execute a few dozen web service requests for each use case), we've written the service to return a number of pieces of data with each request, in the form of a complex XML type. (If you're new to web services programming, now would be a good time to go read Java Web Services, by Dave Chappell and Tyler Jewell, also published by O'Reilly.)

A call to a web service can return a simple data type (numbers, strings, etc.) or a complex type. Complex types are defined within the WSDL file that specifies the web service, in the form of embedded XML Schema documents. In this case, the schema for the patient record type looks like this:

<schema
         targetNamespace="http://chapter9/IMedicalRecordService.xsd"
         xmlns="http://www.w3.org/2001/XMLSchema"
         xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
         <complexType name="chapter9_PatientRecord">
            <all>
               <element name="FirstName" type="string"/>
               <element name="LastName" type="string"/>
               <element name="MiddleName" type="string"/>
               <element name="Address1" type="string"/>
               <element name="Address2" type="string"/>
               <element name="City" type="string"/>
               <element name="State" type="string"/>
               <element name="Zip" type="string"/>
               <element name="Country" type="string"/>
               <element name="PatientID" type="long"/>
            </all>
         </complexType>
      </schema>

Within our application, we've defined a JavaBean, PatientRecord, which corresponds with this type. The JavaBean includes the same String and long properties, in traditional get/set form, and is otherwise indistinguishable from any other JavaBean.

Example 9-5 shows a class that uses the Apache SOAP toolset to identify a web service, calls its getMedicalRecord( ) method with a patient identifier as a parameter, and translates the return value from the XML defined in the WSDL file (shown above) into the JavaBean defined within our application. It then returns the JavaBean to the code that called it.

The code is pretty simple, and it gets even simpler once you realize that it's largely auto-generated: our IDE put most of the adapter code together based on the WSDL file and the JavaBean. All we did was clean it up and replace a vendor-provided SOAPHTTPConnection implementation with Apache's freely available version.

Example 9-5. MedicalRecordServiceAdapter.java
import org.apache.soap.transport.http.SOAPHTTPConnection;
import org.apache.soap.encoding.soapenc.BeanSerializer;
import org.apache.soap.encoding.SOAPMappingRegistry;
import org.apache.soap.util.xml.QName;
import org.apache.soap.*;
import org.apache.soap.rpc.*;

import java.net.URL;
import java.util.*;

public class MedicalRecordServiceAdapter {

  public MedicalRecordServiceAdapter(  ) {
    m_httpConnection = new SOAPHTTPConnection(  );
    m_smr = new SOAPMappingRegistry(  );

    BeanSerializer beanSer = new BeanSerializer(  );
    m_smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
      new QName("http://chapter9/IMedicalRecordService.xsd", 
        "chapter9_PatientRecord"), chapter9.PatientRecord.class, 
        beanSer, beanSer);
  }

  public String endpoint = 
    "http://service.hospital.org/records/MedicalRecordService";
  private SOAPHTTPConnection m_httpConnection = null;
  private SOAPMappingRegistry m_smr = null;

  public PatientRecord getMedicalRecord(Long patientID) 
    throws Exception {
    PatientRecord returnVal = null;

    URL endpointURL = new URL(endpoint);
    Call call = new Call(  );
    call.setSOAPTransport(m_httpConnection);
    call.setTargetObjectURI("chapter9.MedicalRecordService");
    call.setMethodName("getMedicalRecord");
    call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);

    Vector params = new Vector(  );
    params.addElement(new Parameter("patientID", 
      java.lang.Long.class, patientID, null));
    call.setParams(params);

    call.setSOAPMappingRegistry(m_smr);

    Response response = call.invoke(endpointURL, "");

    if (!response.generatedFault(  )) {
      Parameter result = response.getReturnValue(  );
      returnVal = (PatientRecord)result.getValue(  );
    }
    else {
      Fault fault = response.getFault(  );
      throw new SOAPException(fault.getFaultCode(), fault.getFaultString(  ));
    }

    return returnVal;
  }

}

Using this service adapter in a regular application is easy. To print out a patient's first name, all you would need to do is:

try {
      MedicalRecordServiceAdapter adapter = 
         new MedicalRecordServiceAdapter(  );
      System.out.println(adapter.getMedicalRecord(new Long(4)).getFirstName(  ));
    }
    catch(Exception ex) {
      ex.printStackTrace(  );
    }

9.2.2 Session Façade

Enterprise JavaBeans take much of the complexity of building a distributed system out of the hands of the developer. They do not, however, eliminate that complexity: they hide it under an easy-to-use implementation (or at least easier). Just because you, the programmer, don't have to write the code for transporting data across a TCP/IP connection, doesn't mean that the data doesn't have to get transported.

Performance can be a challenge in EJB architectures, particularly when you don't have a lot of computing muscle to put behind your application. If a client application needs to access multiple entity beans in order to perform a transaction, it must make a network call each time it retrieves a new entity bean or calls an EJB method. The performance consequences are potentially huge, since a single network invocation can take several hundred milliseconds or more, depending on the amount of work that has to be done on the other side and the state of the intervening network.

The Composite Entity Bean pattern reduces the number of entity beans in a system and makes them easier to manipulate but does not address the broader performance problem. To make matters worse, even the best entity bean designs usually involve more than one bean per use case. A client wishing to modify more than one entity bean in the course of a transaction must handle transaction control itself, via the JTA API or another mechanism. Alternately, the business logic for the specific use case can be built into one entity bean, but this approach results in a massively overcomplicated entity bean design, in which domain objects have far too much knowledge of the context in which they are used.

EJB entity beans also don't provide much help when it comes to structuring business logic. The Business Delegate pattern helps organize an application's business logic in a convenient, easy-to-use manner, but doesn't control the number of network calls or simplify transaction management (although the burden can be shifted to the Business Delegate from the client application itself, which can be of some advantage to the programmers involved).

The Session Façade pattern addresses these issues by grouping sets of activities into a session bean that can be invoked by a client to complete an entire use case at once. The façade is a business-tier object that hides the session beans, entity beans, and other business services from the presentation tier. Figure 9-7 shows how the components fit together.

Figure 9-7. Package diagram for a session façade
figs/j2ee_0907.gif

Using the session façade simplifies transaction management. Since transaction control is implemented at the first point of entry into a method on the EJB, a business method attached to a session façade can manipulate all of the entity and session beans required to fulfill the current use case within a single transaction.

Figure 9-8 shows a potential class diagram for an EJB session façade that provides two business methods and makes use of generic business objects, EJB session beans, and EJB entity beans (in one case directly, in another indirectly via the session bean). The session façade can also use service adapters to access non-EJB resources.

Figure 9-8. A session façade class structure
figs/j2ee_0908.gif

Session façades provide a performance boost because interactions between EJBs within a server are much more efficient than interactions between EJBs and remote clients: with EJB 2.0, local beans can use local interfaces to communicate with one another, cutting out the networking layer. All of the activities shown in Figure 9-9 can take place on the server. The client needs to call only one of the façade's business methods.

Figure 9-9. Sequence diagram for a session façade
figs/j2ee_0909.gif

Not all session beans are session façades! Business rules usually should not be implemented within the façade itself. A mathematical formula assessing a customer's credit rating, for example, belongs either in the entity bean representing the customer or in a separate, "utility" session bean, depending on how tightly coupled it is to the entity and whether the rule needs to be reused in other contexts. Conversely, remote applications shouldn't access any EJBs except for session façades. That same credit rating assessment should be included in (or at least accessible via) a session façade when the use case reports a credit assessment back to the presentation tier.

Use cases can be divided between a set of façades, although splitting use cases between session façades can be as much an art as a science. Creating a separate session façade for each use case can rapidly lead to a profusion of session façades. At the same time, avoid creating a single session façade for the entire application, unless the application is extremely small. All-encompassing session façades are sometimes known as "God Façades" or "God Objects." A God façade inevitably becomes difficult to maintain as the number of included use cases increases.

When organizing logic into a session façade, try to locate groups of related use cases. An e-commerce system, for example, might use a single session façade for all activities related to processing sales: checking inventory, setting shipping, arranging handling, and billing customers. Another façade might support catalog activities, and a third could handle account management for individual customers.

It's helpful to organize your use cases in an outline format, using the highest-level items as your top-level session façades. This technique usually leads to a nice balance between class complexity and class profusion.

9.2.2.1 Implementing a session façade

The EJB 2.0 specification introduced local EJBs, which can boost the performance of session beans by avoiding excessive round trips to the same server (see the Round-Tripping antipattern in Chapter 12). By restricting local EJBs to the same JVM, much of the overhead associated with translating data to and from network formats can be avoided. Example 9-6 shows a simple session façade, which uses the local interfaces of a number of EJBs to process a sale.

Example 9-6. A session façade EJB
import javax.ejb.*;
import java.util.*;
import javax.naming.*;

public class SaleFacadeBean implements SessionBean {
    private SessionContext context;
    private LocalCustomerHome customerHome;
        private LocalItemHome itemHome;
        private LocalSalesRecordHome recordHome;
    
    public void setSessionContext(SessionContext aContext) {
        context=aContext;
    }
    
    public void ejbActivate(  ) {}
    public void ejbPassivate(  ) {}
    
    public void ejbRemove(  ) {
        customerHome = null;
        itemHome = null;
        recordHome = null;
    }

    public void ejbCreate(  ) {
        try {
            InitialContext ic = new InitialContext(  );
            customerHome = 
               (LocalCustomerHome)
               ic.lookup("java:comp/env/ejb/local/Customer");
            itemHome = 
               (LocalItemHome)
               ic.lookup("java:comp/env/ejb/local/Item");
            recordHome = 
               (LocalSalesRecordHome)
             ic.lookup("java:comp/env/ejb/local/Record");
        } catch(Exception ex) {
            throw new EJBException(
           "Error looking up home object: " + ex, ex);
        }
    }

    public ReceiptDTO doSale(int itemNumbers[], int customerId) {    
        try {
           LocalCustomer cust =     
          customerHome.findByPrimaryKey(customerId);
      
           LocalItem items[] = new LocalItem[itemNumbers.length];
                for (int i = 0; i < itemNumbers.length; i++) {
               items[i] = itemHome.findByPrimaryKey(itemNumbers[i]);
           }
  
           LocalSalesRecord record = 
           recordHome.createRecord(items, cust);
                
           return (new ReceiptDTO(record));
            } catch(Exception ex) {
                throw new 
           EJBException("Error processing sale: " + ex, ex);
        }
    }
}
9.2.2.2 Web services session façades

The primary benefits of a session façade, including a reduction of network overhead and a centralization of business process logic, apply to any business service. Web services in particular benefit from the session façade approach, especially when they've been wrapped in a service adapter. Since SOAP connections are even less efficient than CORBA or JRMP (EJB) connections, the overhead involved in accessing a web service is much greater than the overhead associated with EJB connections. The same calculus that applies to session façades also applies in this circumstance.

A web services session façade consists of adding an additional method to the web service, rather than creating an entirely new object, as with EJBs. The new method factors the business logic that makes multiple calls to the web service out to the web service side rather than the client side. The simplest implementations just make the same sets of SOAP connections (to itself) as the client would, taking advantage of the fact that all of the processing is local rather than networked. Assuming that the web service itself takes advantage of properly compartmentalized design (separating the business objects from details of the web services interface), the new method could even access the business objects directly, effectively refactoring the web service down to a simpler model.

9.2.2.3 Session façade and business delegate

Session façades and business delegates are often used together. This symbiosis generally involves simplifying the business delegate implementation, by moving all of the business logic implementation to the session façade. The business delegate is responsible for EJB or web services lookups and handling networking exceptions. Business method invocations are simply passed on to the session façade. Since this conversion often doesn't affect the rest of the client code, using business delegates can pave the way for extending your architecture to EJBs in the future.

    [ Team LiB ] Previous Section Next Section