Thursday, March 21, 2013

Consuming SOAP services with Ruby / Rails

Savon is a ruby soap library, and I mostly like it. I've been using the latest Savon to do integrations with SSRS at work.

Getting started
As practical advice, I strongly recommend you go and find a decent pre-built SOAP IDE. I've used XMLSpy and Oxygen before, which have a slightly wider focus than just pure SOAP; but in recent times I've come to like SoapUI quite a lot.

Compared to other tools, it will:

  • Take a WSDL
  • Generate a sample request for each action
  • Generate code for you in you happen to be writing in Java - and stub services
  • Allow you to easily set different endpoints (ie: prod, staging, dev) without stuffing around with WSDLs.
  • Allow you to develop a number of premade requests which will return expected responses.
In short, it's a heck of a tool with a low price point: free.

If you aren't developing in C# or similar, MSDN isn't very helpful. Not having had to... enjoy... windows servers for some time, I found a lot of time was spent:
  • Working out if I was authenticating correctly (browser to web UI, browser to WSDL, SOAP UI to WSDL, SOAP UI to SOAP Action, Actual Code to Soap Action)
  • Blundering around looking for configuration files in a file manager, instead of using google to find a likely file path and using vim until success was achieved.
  • Finding all kinds of Authentication that should work, simply don't. In the end, if the SOAP MSDN articles had simply pointed me to Configure Basic Authentication on the Report Server, I think I have achieved more.
  • Discovering the docs were incorrect re what WSDLs were actually available vs what the server published, and what worked vs the configuration of 'Native Mode'.

Once you get over the successive hurdles of Auth, identifying the correct service, and guessing that ListChildren is the method you want to get a list of reports, it's fairly straight forward.

Gotchas with Savon
A few of the soap calls wanted specific soap headers. Savon only supports global soap headers, and once you've built the object it's hard to mutate it.

1) To get more control, ditch the Savon::client() factory method - it's not really doing too much you can't do yourself.

2) If you need to monkey with the global options, you can often extend your client to make them accessible.

class ExtendedSoapClient < Savon::Client
   attr_writable :globals

3) It's worth stealing from Javaland in most cases - for each configured Savon Client, do up a quick FooServiceProxy. Inject your client into the constructor, and for all of the management of WSDLs, go stick that in your environment config.
If needed, go build yourself a SSRSServiceFactory to handle all of this wiring up.

The primary advantage - you get a reasonably clean API that can return either hashes, or if you go a little further you can generate models for the SOAP responses and have those appropriately populated.

It's important to remember these proxy classes exist only to translate a SOAP request from your code, to the transport layer, and to return a simple, meaningful result to your code.

That means avoiding a lot of logic in favour of making an API explicit.

No comments: