Monday, May 31, 2010

I spoke at the Gateway Software Symposium (NFJS St. Louis) last weekend. After my RESTful Web Services with JAX-RS talk, one of the attendees asked me how to discover the proper URLs when starting to interact with a RESTful service. The impetus for the question came from my statement that many RESTful services don't have large specifications (such as the WSDL files for "Big" web services).

I explained that well-designed RESTful services are "well-connected". By this, I mean they adhere to the idea of "hypermedia as the engine of application state", or HATEOAS. If your service adheres to HATEOAS, your service is accessible and navigable given just a single URL. How can this be?

Recall that in RESTful services, we use URIs/URLs to connect various related resources. To be well-connected (I'll use this term in place of to HATEOAS mostly because the acronym looks odd to some people) and be RESTful, representations of resources should contain URIs/URLs to related resources. To make this clearer, let's look at a specific example. As we look at this example, we'll make note of the aspects of the API which couldn't be discovered by examining the results of searches or resource retrieval.

In my talks on JAX-RS, I use a simple music service. In this service, each artist has a name and some number of music albums they have published. Each album as a name and some number of tracks on that album. Here is a simple UML diagram of the objects:

We now come to the first piece of information we can't discover for ourselves. We need to know a starting URL. With the example service, it's simply a host name and a port, as in: "http://localhost:3131".

When we run the music service and hit it with a browser, we see a spartan web page:

While this certainly allows us to do some (mildly) interesting things, how does it help us discover the API for this RESTful service? The answer lies in examining the underlying HTML. When we do that, we see this:

At first, there might not seem to be much here. But let's look at the form for searching for artists:

From this we can see that we can search for artists whose name "is" (IS), "starts with" (STARTS), "contains" (CONTAINS), or "ends with" (ENDS) the text specified by 'searchString'. If you don't live and work with HTML forms and their resulting queries every day, this still might not be obvious. So we can always go ahead and do a search. Let's search for artists whose name begins with 'F':
Clicking the 'Search' button does two things for us:

1) It performs the search and gives us the results:

2) It shows us the proper format for executing queries against the service (in the browser's address text box):

Looking at this, we know that we can query for artists by specifying a searchType and a searchString if we search under /artists.

My example music service supports more than HTML for representations. It also supports an XML format and a JSON format. In order to use the XML format, we need to specify that we accept a content type of "application/music-xml" (a content type I made up for this example). This is the second piece of information the service itself did not tell us. However, we could simply query the service and get back an HTML representation of the search results so it's not strictly necessary. The XML format is a more compact representation than the HTML, but the same information is contained in the HTML version.

If we use the curl command line utility to query:

curl --header "Accept: application/music-xml" "http://localhost:3131/artists?searchType=STARTS&searchString=F"

We get XML results:

Because this service is designed with API discovery in mind, we see that it reminds us of the search that was performed:

< artists uri="/artists?" searchString="F" searchType="STARTS">

In these results we also see an example of how to build well-connected services. Notice that each search result lists not only the name of the artist found, but also the URI of the resource we would need to retrieve in order to get a representation for that artist. For example, the artist "Fish" lists a URI of /artists/id/149. If we go to the music service in the browser and query for this URI (a URL of http://localhost:3131/artists/id/149), we get information about the artist named Fish:

Looking at the HTML, we see:

And if we query for the XML representation using curl:

curl --header "Accept: application/music-xml" "http://localhost:3131/artists/id/149"

We see:

Notice that the information about each album contains a URI we can use to retrieve a representation of that album, just like the search results listed a URI for each artist that was found. These are examples of how we build a well-connected RESTful service. By making sure that each HTML page (or XML result) contains URIs for related resources, users of our services can discover the proper URIs/URLs to use when invoking our service without a large specification.

In part two, I'll explore this notion further, looking at how our self-describing service allows us to discover how to create and delete resources as well as search for and retrieve them.

No comments: