(By Conrad F. D'Cruz)
This section describes the implementation of the loan broker example using Java and XML Web Services. We use the open-source Apache AXIS toolkit to take care of the Web Services mechanics for us. We do not want this to be an exercise in Java development and chose this tool to abstract the complexity of designing and implementing a synchronous Web Service. The discussion in this section focuses on the design decisions that we make in designing a synchronous messaging solution.
Solution Architecture
The figure above shows the overall architecture of the synchronous predictive web service example. There are seven significant interfaces between the loan broker and the rest of the solution. As indicated, SOAP over HTTP is used to communicate between each pair of participants.
The first interface is the entry point in to the loan broker that the client application uses to pass in the message containing the loan application information. The client receives the results of the query back from the loan broker via the same interface. Although the Axis server is not shown in this diagram it receives the Message from the client and implements the Service Activator.
The second interface is between the loan broker and the credit agency. The credit agency is an external bureau and provides a Web service interface for the loan broker to acquire additional customer data that is required by the banks. The loan broker enriches the incoming request with the data from the credit agency, implementing a Content Enricher.
The next five interfaces are between the loan broker and each of the five banks. These interfaces are used to obtain the rate quote for a loan from that particular bank. Each bank interface provides the same functionality (obtain a quote) but may specify a different format for the associated SOAP messages. Therefore, the loan broker has to translate the quote request message to the format required by each particular bank before querying the bank.
The loan broker contacts each bank directly using predictive routing i.e., the set of banks that participate in the quote gathering is known right before they are called for a quote. In this stage the loan broker application sends the request to the selected banks using the Recipient List pattern. Because each bank can use a different format for the request, the loan broker needs to use an array of Message Translators to convert the request into the format required by each bank.
Likewise, each reply message has to be converted to a common format using a Normalizer, so that all replies can be aggregated into the single best quote. The request and response messages are passed synchronously, i.e., the client and loan broker is blocked until each Bank responds or times out. The loan broker aggregates all responses into the single best quote and sends the best quote reply back to the client.
Synchronous messaging is useful in some problem domains where a simple solution is necessitated. By using synchronous messaging we do not have to worry about handling asynchronous events, thread safety or the infrastructure needed to support them. The client invokes the loan broker web service and then waits for the response from the server. Based on the general description of the overall example the first component of the server application is the loan broker whose endpoint is exposed as the web service. There are several components in this solution and each component makes a synchronous call to the next component and waits for the response.
Web Services Design Considerations
XML Web services rely on the Simple Object Access Protocol (SOAP). The SOAP specification was submitted to the W3C and defines an XML based protocol for the purpose of exchanging messages between decentralized and distributed systems. For more details on SOAP please refer to the documents at the World Wide Web Consortium web site (www.w3.org/TR/SOAP).
Unfortunately, the "S" in "SOAP" is no longer valid. Actually, we have jokingly postulated that SOAP be renamed to Complex Remote Access Protocol -- you figure out the acronym... Seriously, designing a robust Web services interface requires us to dive into a a number of terminologies and associated design trade-offs. While this book is not an introduction into Web Services, we feel it is important to briefly discuss the following design considerations:
- Transport protocol
- Asynchronous messaging versus Synchronous messaging
- Encoding style (SOAP Encoding vs. doc/literal)
- Binding style (RPC vs. Document-style)
- Reliability and Security
Transport Protocol
The SOAP specification was created to allow applications to make synchronous RPC-style calls to a service across the network in a technology-neutral way. The SOAP specification actually defines a separate request and response message for each invocation to a Web Service (as defined in the WSDL document describing the service). Even though SOAP was developed with messaging in mind, the vast majority of the Web Service applications use HTTP as the transport protocol. HTTP is a natural choice because it is the most commonly used protocol on the Web and can sneak through firewalls. However, the HTTP protocol is the "Hyper Text Transfer Protocol" -- it was designed to allow users with Web browsers to retrieve documents over the Internet, not for applications to communicate with each other. The HTTP protocol is inherently unreliable and designed for synchronous document retrieval -- the client application uses the same connection for sending the request to the server and receiving the response. Therefore, Web Services that use the HTTP protocol will invariably use synchronous request/response messaging because asynchronous messaging over an unreliable channel is about as useful as dropping a bottle into the ocean.
Asynchronous vs. Synchronous
In a synchronous implementation of a Web Service, the client connection remains open from the time the request is submitted to the server. The client will wait until the server sends back the response message. The advantage of using the synchronous RPC communication is that the client application knows the status of the Web Service operation in a very short time (either it receives a response or it times out). A serious limitation of using synchronous messaging is that the server may have to deal with a large number of concurrent connections because each concurrent client maintains an open to connection while waiting for the results. This causes the server application to become increasingly complex. If one of the invocations to a synchronous service provider fails, the server application has to provide the mechanism to trap the failure and recover and re-route the processing or flag an error before continuing with the other synchronous invocations.
At the present time, most Web Services toolkits only support synchronous messaging by default. However, using existing standards and tools such as asynchronous message queuing frameworks, some vendors have emulated asynchronous messaging for Web Services. Several organizations, companies and the Web Services working groups have recognized the need to have asynchronous messaging support and have been moving towards defining standards (e.g. WS-ReliableMessaging). For the latest on Web Services standards, please refer to the World Wide Web Consortium web site at http://www.w3.org/ and see the chapter Emerging Standards and Futures in Enterprise Integration at the end of this book.
Encoding Style
The notion of SOAP encoding has led to a fair amount of debate and confusion. The SOAP Specification specifies a mode called "encoding Style" specified by the encodingStyle attribute. This mode can take on two values, "encoded" (by setting the attribute value to "http://schemas.xmlsoap.org/soap/encoding/") and "literal" (by specifying a different (or no) attribute value). This mode determines how application objects and parameters are represented in the XML "on the wire". "Encoded" (also referred to as SOAP Encoding) refers to Section-5 of the SOAP specification (www.w3.org/TR/SOAP), which defines a primitive mechanism for mapping programming language types to XML. "Literal" (also called "doc/literal") means don't do that. Instead, the type information is provided by an external mechanism, more likely than not a WSDL (Web Services Description Language) document that uses XML schema to define exactly what types are used in the SOAP message.
The reason this came about is because the SOAP specification was written prior to the adoption of the XML schema specification (XSD). Thus, the original SOAP specification had to provide a way of encoding type information along with the parameters being sent for method calls because there was no accepted way of specifying it. Where this really comes into play is with complex data types such as Arrays. Section 5.4.2 of the SOAP specification defines a particular mechanism for representing programming language arrays in XML that uses a special SOAPEnc:Array schema type.
However, since the adoption of XML Schema (see http://www.w3.org/TR/xmlschema-0/), most languages have rendered the need for SOAP encoding obsolete by specifying their own mappings (or serialization rules) from XML schema to the programming language types. For instance, the JAX-RPC specification uniquely specifies how Java types are mapped to XML schema elements and vice-versa. This obviates the need for extra encoding information in the XML. So, as a result, SOAP Encoding is no longer favored, and has been superceded by literal encoding with the mapping being specified externally by an XML schema document, usually in the form of a WSDL document.
Binding Style
The WSDL Specification specifies two different "binding styles" in its SOAP binding. The values of the binding style attribute are "RPC" and "Document". What this means is that if a WSDL document specifies an operation has a binding style attribute set to "RPC" then the receiver must interpret that message using the rules found in Section 7 of the SOAP specification. This means, for instance, that the XML element inside the SOAP body (called a "wrapper element") must have a name identical to the name of the corresponding programming-language operation that is to be invoked, that each message part within that element must correspond exactly (in name and order) to a parameter of that programming-language operation, and that there must be only a single element returned (which must be named XXXResponse, where XXX is the name of the corresponding operation in the language) that contains inside it exactly one element, that is the return value of the operation.
The "Document" binding style is much looser. A message in the document binding style must simply be made up of well-formed XML. It is up to the SOAP engine that receives it how it will be interpreted. Now, having said that, it is fairly common (for instance among Microsoft tools) to use "Document" binding style and "Literal" encoding to represent RPC semantics. Even though a "Document" style is used, the message being sent represents a Command Message, with the operation to be invoked and the parameters to be passed encoded in the "Document".
Reliability and Security
In the chapter Emerging Standards and Futures in Enterprise Integration, Sean Neville describes evolving standards that aim to address the issues of reliability and security for Web Services.
Our solution uses the most basic and probably still most prevalent combination of these design choices. The loan broker implementation uses SOAP over HTTP with synchronous communication, encoding messages with the default SOAP encoding style and RPC binding. This makes the Web service behave very much like a Remote Procedure Invocation. We went this route so that we do not get stuck debating Web services internals (well, not any more than we already did) but can focus on contrasting the synchronous Web services implementation with the other implementations.
Apache Axis
This section provides a brief description of the Axis architecture to help elucidate the significant points in our design. For additional details on Axis please refer to the Apache Axis web site at xml.apache.org/axis.
The chapter Root Patterns defines a Message Endpoint as the mechanism an application uses to connect to a messaging channel in order to send and receive messages. In our loan broker application, the Axis framework itself represents the message channel and its main function is to handle the processing of messages on behalf of the user application.
The Axis server implements the Service Activator pattern. In the chapter on Messaging Endpoints, we described how a Service Activator connects a Message Channel to a synchronous service in an application so that when a message is received, the service is invoked.
The Message Adapter is implemented within the Axis server so that the developer does not need to implement this functionality. Therefore the application code contains only business logic for the application and the Axis server takes care of all message-handling services.
The client-programming model of Axis provides the components for the client application to invoke an endpoint URL and then receive the response message from the server. The loan broker client in this application is a synchronous client that uses this client-programming model of Axis. Within the server there is a listener for each transport protocol supported by the server. When the client sends a message to the endpoint, a transport listener within the Axis framework creates a message context object and passes it through a request chain in a framework. The Message Context contains the actual Message received from the client along with associated properties added by the transport client.
The Axis framework is made up of a series of Handlers, which are invoked in a particular order depending on the deployment configuration and whether the client or server is invoking the framework. Handlers are part of the Message Flow subsystem and are grouped together and called Chains. Request messages are processed by a sequence of request Handlers in a Chain. Any response messages are sent back on the corresponding response Chain through a sequence of response Handlers.
The figure above shows a high level representation of the internals of the Axis framework. A detailed discussion on the architecture of Axis can be found at http://xml.apache.org/axis.
Axis consists of several subsystems that work together to provide the functionality of a Message Channel. This framework is available for use by both the client and server application.
The relevant Axis subsystems for our example are as follows:
- Message Model subsystem defines the XML syntax of the SOAP messages.
- Message Flow subsystem defines Handlers and Chains to pass the messages
- Service subsystem defines the service handler (SOAP, XML-RPC)
- Transport subsystem provides alternatives for the transport of messages (for e.g., HTTP, JMS, SMTP)
- Provider subsystem defines the providers for different types of classes (for e.g., java:RPC, EJB, MDB)
As mentioned earlier, the developer only needs to focus on creating an application that implements the business logic, which can then be deployed in the Axis server. There are three techniques for deploying a java class as a Web service and being made available as an endpoint service and we have named them as follows.
- Automatic Deployment
- Using a Web Services Deployment Descriptor
- Generate proxies from an existing WSDL document
The first and simplest way is to write the class containing the business logic as a Java Web Service file (this is a Java source file with a *.jws extension). The methods of this class contain the business logic. The JWS file does not need to be compiled and can be instantly deployed by copying the source file into the webapps directory on the server. Each public method is now accessible as a Web service. The name of this JWS file forms part of the endpoint that the Axis server exposes as a Web service as shown below:
http://hostname:portnumber/axis/LoanBroker.jws
Axis 1.1 automatically generates the WSDL (Web Services Description Language - see http://www.w3.org/TR/wsdl.) document from services deployed within the server. WSDL is an XML format that describes the public interface of a Web service (i.e. the methods available through the interface) as well as the location of the service (i.e. the URL). The automatic generation of WSDL allows other applications to inspect the remote interface provided by the Web service class. It can also be used to automatically generate client stub classes that encapsulate the Web services call inside a regular Java class. The disadvantage of this method is the developer cannot control the deployment parameters.
The second technique deploys a compiled class using a WSDD (Web Services Deployment Descriptor), which allows the developer to control the deployment parameters, e.g. the class scope. By default, a class gets deployed in the request scope i.e., a new instance of the class is instantiated for each request that is received. Once the processing is finished the instance is destroyed. If the class has to persist for the entire session to service multiple requests from the same client over a period of time, we need to define the class in the session scope. For some applications, we need all clients to access a singleton class, i.e., the class has to be instantiated and made available for the entire duration the application is active, in which case the web service is defined in the application scope.
The last technique is more complicated than the previous two techniques but allows the generation of proxies and skeletons from an existing WSDL document (using the wsdl2java tool). The proxies and skeletons encapsulate all SOAP-related code so that the developer does not have to write any SOAP (or Axis)-related code, but can instead insert the business logic right into in the method bodies of the generated skeleton.
We choose to implement all our Web services using the Automatic Deployment technique using JWS files, in order to keep the design and deployment requirements as simple as possible. On the client side it is a lot easier to hand code the call to the Web Service. We could have used wsdl2java to generate the stubs, which would then be called by the client code, however we tried to minimize the amount of code generation because it can make it difficult to walk through an example solution.
Service Discovery
Before we proceed with a discussion of the loan broker application, we need to describe a few general steps that we follow when implementing the solution to help in creating an application that is easy to deploy. In order to invoke a Web service that is deployed on a server, any client application needs to know the endpoint URL. In the Web services model, an application would look up the location of a web service they need to access from a common service registry.
UDDI (Universal Description, Discovery and Integration) is a standard for such a repository. A discussion of UDDI is beyond the scope of this section and you can get additional information at www.uddi.org. In our example we hard code the endpoint URLs in the application itself. However, to make it easy to deploy the example code, we create properties files for both the server and client applications. The properties file has name value pairs for the hostname and port number parameters, which match the parameters of your Axis server installation. This gives you some flexibility in deploying the loan broker application in your environment. We provide a utility method readProps() in some of the Java classes. This method is for reading files that contain the deployment parameters of the Axis server. The readProps() method is not used by any of the functional aspects of the loan broker application.
In any distributed computing framework whether it is Java RMI, CORBA or SOAP Web services, we need to define parameters of the method calls on remote objects as primitive data types or objects that can be serialized to and from the network. These parameters are the properties of the message objects that are sent from the client to the server. To keep the loan broker solution simple we use a java String object to return the response from the loan broker to the client. If the calling parameters are primitive data types (e.g. int, double, etc.) they have to be wrapped in the predefined object wrappers called the type wrappers (for e.g., Integer, Double, etc.).
The Loan Broker Application
The figure below shows the class diagram for the loan broker component. The core business logic for the loan broker is encapsulated inside the LoanBrokerWS class. This class inherits from a class provided by the Axis framework that implements a Service Activator to invoke a method in the LoanBrokerWS class when a SOAP request arrives. The LoanBrokerWS references a set of gateway classes that implement details of interfacing with external entities, such as the credit agency and the banks. This logic is encapsulated inside the classes CreditAgencyGateway, LenderGateway, BankQuoteGateway.
The only service interface from the loan broker to the outside world is the message endpoint for the client to access the loan broker service. There is no need to define a Messaging Gateway since the Axis framework acts as the gateway on behalf of the application.
The following sequence diagram illustrates the interaction between the components for the synchronous predictive Web service example. The loan broker first calls the credit agency gateway component, which enriches the minimum data provided with a credit score and the length of credit history for the customer. The loan broker uses the enriched data to call the lender gateway. This component implements the Recipient List that uses all the data provided to choose the set of lenders that can service the loan request.
The loan broker then calls the bank quote gateway. This component performs the predictive routing operation by calling each bank in turn. The bank components model the interface to a real-world banking operation. For example the Bank1 class is the interface to the Bank1WS.jws Web Service that models the banking operation. When a loan request comes in, there is some amount of clerical work performed before generating the rate quote based on all the parameters. In this example, the rate quote is generated by a banking Web service.
The bank quote gateway aggregates the responses from the banks and chooses the best quote from all the quotes received. The response is sent back to the loan broker, which formats the data from the best quote and returns the report to the client.
We have only shown two of the five banks in the sequence diagram in the interest of keeping the picture manageable. Based on the Recipient List generated for a particular request, the loan broker could contact exactly one bank or all five banks to service the client request.
The diagram highlights the sequential processing of the quote requests to each bank. The sequential processing has some advantages because we can run the application in a single thread calling each bank in turn. The application does not need to spawn threads to request a quote from each bank and so we don’t have to worry about concurrency issues. However, the total time to get the response quote is high since the loan broker has to wait for a response from each bank before submitting a request to the next bank in the lenders list. The net result is the customer will have to wait a long time to get the result quote back after submitting the request.
Components of the Loan Broker Application
We will now start designing the functional aspects of the loan broker application. As stated earlier the Axis framework can support both the client and server application. This allows a remote client to access the published endpoint across the network. A server application may also need to act like a client and access some other Web service endpoint, regardless of whether that endpoint is on the same server or a remote server. We are going to demonstrate this by modeling the key components of our solution as Web services albeit running on the same instance of our server.
The loan broker solution has to implement the following functions. The following sections discuss the implementation of each function.
- Accept Client Requests
- Retrieve Credit Agency Data
- Implement the Credit Agency Service
- Obtain Quotes
- Implement the Banking Operations
Accepting Client Requests
The loan broker is implemented in a JWS file named LoanBrokerWS.jws. It exposes a single public method as a Web service: GetLoanQuote. The loan broker needs three pieces of data from the client to start processing a loan request: the social security number (SSN) that acts as the customer identification number, the amount of the loan and the duration of the loan in months:
public String getLoanQuote(int ssn, double loanamount, int loanduration) { String results = ""; results = results + "Client with ssn= " + ssn + " requests a loan of amount= " + loanamount + " for " + loanduration + " months" + "\n\n"; results = results + this.getLoanQuotesWithScores(ssn,loanamount,loanduration); return results; }
The only step in this method is the call to the method getLoanQuotesWithScores. This method returns a string, which is sent back to the client along a response chain in the Axis framework. At the end of the chain, the transport listener that received the request will accept the response message from within the Axis framework and send it back to the client on the network.
As described earlier, since we use the Automatic Deployment of the JWS file for the loan broker component, Axis generates the WSDL file for the LoanBrokerWS service. The WSDL file for the LoanBrokerWS.jws file is shown below.
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <wsdl:message name="getLoanQuoteRequest"> <wsdl:part name="ssn" type="xsd:int"/> <wsdl:part name="loanamount" type="xsd:double"/> <wsdl:part name="loanduration" type="xsd:int"/> </wsdl:message> <wsdl:message name="getLoanQuoteResponse"> <wsdl:part name="getLoanQuoteReturn" type="xsd:string"/> </wsdl:message> <wsdl:portType name="LoanBrokerWS"> <wsdl:operation name="getLoanQuote" parameterOrder="ssn loanamount loanduration"> <wsdl:input message="intf:getLoanQuoteRequest" name="getLoanQuoteRequest"/> <wsdl:output message="intf:getLoanQuoteResponse" name="getLoanQuoteResponse"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="LoanBrokerWSSoapBinding" type="intf:LoanBrokerWS"> <wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="getLoanQuote"> <wsdlsoap:operation soapAction=""/> <wsdl:input name="getLoanQuoteRequest"> <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="..." use="encoded"/> </wsdl:input> <wsdl:output name="getLoanQuoteResponse"> <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="..." use="encoded"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="LoanBrokerWSService"> <wsdl:port binding="intf:LoanBrokerWSSoapBinding" name="LoanBrokerWS"> <wsdlsoap:address location="http://192.168.1.25:8080/axis/LoanBrokerWS.jws"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
In the interest of space we condensed the WSDL document to highlight the most significant elements. The <wsdl:service> tags define the service name (LoanBrokerWSService) and the endpoint location. The <wsdl:operation> tags define the method (operation) name along with the parameters required for the client to access the LoanBrokerWS Web Service. The two messages defined in the <wsdl:message> tags define the request and the response messages for the getLoanQuote operation: getLoanQuoteRequest and getLoanQuoteResponse with the appropriate parameters they pass in to or receive from the Web Service. The <wsdlsoap:binding> element confirms that we are using the RPC binding style that we discussed at the beginning of this chapter while the <wsdlsoap:body> reveals that we are using SOAP encoding (as opposed to doc/literal).
If you want to see the WSDL file from your server, you can type in the following URL in your browser window where hostname and portnumber are replaced with the appropriate values of your server installation,
http://hostname:portnumber/axis/LoanBrokerWS.jws?wsdl
Retrieving Credit Agency Data
Another requirement for the loan broker is to gather additional data on the customer to complete the loan request application. The next stage of the LoanBrokerWS, implements the Content Enricher as shown below:
private String getLoanQuotesWithScores(int de_ssn, double de_loanamount, int de_duration) { String qws_results="Additional data for customer: credit score and length of credit history\n"; int ssn = de_ssn; double loanamount = de_loanamount; int loanduration = de_duration; int credit_score = 0; int credit_history_length = 0; CreditProfile creditprofile = CreditAgencyGateway.getCustomerCreditProfile(ssn); credit_score = creditprofile.getCreditScore(); credit_history_length = creditprofile.getCreditHistoryLength(); qws_results = qws_results + "Credit Score= " + credit_score + " Credit History Length= " + credit_history_length; qws_results = qws_results + "\n\n"; qws_results = qws_results + "The details of the best quote from all banks that responded are shown below: \n\n"; qws_results = qws_results + getResultsFromLoanClearingHouse(ssn,loanamount,loanduration,credit_history_length,credit_score); qws_results = qws_results + "\n\n"; return qws_results; }
We now design the credit agency operations, which is the next logical area of the loan broker application. In order to keep the SOAP code out of the loan broker application, and minimize the dependencies between the loan broker and the credit agency we will use the Gateway pattern. In Martin’s P of EAA book, using an example created by Mike Rettig, Martin highlighted two key advantages of using the Gateway pattern. First, it abstracts the technical details of the communication from the application. Second, if we choose to separate the gateway interface from the gateway implementation we can replace the actual external service with a Service Stub [EAA] for testing.
Our CreditAgencyGateway abstracts the technical details of the communication between the CreditAgencyGateway and the credit agency Web Service (CreditAgencyWS). We could replace the Web service with a test stub if needed. The gateway takes in the customer identification number (ssn) and acquires additional data from the credit agency. The two pieces of data returned by the credit agency are the credit score and the length of credit history for the customer both of which are needed to complete the loan application. This gateway will contain all the client side code to access the CreditAgencyWS implemented in the CreditAgencyWS.jws file.
public static CreditProfile getCustomerCreditProfile(int ssn){ int credit_score = 0; int credit_history_length = 0; CreditProfile creditprofile = null; try{ CreditAgencyGateway.readProps(); creditprofile = new CreditProfile(); String creditagency_ep = "http://" + hostname + ":" + portnum + "/axis/CreditAgencyWS.jws"; Integer i1 = new Integer(ssn); Service service = new Service(); Call call = (Call) service.createCall(); call.setTargetEndpointAddress( new java.net.URL(creditagency_ep) ); call.setOperationName("getCreditHistoryLength"); call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN ); call.setReturnType( XMLType.XSD_INT ); Integer ret1 = (Integer) call.invoke( new Object [] {i1}); credit_history_length = ret1.intValue(); call.setOperationName("getCreditScore"); Integer ret2 = (Integer) call.invoke( new Object [] {i1}); credit_score = ret2.intValue(); creditprofile.setCreditScore(credit_score); creditprofile.setCreditHistoryLength(credit_history_length); Thread.sleep(credit_score); }catch(Exception ex){ System.out.println("Error accessing the CreditAgency Webservice"); } return creditprofile; } }
The purpose of using this Gateway is to show how a server application uses the Axis client framework to access a Web service, either on the same server or a remote server.
Implementing the Credit Agency Service
The credit agency Web service is coded in CreditAgencyWS.jws and shown below. . The two significant pieces of data needed to complete the loan application are the customer credit score and the length of the customer’s credit history. The credit agency has this data for each customer with a credit history and can be accessed using the customer identification number.
public int getCreditScore(int de_ssn) throws Exception { int credit_score; credit_score = (int)(Math.random()*600+300); return credit_score; } public int getCreditHistoryLength(int de_ssn) throws Exception { int credit_history_length; credit_history_length = (int)(Math.random()*19+1); return credit_history_length; }
The code below shows the WSDL file for the CreditAgencyWS.jws file. This file was automatically generated by the Axis server.
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"> <wsdl:message name="getCreditScoreResponse"> <wsdl:part name="getCreditScoreReturn" type="xsd:int"/> </wsdl:message> <wsdl:message name="getCreditHistoryLengthRequest"> <wsdl:part name="de_ssn" type="xsd:int"/> </wsdl:message> <wsdl:message name="getCreditScoreRequest"> <wsdl:part name="de_ssn" type="xsd:int"/> </wsdl:message> <wsdl:message name="getCreditHistoryLengthResponse"> <wsdl:part name="getCreditHistoryLengthReturn" type="xsd:int"/> </wsdl:message> <wsdl:portType name="CreditAgencyWS"> <wsdl:operation name="getCreditHistoryLength" parameterOrder="de_ssn"> <wsdl:input message="intf:getCreditHistoryLengthRequest" name="getCreditHistoryLengthRequest"/> <wsdl:output message="intf:getCreditHistoryLengthResponse" name="getCreditHistoryLengthResponse"/> </wsdl:operation> <wsdl:operation name="getCreditScore" parameterOrder="de_ssn"> <wsdl:input message="intf:getCreditScoreRequest" name="getCreditScoreRequest"/> <wsdl:output message="intf:getCreditScoreResponse" name="getCreditScoreResponse"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="CreditAgencyWSSoapBinding" type="intf:CreditAgencyWS"> ... </wsdl:binding> <wsdl:service name="CreditAgencyWSService"> <wsdl:port binding="intf:CreditAgencyWSSoapBinding" name="CreditAgencyWS"> <wsdlsoap:address location="http://192.168.1.25:8080/axis/CreditAgencyWS.jws"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
The <wsdl:service> defines the CreditAgencyWSService and exposes the endpoint that will be accessed by the client in this case the loan broker. The <wsdl:operation> defines the two public methods defined in the CreditAgencyWS.jws file, getCreditScore and getCreditHistoryLength. For each of these methods, there will be a request-response message pair defined as can be seen in the <wsdl:message> tags. These are listed here, getCreditScoreRequest, getCreditScoreResponse, getCreditHistoryLengthRequest, getCreditHistoryLengthResponse.
Once again, in the interest of space we have collapsed most of the elements of the WSDL file to fit the format of the section. If you are interested in seeing the entire file, it can be accessed on your server using the following URL where hostname and portnumber are the values for your server installation,
http://hostname:portnumber/axis/CreditAgencyWS.jws?wsdl
The credit agency webservice in our example stubs out the implementation and returns dummy data that can be used by other parts of the application.
Obtaining Quotes
Having enriched the customer data, the loan broker now calls the loan clearing house function, which is part of the loan broker itself. As seen in the call below, the loan-clearing house receives all the data for the loan application.
getResultsFromLoanClearingHouse(ssn,loanamount,loanduration,credit_history_length,credit_score);
The loan clearing house functions could be further demarcated into its own logical unit if the application gets more complex or the loan broker requirements change to use an external loan-clearing house. In our example the loan clearing house functionality can be broken down into three steps, namely:
- Get a list of lenders who can service the customer loan
- Get the best quote out of all quotes from each bank in the lender list
- Format the data from the best quote and return it to the loan broker
The code shows these steps:
private String getResultsFromLoanClearingHouse(int ssn, double loanamount, int loanduration, int credit_history_length, int credit_score) { String lch_results="Results from Loan Clearing House "; ArrayList lenderlist = LenderGateway.getLenderList(loanamount, credit_history_length, credit_score); BankQuote bestquote = BankQuoteGateway.getBestQuote(lenderlist,ssn,loanamount,loanduration,credit_history_length,credit_score); lch_results = "Out of a total of " + lenderlist.size() + " quote(s), the best quote is from" + this.getLoanQuotesReport(bestquote); return lch_results; }
The first requirement for the loan-clearing house is to get a list of suitable lenders that can service the customer loan application. In order to abstract the functions of the lender selection process from the loan broker we will create a LenderGateway class. We could make the solution more interesting by making this a Web Service. However, Web Services should be designed carefully and the parameters and return types should be taken into consideration since these have to be types that can be serialized to and from the network.
For this example, we could have chosen to return a collection of the service endpoint for the banks from a LenderGateway. The drawback of doing this is the identification of the bank webservice will have to be hard coded in the lender gateway. This becomes a maintenance nightmare in real life, especially since banks get bought, sold or merged more often than IT architects change jobs. We will discuss a robust solution for the bank and its webservice in the discussion of the BankQuoteGateway topic later in this section.
The method getLenderList shown below will return the set of lenders (i.e. banks) that can service the loan request.
public static ArrayList getLenderList(double loanamount, int credit_history_length, int credit_score){ ArrayList lenders = new ArrayList(); LenderGateway.readProps(); if ((loanamount >= (double)75000) && (credit_score >= 600) && (credit_history_length >= 8)) { lenders.add(new Bank1(hostname, portnum)); lenders.add(new Bank2(hostname, portnum)); } if (((loanamount >= (double)10000) && (loanamount <= (double)74999)) && (credit_score >= 400) && (credit_history_length >= 3)) { lenders.add(new Bank3(hostname, portnum)); lenders.add(new Bank4(hostname, portnum)); } lenders.add(new Bank5(hostname, portnum)); return lenders; }
This method implements the RecipientList pattern. In our example the rule base consists of very simple if statements that choose a bank or banks based on a set of predefined conditions. We have also set a default selection for every customer request so that every customer request will have at least one quote.
The loan broker will now pass the list of lenders to the bank quote gateway to start gathering quotes and make a selection. Once again we create a class named BankQuoteGateway to abstract the internal functioning of the bank quote gateway. The loan broker will only need to request the best quote from the BankQuoteGateway as seen in the method call below:
BankQuote bestquote = BankQuoteGateway.getBestQuote(lenderlist,ssn,loanamount,loanduration,credit_history_length,credit_score);
The BankQuoteGateway responds to the loan broker request by getting the quotes from all the banks and then selecting the best quote (i.e., the quote with the lowest rate). The getBestQuote method is shown below:
public static BankQuote getBestQuote(ArrayList lenders, int ssn, double loanamount, int loanduration, int credit_history_length, int credit_score){ BankQuote lowestquote = null; BankQuote currentquote = null; ArrayList bankquotes = BankQuoteGateway.getBankQuotes(lenders,ssn,loanamount,loanduration,credit_history_length,credit_score); Iterator allquotes = bankquotes.iterator(); while (allquotes.hasNext()){ if (lowestquote == null){ lowestquote = (BankQuote)allquotes.next(); } else{ currentquote = (BankQuote)allquotes.next(); if (currentquote.getInterestRate() < lowestquote.getInterestRate()){ lowestquote = currentquote; } } } return lowestquote; }
The most significant line in the code above is the call to getBankQuotes. This method not only performs the controlled Auction but also implements the Aggregator pattern. The listing below shows the getBankQuotes method:
public static ArrayList getBankQuotes(ArrayList lenders, int ssn, double loanamount, int loanduration, int credit_history_length, int credit_score) { ArrayList bankquotes = new ArrayList(); BankQuote bankquote = null; Bank bank = null; Iterator banklist = lenders.iterator(); while (banklist.hasNext()){ bank = (Bank)banklist.next(); bankquote = bank.getBankQuote(ssn,loanamount,loanduration,credit_history_length,credit_score); bankquotes.add(bankquote); } return bankquotes; }
The while loop performs the function of the controlled Auction. Each bank is extracted from the lender list and the method to generate a bank quote is invoked as highlighted below. Pay special attention to the order of the parameter list in the call to the method and we will explain the significance when we start designing the banks and the associated webservices.
bank.getBankQuote(ssn, loanamount,loanduration, credit_history_length, credit_score);
The response is aggregated using the bankquotes ArrayList. The getBestQuote method iterates over this collection of bank quotes and selects the lowest quote, which is sent back to the loan broker.
As mentioned earlier, we will design the bank and bank webservice to emulate a real-world bank operation and keep the functions of the bank separate and not couple them tightly with the functions of the loan broker. This will let our bank classes have the advantages of using the Gateway pattern described earlier in the CreditAgencyGateway class.
We define an abstract Bank class as follows:
public abstract class Bank{ String bankname; String endpoint = ""; double prime_rate; public Bank(String hostname, String portnum){ this.bankname = ""; this.prime_rate = 3.5; } public void setEndPoint(String endpt){this.endpoint = endpt;} public String getBankName(){return this.bankname;} public String getEndPoint(){return this.endpoint;} public double getPrimeRate(){return this.prime_rate;} public abstract BankQuote getBankQuote(int ssn, double loanamount, int loanduration, int credit_history_length, int credit_score); public void arbitraryWait(){ try{ Thread.sleep((int)(Math.random()*10)*100); }catch(java.lang.InterruptedException intex){ intex.printStackTrace(); } } }
In our example, the process for getting a quote from a bank is modeled roughly along the same lines a real world bank operates. First there is a small amount of clerical work done followed by an access to the computerized rate quote system and then some additional amount of clerical work before the quote is returned to the BankQuoteGateway.
The Bank abstract class and the child bank classes (Bank1 to Bank5) model the operation of a regular bank. In our example, the bank receives the loan request and the clerical staff conducts the due diligence research while verifying that the customer information is accurate. We have chosen to model the clerical work as an arbitrary wait method implemented in the Bank abstract class as shown above and invoked in the getBankQuote method. To make things interesting, we will model the bank’s rate quote system for getting rate quotes, using webservices. For a given bank (Bankn) the rate quote system is modeled using a BanknWS and coded in a file named BanknWS.jws. There will be five bank classes (Bank1 to Bank5) and five rate quote systems (Bank1WS to Bank5WS). To make things even more interesting each of the rate quote systems will use a different format for the parameter list in the method call. This means the Bank class has to use a Translator to translate the format of the message before calling the webservice. We will show this after discussing the Bank classes.
You will note that the getBankQuote method in the abstract Bank class is an abstract method and has the parameters ordered in a particular format. We will now look at one of the bank implementations and for no particular reason choose Bank1. The class structure of all the banks will be identical and each will differ only in the values of its fields (the bank name and endpoint address) which are set when the bank object is constructed.
public class Bank1 extends Bank { public Bank1(String hostname, String portnum){ super(hostname,portnum); bankname = "Exclusive Country Club Bankers\n"; String ep1 = "http://" + hostname + ":" + portnum + "/axis/Bank1WS.jws"; this.setEndPoint(ep1); } public void setEndPoint(String endpt){this.endpoint = endpt;} public String getBankName(){return this.bankname;} public String getEndPoint(){return this.endpoint;} public BankQuote getBankQuote(int ssn, double loanamount, int loanduration, int credit_history_length, int credit_score) { BankQuote bankquote = new BankQuote(); Integer i1 = new Integer(ssn); Double i2 = new Double(prime_rate); Double i3 = new Double(loanamount); Integer i4 = new Integer(loanduration); Integer i5 = new Integer(credit_history_length); Integer i6 = new Integer(credit_score); try{ Service service = new Service(); Call call = (Call) service.createCall(); call.setTargetEndpointAddress( new java.net.URL(endpoint) ); call.setOperationName("getQuote"); call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN ); call.addParameter( "op2", XMLType.XSD_DOUBLE, ParameterMode.IN ); call.addParameter( "op3", XMLType.XSD_DOUBLE, ParameterMode.IN ); call.addParameter( "op4", XMLType.XSD_INT, ParameterMode.IN ); call.addParameter( "op5", XMLType.XSD_INT, ParameterMode.IN ); call.addParameter( "op6", XMLType.XSD_INT, ParameterMode.IN ); call.setReturnType( XMLType.XSD_DOUBLE); Double interestrate = (Double) call.invoke( new Object [] {i1,i2,i3,i4,i5,i6}); bankquote.setBankName(bankname); bankquote.setInterestRate(interestrate.doubleValue()); }catch(Exception ex){ System.err.println("Error accessing the axis webservice from " + bankname); BankQuote badbq = new BankQuote(); badbq.setBankName("ERROR in WS"); return badbq; } arbitraryWait(); return bankquote; } }
As seen in the code above, the getBankQuote method is implemented and has the parameters in a particular order.
public BankQuote getBankQuote(int ssn, double loanamount, int loanduration, int credit_history_length, int credit_score)
Implementing the Banking Operations
As described earlier, the format for the parameters of the rate quote system for each bank is different. This means the getBankQuote method of the Bank class implements the Translator pattern and has to translate the order of the parameters before calling the respective webservice. The details of the method signature for each bank webservice getQuote method is shown below:
Bank1WS:
getQuote(int ssn, double prime_rate, double loanamount, int loanduration, int credit_history_length, int credit_score)
Bank2WS:
getQuote(double prime_rate, double loanamount, int loanduration, int credit_history_length, int credit_score, int ssn)
Bank3WS:
getQuote(double loanamount, int loanduration, int credit_history_length, int credit_score, int ssn, double prime_rate)
Bank4WS:
getQuote(int loanduration, int credit_history_length, int credit_score, int ssn, double prime_rate, double loanamount)
Bank5WS:
getQuote(int credit_history_length, int credit_score, int ssn, double prime_rate, double loanamount, int loanduration)
The actual implementation of the getQuote method is a placeholder and returns a rate quote using a simple algorithm as shown below for Bank1WS.jws.
public class Bank1WS { public double getQuote(int ssn, double prime_rate, double loanamount, int loanduration, int credit_history_length, int credit_score) { double ratepremium = 1.5; double int_rate = prime_rate + ratepremium + (double)(loanduration/12)/10 + (double)(Math.random()*10)/10; return int_rate; } }
In a real world application, the formula is a lot more detailed and complicated. The return type of the getQuote method is a double precision number representing the rate the bank offers the customer given the parameters in the loan application.
Once again the Axis server automatically generates a WSDL file for each Bank JWS file (exposed at http://hostname:portnum/axis/Bank1WS.jws?wsdl) . The WSDL files for the other Banks will have a similar format but will differ in the definition of the parameters. The WSDL file for the Bank1WS.jws Web Service is shown below.
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"> <wsdl:message name="getQuoteRequest"> <wsdl:part name="ssn" type="xsd:int"/> <wsdl:part name="prime_rate" type="xsd:double"/> <wsdl:part name="loanamount" type="xsd:double"/> <wsdl:part name="loanduration" type="xsd:int"/> <wsdl:part name="credit_history_length" type="xsd:int"/> <wsdl:part name="credit_score" type="xsd:int"/> </wsdl:message> <wsdl:message name="getQuoteResponse"> <wsdl:part name="getQuoteReturn" type="xsd:double"/> </wsdl:message> <wsdl:portType name="Bank1WS"> <wsdl:operation name="getQuote" parameterOrder="ssn prime_rate loanamount loanduration credit_history_length credit_score"> <wsdl:input message="intf:getQuoteRequest" name="getQuoteRequest"/> <wsdl:output message="intf:getQuoteResponse" name="getQuoteResponse"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="Bank1WSSoapBinding" type="intf:Bank1WS"> ... </wsdl:binding> <wsdl:service name="Bank1WSService"> <wsdl:port binding="intf:Bank1WSSoapBinding" name="Bank1WS"> <wsdlsoap:address location="http://192.168.1.25:8080/axis/Bank1WS.jws"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
As we can see, the WSDL contains one operation getQuote defined in the <wsdl:operation> tag, which Axis maps to the getQuote method in the Bank1WS.jws class. The <wsdl:service> tag defines the Web Service Bank1WS Web Service. There is a request-response message pair, each defined in a <wsdl:message> tag and they are getQuoteRequest and getQuoteResponse.
This rate quote is set in a bank quote bean, which is added to the collection sent back to the BankQuoteGateway. The bean does not require any formatting and essentially the beans returned by all the banks look the same. This eliminates the need for a Normalizer to convert the reply messages to a common format.
The BankQuoteGateway selects the lowest quote from the collection of bank quotes it gets back and sends one bank quote bean back to the loan broker. The loan broker will access the data in the bean and format a report to send back to the client application. The method that formats the report is shown below:
private static String getLoanQuotesReport(BankQuote bestquote){ String bankname = bestquote.getBankName(); double bestrate = ((double)((long)(bestquote.getInterestRate()*1000))/(double)1000); String results = "\nBank Name: " + bankname + "Interest Rate: " + bestrate; return results; }
Client Application
The client application is the customer's interface to the loan broker application. The main requirement of the client is to gather information from the customer in a functional graphical user interface environment with adequate error checking. Beneath the covers and far away from the eyes of the customer, the client application will prepare the three pieces of data for delivery to the endpoint of the server application. For simplicity we designed the client application to be a java class with a main method that takes in the client information as command line arguments. In reality the user interface could be implemented as a windows based fat client or a browser based thin client. The client could also be part of another business system that has the client-programming model deployed. The most significant part of the client application with respect to invoking the webservice is shown below:
Service service = new Service(); Call call = (Call) service.createCall(); call.setTargetEndpointAddress( new java.net.URL(endpoint) ); call.setOperationName( "getLoanQuote" ); call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN ); call.addParameter( "op2", XMLType.XSD_DOUBLE, ParameterMode.IN ); call.addParameter( "op3", XMLType.XSD_INT, ParameterMode.IN ); call.setReturnType( XMLType.XSD_STRING ); String ret = (String) call.invoke( new Object [] {ssn, loanamount, loanduration});
The salient points in the code, are the lines, which define the method name (getLoanQuotes) and those that set the parameters and return types. The method getLoanQuotes takes in a customer id, loan amount and loan duration. The return value will be a string.
Since we are not using UDDI, the webservices lookup service, we hard code the endpoint URL for our webservice. Since we chose to deploy the loan broker application as a JWS file, the endpoint will have the standard format as defined by the Axis API for java web services (JWS). The end point for our deployment is shown below, where hostname and portnumber are the values of your server installation,
http://hostname:portnumber/axis/LoanBroker.jws
The client application which was blocked all along waiting for the response will accept the formatted report and display it in the GUI area setup for it. Alternatively it can be saved or sent to a printer.
Running the Solution
Please refer to the sections on installation of Axis and the loan broker application which appears towards the end of the chapter. The server now needs to be restarted or started if it were not running. Follow the documentation in the Tomcat help files for the proper steps to start or restart the server. You can then verify that Tomcat and Apache Axis are up and running by following the steps that were described in the section Installing XML-Axis at the beginning of this chapter.
Open a shell or command window on the client machine and run the application as shown for either Unix/Linux or Microsoft Windows:
java -classpath %CLASSPATH% LoanQueryClient [customerid[ [loanamount] [loanduration in months]
or
java -classpath $CLASSPATH LoanQueryClient [customerid] [loanamount] [loanduration in months]
for e.g.:
java -classpath %CLASSPATH% LoanQueryClient 199 100000.00 29
This invokes the webservice and returns the following results:
Calling the LoanBroker webservice at 1053292919270 ticks LoanBroker service replied at 1053292925860 ticks Total time to run the query = 6590 milliseconds The following reply was received from the Loan Clearing House Client with ssn= 199 requests a loan of amount= 100000.0 for 29 months Additional data for customer: credit score and length of credit history Credit Score= 756 Credit History Length= 12 The details of the best quote from all banks that responded are shown below: Out of a total of 3 quote(s), the best quote is from Bank Name: Exclusive Country Club Bankers Interest Rate: 6.197
You can test out the loan broker by entering different loan amounts when running the client. We will now analyze the output results with a single client running. Later on we will launch multiple clients and study the output.
Analyzing the Output
The client application keeps track of the time when it invokes the webservice endpoint on the server. The client also notes the time when the server responds with the result. Both start and end times of the call to the webservice are reported in the client application together with the difference between the two.
Calling the LoanBroker webservice at 1053292919270 ticks LoanBroker service replied at 1053292925860 ticks Total time to run the query = 6590 milliseconds
The Loan Clearing House webservice, reports all relevant details of the customer request sent across the network by the client application.
Client with ssn= 199 requests a loan of amount= 100000.0 for 29 months
The Loan Clearing House also reports additional credit data that was gathered to support the customer request.
Additional data for customer: credit score and length of credit history Credit Score= 756 Credit History Length= 12
The LoanBroker analyzes the data and selects a set of banks that fit the customer loan request criteria. The LoanBroker submits the customer data in the format required for individual banks and waits for each bank to respond. Since this is a synchronous application, this request is a blocking wait until a bank responds or the request times out or fails.
The LoanBroker collects all the responses, analyzes the return quotes and chooses the best quote. This quote is formatted and sent bank to the customer along with any relevant data.
The details of the best quote from all banks that responded are shown below:
Out of a total of 3 quote(s), the best quote is from Bank Name: Exclusive Country Club Bankers Interest Rate: 6.197
From the above code segment we see that the LoanBroker reports that three banks responded and it had select the best quote and presented that to the user.
Performance Limitations
As we discussed in the topic on the sequence diagram earlier on, the total time to get the response quote is significantly high since the loan broker has to wait for a response from each bank before submitting to the next bank in the lenders list. The net result is the customer will have to wait a long time to get the result quote back after submitting the request. We ran several baseline tests with a single client against the server and obtained an average of the total time to run the query (8070 milliseconds). We then launched four instances of the client in separate windows on the same client machine and obtained the following average times:
Client 1: 12520 milliseconds Client 2: 12580 milliseconds Client 3: 15710 milliseconds Client 4: 13760 milliseconds
While these tests could not be consider scientific by any stretch of imagination, the empirical evidence points to the fact that performance suffers tremendously when multiple clients try to simultaneously access our loan broker system.
Limitations of This Example
To make the discussion of the design of the loan broker example easier, we chose to implement all the webservices as JWS files. This gave us the advantage of deploying the service by simply copying it over to the server. The disadvantage, however, is that the webservice class gets instantiated when it is invoked and dies as soon as the service is complete. This means that some amount of the time lag we noticed is a result of the server creating instances of the class before invoking the service.
We could have chosen the more complicated route and designed a java class file that would get deployed using a WSDD file. This would give us the flexibility of defining how long the instantiated class would persist, i.e. for the entire client session or the duration of the application. The issue of how the webservice will be deployed is very significant when designing a real world application. If we had chosen to include the deployment issue, the description of the example would have become very lengthy and the design details would have become unnecessarily complicated for the purposes of this chapter.
Summary
In this section we stepped through the implementation of the loan broker application using synchronous access to SOAP/HTTP web services. We used predictive routing to submit our loan request to a set of banks. We highlighted the strengths and drawbacks of using this approach. We made some design tradeoffs to manage the discussion and avoid getting bogged down in describing deployment details. The overall intention was to provide a discussion on the merits and demerits of this approach. We also got to see many of the patterns described in this book used which will help in adapting the synchronous predictive approach to other business domains.