Searching for Books on Amazon

Problem

You want to incorporate information about books or other cultural artifacts into your application.

Solution

Amazon.com exposes a web service that gives you access to all kinds of information on books, music, and other media. The third-party Ruby/ Amazon library provides a simple Ruby interface to the Amazon web service.

Heres a simple bit of code that searches for books with Ruby/Amazon, printing their new and used prices.

require amazon/search $AWS_KEY = Your AWS key goes here # See below. def price_books(keyword) req = Amazon::Search::Request.new($AWS_KEY) req.keyword_search(keyword, ooks, Amazon::Search::LIGHT) do |product| newp = product.our_price || Not available usedp = product.used_price || ot available puts "#{product.product_name}: #{newp} new, #{usedp} used." end end price_books( uby cookbook) # Ruby Cookbook (Cookbooks (OReilly)): $31.49 new, not available used. # Rails Cookbook (Cookbooks (OReilly)): $25.19 new, not available used. # Ruby Anns Down Home Trailer Park Cookbook: $10.85 new, $3.54 used. # Rubys Low-Fat Soul-Food Cookbook: Not available new, $12.43 used. # …

To save bandwidth, this code asks Amazon for a "light" set of search results. The results won include things like customer reviews.

Discussion

Whats going on here? In one sense, it doesn matter. Ruby/Amazon gives us a Ruby method that somehow knows about books and their Amazon prices. Its getting its information from a database somewhere, and all we need to know is how to query that database.

In another sense, it matters a lot, because this is just one example of a REST-style web service. By looking under the cover of the Amazon web services, you can see how to use other REST-style services like the ones provided by Yahoo! and Flickr.

REST-style web services operate directly on top of HTTP. Each URL in a REST system designates a resource or a set of them. When you call keyword_search, Ruby/ Amazon retrieves a URL that looks something like this:

http://xml.amazon.com/onca/xml3?KeywordSearch=ruby+cookbook&mode=books…

This URL designates a set of Amazon book records that match the keywords "ruby cookbook". Ruby/Amazon uses the Net::HTTP library to send a GET request to this URL. Amazon returns a representation of the resource, an XML document that looks something like this:

11 2

Ruby Cookbook Book <Authors> <Author>Lucas Carlson <Author>Leonard Richardson September, 2006 OReilly Media

Ruby/Amazon uses REXML to parse this XML data and turn it into Amazon::Product objects. An Amazon::Product is a lot like a Ruby Struct: its got a bunch of member methods for getting information about the object (you can list these methods by calling Product#properties). All that information is derived from the original XML.

A REST web service works like a web site designed for a software program instead of a human. The web is good for publishing and modifying documents, so REST clients make HTTP GET requests to retrieve data, and POST requests to modify server state, just like youd do from a web browser with an HTML form. XML is good for describing documents, so REST servers usually give out XML documents that are easy to read and parse.

How does REST relate to other kinds of web services? REST is a distinct design philosophy, but not all "REST-style" web services take it as gospel.[2] Theres a sense in which "REST" is a drive for simpler web services, a reaction to the complexity of SOAP and the WS-[3] standards. Theres no reason why you can use SOAP in accordance with the REST philosophy, but in practice that never seems to happen.

[2] Amazons web services are a case in point. They use GET requests exclusively, even when they e modifying data like the items in a shopping cart. This is very unRESTful because "put the Ruby Cookbook in my shopping cart" is a command, not an object the way a set of books is an object. To avoid the wrath of the pedant I refer to Amazon Web Services as a "REST-style" service. It would be more RESTful to define a separate resource (URL) for the shopping cart, and allow the client to POST a message to that resource saying "Hey, shopping cart, add the Ruby Cookbook to yourself."

[3] Amazons web services are a case in point. They use GET requests exclusively, even when they e modifying data like the items in a shopping cart. This is very unRESTful because "put the Ruby Cookbook in my shopping cart" is a command, not an object the way a set of books is an object. To avoid the wrath of the pedant I refer to Amazon Web Services as a "REST-style" service. It would be more RESTful to define a separate resource (URL) for the shopping cart, and allow the client to POST a message to that resource saying "Hey, shopping cart, add the Ruby Cookbook to yourself."

Like REST, XML-RPC and SOAP web services run atop HTTP.[4] But while REST services expect clients to operate on a large URL space, XML-RPC and SOAP services are generally bound to a single "server" URL. If you have a "resource" to specify, you include it in the document you send to the server. REST, XML-RPC, and SOAP all serve XML documents, but XML-RPC and SOAP serve serialized versions of data structures, and REST usually serves RDF, Atom, or Plain Old XML.

[4] SOAP services can run over other protocols, like email. But almost everyone uses HTTP. After all, they e "web services," not "Internet services."

If there were no Ruby/Amazon library, it wouldn be hard to do the work yourself with Net::HTTP and REXML. Itd be more difficult to write a Ruby XML-RPC client without xmlrpc4r, and much more difficult to write a SOAP client without SOAP::RPC::Driver.

The downside of this flexibility is that, at least for now, every REST service is different. Everyone arranges their resources differently, and everyones response documents need to be parsed with different code. Ruby/Amazon won help you at all if you want to use some other REST service: youll need to find a separate library for that service, or write your own using Net::HTTP and REXML.

See Also

Категории