Introduction
Interoperability between custom-developed distributed systems running on Java/Linux platform and .NET/Windows remains a challenge even to this day. RPC based solutions like the “COM-CORBA bridge” address it but introduce a major liability in terms of brittle coupling. Several propriety and open source solutions based on web services, database integration and remoting bridges also attempt to address this problem. However such solutions often comprise deep and (in some cases expensive) software stacks introducing accidental complexity, large resource footprint, steep learning curve and performance degradation.
In this article, I present an architectural solution for cross-platform interoperation. The architecturally significant aspect of this solution is to surface, define and govern the structure and exchange of data across the two platforms. The architecture is based on ServiceInterface and Data Transfer Object (DTO) patterns.
The reference implementation demonstrates using this architecture to integrate two business systems, “Fulfillment Center” running on Java/Linux tier and “CustomerOrderProcessing
” running on .NET/Windows.
Background
Service Interface
The service interface implements the contract between the consumer and provider. This contract allows them to exchange information even if they are on different systems. The service interface is responsible for all of the implementation details needed to perform this communication.
Data Transfer Object
Data transfer object is a distribution pattern where you create a serializable object to transfer state between two systems. DTO has no behavior and its serialization/deserialization is handled by an assembler object.
XSD
XML Schema or XSD provide constructs to express data types in a platform/language agnostic format. For example, a data type to define Customer Address information in XSD will look like:
<xs:complexType name="Address">
<xs:sequence>
<xs:element name="name" type="xs:string" />
<xs:element name="zip" type="xs:integer" />
<xs:element name="city" type="xs:string" />
<xs:element name="country" type="xs:string" />
</xs:sequence>
</xs:complexType>
XXsd2Code
XXsd2Code is an open source code generator to generate serializable Java, C#, C++ and C++/CLI classes from XSD.
Problem
“Fulfillment Center” application is part of a warehouse management system that is written in Java and runs on RHEL. “CustomerOrderProcessing
“ is part of a POS system written in .NET and running on Microsoft Windows 2008 server.
Use Case
Any new order processed by the CustomerOrderProcessing
system needs to be sent to the "Fulfillment Center". Once the fulfillment system ships the order, it in turn notifies “CustomerOrderProcessing
”.
Solution Outline
- Based on the DTO pattern, create a schema that represents the structure of the data interchange between the two systems. This is the interface contract.
- Model this contract as an XSD.
- Use
XXsd2Code
to code generate platform specific contracts in C# and Java. - Create a service interface for the “Fulfillment Center” application using TCP/IP as transport.
- Create a service gateway for the “
CustomerOrderProcessing
“ that connects to this service interface.
Structure (Class Diagram) design.pdf
Behaviour (Sequence Diagram) design.pdf
Solution Implementation
Service Contract
- Defining the Contract
Using any XSD editor, create the following data contract. I created this XSD using Microsoft VS 2005.
- Code generates C# classes
Assuming the XXsd2Code.exe is in the same folder as the .xsd file, execute the following command:
XXsd2Code.exe .\/.\/C#
This will code generate C# bindings for the xsd contract:
namespace CustomerOrderAndFulfillment
{
{
namespace Contract
[Serializable]
public class FulfillmentRecord : ICloneable
{
public string FulfillmentRecordID;
public bool IsBackOrder;
public FuzzyCondition StorageTemperature;
...
}
[Serializable]
public class CustomerOrder : ICloneable
{
public string OrderNumber;
public CreditRating Rating;
public Address AddressInfo;
public List<OrderItem> Orders;
public CreditCardDetails CcInfo;
...
}
}
- Code generate Java classes
Execute the following command:
XXsd2Code.exe .\/.\/Java
This will code generate Java bindings for the xsd contract:
package CustomerOrderAndFulfillment.Contract;
public class FulfillmentRecord implements Cloneable
{
public String FulfillmentRecordID;
public boolean IsBackOrder;
public FuzzyCondition StorageTemperature;
...
}
package CustomerOrderAndFulfillment.Contract;
public class CustomerOrder implements Cloneable
{
public String OrderNumber;
public CreditRating Rating;
public Address AddressInfo;
public java.util.List<OrderItem> Orders;
public CreditCardDetails CcInfo;
...
}
Transport
- .NET TCP/IP Client
Use the TcpClient and NetworkStream to write a simple TCP/IP client
TcpClient client = new TcpClient(server,port);
NetworkStream stream = client.GetStream();
Byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
stream.Write(data, 0, data.Length)
byte[] buffer = new byte[1024];
stream.Read(buffer, 0, buffer.Length);
return buffer;
- Java TCP/IP Server
Using ServerSocket, BufferedReader and DataOutputStream write a simple TCP/IP deamon on port 1111.
ServerSocket welcomeSocket = new ServerSocket(1111);
Socket connectionSocket = welcomeSocket.accept();
BufferedReader inFromClient = new BufferedReader(new
InputStreamReader(connectionSocket.getInputStream()));
DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());
String request = inFromClient.readLine();
outToClient.writeBytes(response);
Data Transfer Object
- Write an assembler class to
Serialize
and DeSerialize
.NET service interface contract classes using XMLSerializer.
Serialize
method transforms the CustomerOrder
object into a stream of bytes so that it is sent to the FulfillmentCenter
server.
static string SerializeCustomerOrder(CustomerOrder order)
{
XmlSerializer serializer = new XmlSerializer(typeof(CustomerOrder));
using (MemoryStream memoryStream = new MemoryStream())
{
serializer.Serialize(memoryStream, order);
return Encoding.UTF8.GetString(memoryStream.ToArray());
}
}
DeSerialize
converts the response from the FulfillmentCenter
server into a strongly typed FulfillmentRecord
object:
static FulfillmentRecord DeSerializeFulfillmentRecord(byte[] stream)
{
XmlSerializer serializer = new XmlSerializer(typeof(FulfillmentRecord));
using (MemoryStream memoryStream = new MemoryStream(stream))
{
return serializer.Deserialize(memoryStream) as FulfillmentRecord;
}
}
- Write an assembler class to
Serialize
and DeSerialize
Java service interface contract classes using xstream.
DeSerialize
converts the request from the CustomerOrderProcessing
into a strongly typed CustomerOrder
object.
static CustomerOrder DeSerializeCustomerOrder(String request)
{
XStream serializer = new XStream();
RegisterXstreamConverters(serializer);
return (CustomerOrder) serializer.fromXML(request);
}
Serialize
transforms the FulfillmentRecord
object into a stream of bytes so that it can be sent back to the CustomerOrderProcessing
system.
static String SerializeFulfillmentRecord(FulfillmentRecord
fulfillmentRecord)
{
XStream serializer = new XStream();
RegisterXstreamConverters(serializer);
return serializer.toXML(fulfillmentRecord);
}
XMLSerializer and xstream XML compatibility.
To make sure xstream generates XML that is compatible with XMLSerializer, use the following code:
xs.aliasType("CustomerOrder", CustomerOrder.class);
xs.aliasType("OrderItem", OrderItem.class);
xs.aliasType("FulfillmentRecord", FulfillmentRecord.class);
xs.aliasType("Address", Address.class);
XStream Emun Serialization
To force xstream
serialize enum
s correctly write an Enum
Converter:
public class EnumSingleValueConverter< T extends Enum<T> > extends
EnumConverter implements SingleValueConverter
{
private Class<T> enumType;
public static <V extends Enum<V>> SingleValueConverter create(Class<V>
enumClass) { return new EnumSingleValueConverter<V>(enumClass); }
private EnumSingleValueConverter(Class<T> newEnumType)
{ this.enumType = newEnumType; }
public boolean canConvert(Class type) { return type == enumType; }
public Object fromString(String str) {
Object r = null;
Method m = enumType.getMethod("fromValue", String.class);
r = m.invoke(null, str);
}
public String toString(Object obj) { return obj.toString(); }
}
Using the Reference Implementation
Download and unzip the reference implementation, then follow these steps:
- Run the GenerateCrossPlatformDataBindings.bat.
- Open .\CustomerOrderProcessing-DotNetClient\CustomerOrderProcessing.sln and compile it.
- Open .\FulfillmentCenterSimple-JavaTcpIpServer using Eclipse or Netbeans.
- Export a runnable jar and name it FulfillmentCenterApplication.jar.
- Run the jar file using this command:
java -jar FulfillmentCenterApplication.jar
- Now run
CustomerOrderProcessing
using this command:
CustomerOrderProcessing.exe 127.0.0.1 1111
Note: If you modify the xsd (interface contract), then make sure to run the GenerateCrossPlatformDataBindings.bat again.
Integration Test
I used Eclipse Ganymede to create a runnable jar. To test my “Fulfillment Center” server application, I used Ubuntu 10.10 running as a VirtualBox
guest on windows 7 host. Make sure to add a bridge network adaptor for the VM so that the Windows .NET app can reach the Java server running on the Ubuntu guest. Now run Java/Linux “Fulfillment Center” server application.
Now run the “CustomerOrderProcessing
” .NET application. Make sure you point to the IPAddress
of the Linux Virtual machine.
Conclusion
The architectural solution described in the article enable you to:
- Decouple interoperation/communication plumbing from the business applications using the Service Interface pattern.
- Minimize number of calls between the two systems using the Data Transfer Object pattern.
- Surface, define, govern and ensure compile time enforcement of the service contract by
- creating platform agnostic data types using XSD
- then code generating platform specific data types using XXS2Code
History
- 7th February, 2011: Initial post