Introducing Soap2Rest (S2R) 0.0.1

One of my first tasks at my job was to convert some of our WCF services so as to integrate them with Talend, an incredibly powerful data integration tool. Because the underlying WSDL produced by the guys at Big Mike does not follow the open standard of SOA, it is not possible to easily create a java proxy client for a WCF service with a tool such as wsdl2java.

One solution is to wrap WCF services around services created with Java, or implement another set of services for interoperability purposes in java. Doing so however would mean you’ll now have to support 2 sets of services in 2 different languages, which could lead to maintenance headaches.

The most desirable alternative in my opinion is to implement RESTful equivalents of the existing services. This allows you to keep the code base in a single language (C#), and will make it easier for consumers using Java, Ruby, and what not to consume your services. This still creates a situation in which the developers would have to maintain 2 set of services: the SOAP ones and the RESTful ones (unless you decide to get rid of the SOAP services). If you only have to do that for one service, the pain might be bearable. If you were to do this manually for several services however, maintaining the system will get harder.

Introducing Soap2Rest (S2R), a tool written in C# that automagically converts SOAP services to their RESTful equivalent based on their AST.

How does it work?

Based on the supplied configuration, S2R will traverse the AST of the service to be converted using NRefactory 4 (later versions will use NRefactory 5).

During the traversal of the interface’s AST, it will modify the Service Contract, adding the web request on top of the interface and changing the method signature. It will keep track of the modifications performed so as to refer to them during the conversion of the service implementation. The web request will be based on the operation name and the properties of the Data Contracts accepted by it.

During the traversal of the implementation’s AST, it will use the information kept from the traversal of the interface’s AST so as to modify only the method whose signature were changed. It will add a class called “RestfulConverter” that will produce conversion statement to essentially convert back the property of the Data Contracts that were broken into their original. Having preserved the body of the original soap implementation of each operation, it will add a line on top of it to recreate the original parameter, so as to use them with the original code. As you may have gathered, this creates code duplication (as in, the code from one service is duplicated on the other). Eventually, I will make it so it makes calls from the original service instead of duplicating its logic.

Example

Suppose you have the config below.

<config outputdir="RestServices">
  <contracts>
    <assembly>S2R.TestContracts.dll</assembly>
  </contracts>
  <complexTypes>
    <type Name="LookUpMessage" postSeperator="|">
      <argument Name="Name" />
      <argument Name="Id" />
    </type>
    <type Name="ItemRequest" postSeperator="|" >
      <argument Name="Id" />
    </type>
  </complexTypes>
  <convertibles>
    <add contract="interfaces\IComplex2.txt"
    implementation="implementations\Complex2.txt"/>
  </convertibles>
</config>

Note that it is possible to override how particular Data Contracts can be broken down in the config file, as well as which type of separator you want to use.

Suppose your datacontract assembly/assemblies had the following classes:

  [DataContract]
  public class Complexy1 {
    [DataMember]
    public long Id { get; set; }

    public DateTime DontIncludeNotMe { get; set; }

    [DataMember]
    public string Name;
  }
[DataContract]
  public class Complexy2 {
    [DataMember]
    public int Boom { get; set; }

    [DataMember]
    public Complexy2_1 ComplexyJanw { get; set; }

    [DataMember]
    public DateTime Beep { get; set; }

    [DataMember]
    public string BeepyBeep { get; set; }
  }

  public class Complexy2_1 {
    public string Jawn { get; set; }
    public string JawnyJawn { get; set; }
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public int Number { get; set; }
  }

Suppose you had the following Service Contract and Implementation in 2 separate files:

using System;
using SomeSusing;

[ServiceContract(Namespace = Namespaces.SomeNamespaces)]
public interface IJonyJawnp{
[OperationContract]
List FindAllMessageX(Complexy2 complex, long k);

[OperationContract]
List FindAllMessageY(Complexy1 complex);
}

using System;
using SomeSusing;

[Jawn]
public class JonyJawnp : IJonyJawnp{

public JonyJawnp(){
}

public JonyJawnp(string jawn, IRepository foSho){
}

[SomeShieldy]
List FindAllMessageX(Complexy2 complex, , string k){

return complex.ToJawn();
}

[MyCont]
List FindAllMessageY(Complexy1 complex){
return complex.ToJawn();
}
}

The tool will produce the following new contract and implementation:


using System;
using SomeSusing;
[ServiceContract(Namespace = Namespaces.SomeNamespaces)]
public interface IJonyJawnpRest
{
	[OperationContract()]
	[WebGet(UriTemplate = "findallmessagex/boom={arg0_0}|complexyjanw={arg0_1}|beepday={arg0_beepday}|beepmonth={arg0_beepmonth}|beepyear={arg0_beepyear}|beephour={arg0_beephour}|beepminute={arg0_beepminute}|beepybeep={arg0_3}|k={arg1}")]
	List FindAllMessageX(string  arg0_0, string  arg0_1, string  arg0_BeepDay, string  arg0_BeepMonth, string  arg0_BeepYear, string  arg0_BeepHour, string  arg0_BeepMinute, string  arg0_3, string  arg1);
	[OperationContract()]
	[WebGet(UriTemplate = "findallmessagey/id={arg0_0}")]
	List FindAllMessageY(string  arg0_0);
}

using System;
using SomeSusing;
[Jawn()]
public class JonyJawnpRest : IJonyJawnp
{
	public JonyJawnpRest()
	{
	}
	public JonyJawnpRest(string jawn, IRepository foSho)
	{
	}
	[SomeShieldy()]
	List FindAllMessageX(string  arg0_0, string  arg0_1, string  arg0_BeepDay, string  arg0_BeepMonth, string  arg0_BeepYear, string  arg0_BeepHour, string  arg0_BeepMinute, string  arg0_3, string  arg1)
	{
		var complex = RestfulConverter.ConvertToComplexy2(arg0_0, arg0_1, arg0_BeepDay, arg0_BeepMonth, arg0_BeepYear, arg0_BeepHour, arg0_BeepMinute, arg0_3);
		var k = arg1;
		return complex.ToJawn();
	}
	[MyCont()]
	List FindAllMessageY(string  arg0_0)
	{
		var complex = RestfulConverter.ConvertToComplexy1(arg0_0);
		return complex.ToJawn();
	}
	private class RestfulConverter
	{
		public static Complexy2 ConvertToComplexy2(string  arg0_0, string  arg0_1, string  arg0_BeepDay, string  arg0_BeepMonth, string  arg0_BeepYear, string  arg0_BeepHour, string  arg0_BeepMinute, string  arg0_3)
		{
			var item = new Complexy2();
			item.Boom = System.Int32.Parse(arg0_0);
			item.ComplexyJanw = SoapToRest.Test.ComplexTypes.Complexy2_1.Parse(arg0_1);
			item.BeepyBeep = arg0_3;
			return item;
		}
		public static Complexy1 ConvertToComplexy1(string  arg0_0)
		{
			var item = new Complexy1();
			item.Id = System.Int64.Parse(arg0_0);
			return item;
		}
	}
}

Not that (as of now) it introduces compile time error, mainly because of missing “using” directive for the web activation dll (for which you will need to add a reference), and because it does not rename the implemented interface (IJonyJawnpRest instead of IJonyJawnp). These 2 issues will be resolved by the time I create the v.0.0.1 branch.

Known Issues

  • Cannot yet process Data Contracts with deep graphs
  • Cannot yet process nullable objects

The code is available on github https://github.com/boguste/soap2rest. Code reviews are welcome.

To run the tool, use the command: “SoapToRest.Console.exe <config_filepath>”

Advertisements
Introducing Soap2Rest (S2R) 0.0.1

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s