RESTful api design, HATEOAS and resource discovery

One of the core ideas behind HATEOAS is that clients should be able to start from single entry point URL and discover all exposed resources and state transitions available for those. While I can perfectly see how that works with HTML and a human behind a browser clicking on links and "Submit" buttons, I'm quizzed about how this principle can be applied to problems I'm (un)lucky to deal with.

I like how RESTful design principle is presented in papers and educational articles where it all makes sense, How to GET a Cup of Coffee is a good example of such. I'll try to follow convention and come up with an example which is simple and free from tedious details. Let's look at zip codes and cities.

Problem 1

Let's say I want to design RESTful api for finding cities by zip codes. I come up with resources called 'cities' nested into zip codes, so that GET on http://api.addressbook.com/zip_codes/02125/cities returns document containing, say, two records which represent Dorchester and Boston.

My question is: how such url can be discovered through HATEOAS? It's probably impractical to expose index of all ~40K zip codes under http://api.addressbook.com/zip_codes . Even if it's not a problem to have 40K item index, remember that I've made this example up and there are collections of much greater magnitude out there.

So essentially, I would want to expose not link, but link template, rather, like this: http://api.addressbook.com/zip_codes/{:zip_code}/cities , and that goes against principles and relies on out-of-band knowledge possessed by a client.

Problem 2

Let's say I want to expose cities index with certain filtering capabilities:

  • GET on http://api.addressbook.com/cities?name=X would return only cities with names matching X .

  • GET on http://api.addressbook.com/cities?min_population=Y would only return cities with population equal or greater than Y .

  • Of course these two filters can be used together: http://api.addressbook.com/cities?name=X&min_population=Y .

    Here I'd like to expose not only url, but also these two possible query options and the fact that they can be combined. This seems to be simply impossible without client's out-of-band knowledge of semantics of those filters and principles behind combining them into dynamic URLs.

    So how principles behind HATEOAS can help making such trivial API really RESTful?


    I suggest using XHTML forms:

    GET /
    
    HTTP/1.1 OK
    
    <form method="get" action="/zip_code_search" rel="http://api.addressbook.com/rels/zip_code_search">
       <p>Zip code search</p>
       <input name="zip_code"/>
    </form>
    
    GET /zip_code_search?zip_code=02125
    
    HTTP/1.1 303 See Other
    Location: /zip_code/02125
    

    What's missing in HTML is a rel attribute for form .

    Check out this article:

    To summarize, there are several reasons to consider XHTML as the default representation for your RESTful services. First, you can leverage the syntax and semantics for important elements like <a> , <form> , and <input> instead of inventing your own. Second, you'll end up with services that feel a lot like sites because they'll be browsable by both users and applications. The XHTML is still interpreted by a human—it's just a programmer during development instead of a user at runtime. This simplifies things throughout the development process and makes it easier for consumers to learn how your service works. And finally, you can leverage standard Web development frameworks to build your RESTful services.

    Also check out OpenSearch.


    To reduce the number of request consider this response:

    HTTP/1.1 200 OK
    Content-Location: /zip_code/02125
    
    <html>
    <head>
    <link href="/zip_code/02125/cities" rel="related http://api.addressbook.com/rels/zip_code/cities"/>
    </head>
    ...
    </html>
    

    This solution comes to mind, but I'm not sure that I'd actually recommend it: instead of returning a resource URL, return a WADL URL that describes the endpoint. Example:

    <application xmlns="http://wadl.dev.java.net/2009/02" xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <grammars/>
      <resources base="http://localhost:8080/cities">
        <resource path="/">
          <method name="GET">
            <request>
              <param name="name" style="query" type="xs:string"/>
              <param name="min-population" style="query" type="xs:int"/>
            </request>
            <response>
              <representation mediaType="application/octet-stream"/>
            </response>
          </method>
        </resource>
      </resources>
    </application>
    

    That example was autogenerated by CXF from this Java code:

    import javax.ws.rs.GET;
    import javax.ws.rs.QueryParam;
    import javax.ws.rs.core.Response;
    
    public class Cities {
        @GET
        public Response get(@QueryParam("name") String name, @QueryParam("min-population") int min_poulation) {
            // TODO: build the real response
            return Response.ok().build();
        }
    }
    

    In answer to question 1, I'm assuming your single entry point is http://api.addressbook.com/zip_codes , and the intention, is to enable the client to traverse the entire collection of zip codes and ultimately retrieve the cities related to them.

    In which case i would make the http://api.addressbook.com/zip_codes resource return a redirect to the first page of zip codes, for example:

    http://api.addressbook.com/zip_codes?start=0&end=xxxx

    This would contain a "page" worth of zip code links (whatever number is suitable for the system to handle, plus a link to the next page (and previous page if there is one).

    This would enable a client to crawl the entire list of zip codes if it so desired.

    The urls returned in each page would look similar to this:

    http://api.addressbook.com/zip_codes/02125

    And then it would be a matter of deciding whether to include the city information in the representation returned by a zip code URL, or the link to it depending on the need.

    Now the client has a choice whether to traverse the entire list of zip codes and then request the zipcode (and then cities) for each, or request a page of zip codes, and then request drill down to a parti

    链接地址: http://www.djcxy.com/p/41016.html

    上一篇: RESTful API运行时发现/ HATEOAS客户端设计

    下一篇: REST风格的api设计,HATEOAS和资源发现