Notes on ‘rethinking web services’

I recently read a good article by Matt Aimonetti on creating web services. He makes some particular good points.

Web APIs are increasingly exposing raw data

“But web APIs are also more and more used to expose raw data to other software. The challenge though, is that we haven’t changed the way we write web applications to adapt to this new way of providing data.

Lately more and more applications provide their raw data to the outside world via web APIs. ”

Matt observes that developers are increasingly focussed on the data. This could be for the reason that Open Data is becoming more established (eg. http://data.gov.uk) or perhaps because such web APIs are better designed this way and a selection mechanism is driving the surviving population of APIs on the web.

I agree too our current tools and methods could be out of date. Perhaps even our language needs updating: how accurate is it call these things ‘web APIs’ or ‘web services’?

Documentation

Matt makes another excellent point about documentation. Users of this exposed data may not have access to or understanding of your source code. They may not even be developers. So explicit documentation is important, and documentation is invariably more accurate if generated.

“For that I use a Domain Specific Language (DSL) which is a fancy way to say that I have some specific code allowing me to explicitly define my web services. These services exist as objects that can then be used to process requests but also to validate inputs, and even more importantly to generate documentation.”

Here’s an example of his DSL, which I’ve combined with the code for this implementation.

describe_service "hello_world" do |service|
  service.formats   :json
  service.http_verb :get
  service.disable_auth # on by default

  # INPUT
  service.param.string  :name, :default => 'World', :doc => "The name of the person to greet."

  # OUTPUT
  service.response do |response|
    response.object do |obj|
      obj.string :message, :doc => "The greeting message sent back. Defaults to 'World'"
      obj.datetime :at, :doc => "The timestamp of when the message was dispatched"
    end
  end

  # DOCUMENTATION
  service.documentation do |doc|
    doc.overall "This service provides a simple hello world implementation example."
    doc.example "<code>curl -I 'http://localhost:9292/hello_world?name=Matt'</code>"
  end

  # SERVICE IMPLEMENTATION
  # the returned value is used as the response body, all the Sinatra helpers
  # are available.
  service.implementation do
    {:message => "Hello #{params[:name]}", :at => Time.now}.to_json
  end
end    

What struck me about this is how similar it is to Philipp Meier’s compojure-rest for Clojure. For example, I’ve translated his example into compojure-rest’s defresource syntax :-

(defresource 

  ^{:doc "This service provides a simple hello world implementation example."
    :example "<code>curl -I 'http://localhost:9292/hello_world?name=Malcolm'</code>"
    :params ["name" "The name of the person to greet."]} 

  hello-world

  :available-media-types ["application/json"]
  :method-allowed? (request-method-in :get)
  :authorized? true

  :handle-ok (fn [context]
               {:message (format "Hello %s" 
                           (or (get-in context [:request :params "name"]) 
                               "World")) ; default
                :datetime (java.util.Date.)}))

In compojure-rest, we should be able to add test metadata to the defresource which, along with the map declaring the service itself, could be used to generate documentation, examples and tests.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>