Ruby SOAP4R For Web Services

February 9, 2006 - 4 minute read -

Web Services are about sharing. SOAP Web Services are about interoperability. If you don't need to share information or services and if you don't need to interoperate with other systems or other programming languages, then why incur the overhead of doing Service Oriented Architecture (SOA)? If you don't need to share services or data, save yourself a bunch of trouble and save yourself the performance penalty and build a monolithic application. There will always be systems that need to be built to share their services with other systems though.

I've (along with a colleague) been working on an example of Contract First Web Service design for a presentation. In doing so, I wanted to provide some examples in different languages (remember interoperable) to show users different ways of interacting with the service. I chose to build the service in Java using Apache Axis. We're building a client in in ASP.NET to use the service. When it comes down to it though Java and .NET are very similar though, so I thought I would choose another language that was very different from those too.

Ruby is pretty different, so I chose that.

Using SOAP4R

Ruby comes with a package called SOAP4R that handles all of the complexity of dealing with Web Services.

SOAP4R offers a couple of different ways to create a web service client. I chose to use the WSDLDriverFactory which parses a WSDL file and dynamically generates service classes at runtime.

service = SOAP::WSDLDriverFactory.new(WSDL_URL).create_rpc_driver
service.generate_explicit_type = true
service.wiredump_dev = STDOUT if $DEBUG

result = service.SearchContacts(:search => 'some_name')
if result.contacts.length > 0
    result.contacts.each { |c| print_contact(c) }
else
    puts "No contacts found..."
end

Ruby is a highly dynamic language which is one of its huge strengths. SOAP4R utilizes this by generating service bindings at runtime. This is a really cool feature but it can also make it difficult to figure out exactly what's going on. To get around this I ended up doing a lot of inspection of the runtime classes that were generated. This kind of inspection can be a bit tedious.

Luckily the SOAP4R package also includes some handy utilities for generating code from XSD and WSDL files.

  • xsd2ruby - A tool that will read an XML Schema Definition (XSD) and create classes from the defined entities.
  • wsdl2ruby - This tool extends the xsd2ruby tool to include creating wrapper classes for the service calls.

Using these tools gives you a file/files that contain the definitions for the business entities and services so that you have something to reference as you write code. The following isn't so different, but it uses generated code from the xsd2ruby tool.

The generated code will look something like:

# {urn:spiderlogic-com:contacts:service:v1}SearchContactsRequest
class SearchContactsRequest
  @@schema_type = "SearchContactsRequest"
  @@schema_ns = "urn:spiderlogic-com:contacts:service:v1"
  @@schema_qualified = "true"
  @@schema_element = [["query", "SOAP::SOAPString"]]

  attr_accessor :query

  def initialize(query = nil)
    @query = query
  end
end

As you can see, it adds in extra information that SOAP will need to map it to the proper schema elements.

Using this generated code, you get code like:

require 'service'

service = SOAP::WSDLDriverFactory.new(WSDL_URL).create_rpc_driver
service.generate_explicit_type = true
service.wiredump_dev = STDOUT if $DEBUG

result = service.SearchContacts(SearchContactsRequest.new('some_name'))
if result.contacts.length > 0
    result.contacts.each { |c| print_contact(c) }
else
    puts "No contacts found..."
end

One Problem I found

SOAP4R was originally built to support RPC Literal encoded Web Services. Support for Document Literal services is more recent. To get the service to work, I had to update to the latest trunk version from their Subversion server. Before the update I was not able to send Arrays of objects to my service. Document Literal services are preferred for Contract First design because it offers full schema validation of the soap message. Document Literal support is still a little bit rough around the edges, but I got it to work successfully.