In the context of Web services, interoperability can be summarized as meaning that the functional characteristics of the service should remain immutable across differing application platforms, programming languages, hardware, operating systems, and application data models. By definition, Web services should be interoperable, and the service consumer should not be tied to the service implementation.
However there are bound to be issues when applications use disparate SOAP libraries that generate and manipulate the underlying SOAP message, disparate programming languages, and disparate hardware-software stacks. The following are common causes for interoperability problems between these libraries or toolkits:
Implementations conform only to a subset of the full SOAP or other XML specifications.
Implementations depend on optional aspects of the SOAP specifications. For example:
Sending type information for encoded parameters is optional; however, if an implementation assumes this will be present in messages it receives, it may not interoperate with others that do not send this information.
There is no differentiation between SOAPAction values of "" and null in the specifications, but some implementations support both, whereas others do not quote this value at all for non-null SOAPActions.
Implementations interpret ambiguous definitions of the SOAP specification differently. For example:
It is not clear how a service should represent an RPC response with a void return and no out parameters. It could be an empty SOAP envelope, an empty SOAP response element, or even an HTTP 204 ("No Response") code.
A null value can be represented either by not including that XML element or by an element with the xsi:nil="true".
In general, architects should keep the following in mind while designing Web services:
Avoid propeietary extensions. Avoid building dependencies into the application that use any vendor-specific extension to the specifications JAX-RPC depends on (SOAP, WSDL, XML schemas, and HTTP).
Test interoperability. Never assume things will work as they should. It is essential to test interoperability of the service implementation across multiple consumers, especially if the consumers are outside the boundaries of the organization. Public interoperability tests are also available from the Web Services Interoperability organization (www.ws-i.org) and White Mesa (www.whitemesa.com).
Analyze disparate data models. When a service is used to integrate applications that have disparate data models, the models may need to be resolved by creating an intermediate model. For example, flutebank.com integrates with brokerage.com to provide customers the ability to view their accounts simultaneously online when in any of the portals. The data model for an account as represented in flutebank.com may be quite different from an account in brokerage.com. In this scenario, the architects will need to reconcile the models by creating an XML schema acceptable to both parties.
Analyze disparate data types. Data types passed as arguments and return types from the service invocation can impact interoperability.
JAX-RPC-defined data types. The data types and mappings defined in the specifications are available in all JAX-RPC runtimes. Because they are subsets of the XML schema specifications and map directly to the data types in the SOAP encoding, they are completely interoperable.
Custom data types. If the data type is custom defined (e.g., a java.util.HashMap of com.flutebank.accounts.Account objects), an XML schema must be created to describe the representation of the data and the custom serializer and deserializer for that data on the server. Such a schema may not be completely interoperable. In addition, the JAX-RPC client would need to write serializers and deserializers to invoke the service. We looked at handling custom data types earlier in this chapter. In summary, custom data types may not be completely interoperable across all service consumers.
Avoid custom data types. Custom data types that force the use of custom serializers and deserializers can potentially cause interoperability issues with other implementations. For example, a List may be represented differently by implementations from vendors A and B. If vendor A's client runtime is used to invoke a service deployed in vendor B's runtime, serialization errors may occur, because each implementation uses its own XML mapping of that data type. If the mapping is not available, the corresponding serailizers and deserializers will need to be written.
For collection classes in particular, the SOAPBuilders community plans to pursue interoperability testing across vendor runtime implementations.
Customize data, protocols, and encoding schemes. Architects should be wary of any code that customizes the messages. A good example is the Compress-Secure handlers example in Listing 10.13. The endpoint of that service cannot be invoked by clients that are not aware of the compression and security algorithm used and understood by the service. From a service perspective, there is no standard way to communicate this information (e.g., it cannot be specified in WSDL).
Promote portability of client code between JAX-RPC implementations. J2EE developers would be familiar with the concept of writing an EJB and deploying it transparently in a J2EE server. The EJB client can be written with complete transparency and used in any J2SE environment by simply altering configuration properties. This portability of client code does not translate identically in the JAX-RPC environment, especially when using custom data types. A JAX-RPC client is not guaranteed to be portable if it uses anything beyond the simple data types. This is tied to the way the serializers and deserializers are written, as discussed earlier. In other words, if architects choose vendor A's implementation of JAX-RPC and write client code that uses serializers and deserializers to invoke the service, they should not expect to simply take the client code and use it in vendor B's runtime. Applications should be designed to abstract away the specificity, minimizing the changes needed.
Let us now look at an example of interoperability in action. We will write a Microsoft C#.NET client to demonstrate how a JAX-RPC Web service can be consumed from a Microsoft.NET environment. We will use the BillPay service developed and deployed previously in this chapter. As in most practical service consumer scenarios, we will invoke a service based on the WSDL describing it.
During service deployment, xrpcc was used to generate the WSDL. This WSDL file can now be passed to the Microsoft.NET wsdl compiler, to generate the client-side stubs:
wsdl /l:CS /protocol:SOAP http://127.0.0.1:9090/billpayservice/billpayservice.wsdl
The above generates the C# source file Billpayservice.cs, which contains the structures and serialization rules based on the schema and bindings defined in WSDL.
Next, we build a Windows DLL out of the generated proxy code using the C# compiler, passing it the referenced dlls from the .NET framework:
csc /t:library /r:System.Web.Services.dll /r:System.Xml.dll Billpayservice.cs
The next step is to write a client and invoke the three methods exposed by the JAX-RPC Web service. Listing 10.17 shows the C# client code for this purpose.
BillPay service using System; namespace BillpayClient{ /// <summary> /// This is a simple C# client to invoke the flutebank.com Web service. /// @Author Sameer Tyagi /// </summary> class JAX-RPCClient{ /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main(string[] args) { // Instantiate the stub/proxy Billpayservice serv = new Billpayservice(); // Set the endpoint URL if(args.Length ==1) serv.Url=args[0]; else serv.Url= "http://127.0.0.1:9090/billpayservice/jaxrpc/BillPay"; // Invoke the schedule payment method PaymentConfirmation conf = serv.schedulePayment(DateTime.Today," my account at sprint",190); Console.WriteLine("Payment was scheduled "+ conf.confirmationNum); // Invoke the listSchedulePayment method PaymentDetail[] detail= serv.listScheduledPayments(); for(int i=0;i< detail.Length;i++){ Console.WriteLine("Payee name "+ detail[i].payeeName); Console.WriteLine("Payee name "+ detail[i].account); Console.WriteLine("Payee name "+ detail[i].amt); Console.WriteLine("Payee name "+ detail[i].date); } // Invoke the getLastPayment method Double lastpaid= serv.getLastPayment("my cable tv provider"); Console.WriteLine("Last payment was "+ lastpaid); } } }
The C# client code is remarkably similar to the JAX-RPC stub client written earlier, because of the syntactic and semantic similarities between the two programming languages. The client code can now be compiled and executed. The output will be similar to the following:
C:\Dotnetclient>billpayClient Payment was scheduled 81263767 Payee name Digital Credit Union Payee name Credit Payee name 2000 Payee name 8/1/2002 11:27:33 PM Last payment was 829
We have just developed a Web service in Java, deployed it in a JAX-RPC runtime, and exposed the service with only a WSDL interface. WSDL was used to develop a client in a completely different language and platform, C# and .NET, yet produced identical behavior.