Skip to content

guilhermesilveira/restfulie-www2010

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

<html>
Introduction

Hypermedia as the Engine of Application State
  Hypermedia for Structure
  Hypermedia for Distributed Application Behavior
  Domain Application Protocols

Contracts
  Media Types
  Protocols

<h1> Restfulie - A Hypermedia Framework</h1>

<h2> Architecture</h2>

Restfulie is based in client and server components independent from each other and our focus will be in the Rails port, more mature therefore currently containing most features implemented compared to the other languages available.

<h3> Common architecture</h3>

In every language Restfulie's server API is based in content type negotiation, hypermedia capable serialization and controller enhancement.

Content type negotiation takes place so the server and client can find the best representation suitable for their communication: by giving support to well known media types, a server can communicate with a higher number of clients.

Typical serialization tools do not consider its serializing object to be a resource, therefore it's relations or state transitions are not considered during both the marshaling and unmarshaling processes. Restfulie enhances those tools by providing default support to well known and custom media types capable of dealing with hypermedia links.

Web mvc frameworks usually support default REST behavior related to specific http verbs and URIs, but do not support default behavior related to the previous two features and others, as default resource creation, update, removal and search. Standard support to such functionalities is one of Restfulie's server objectives.

On the client side, Restfulie's handles content type negotiation and hypermedia capable serialization as the counter part in a request-response http communication. It's unique feature is based on how resource relations and state transitions are exposed and "browsed" through its API.

<h3> Specific technologies</h3>

The rails gem is supported on the server side by Ruby on Rails [link_on_notes]. On the client side, the serialization is done based on the active_support gem and dynamic hashes can be accessed due to jeokkarak's [link_on_notes].

The Java serialization tool chosen to be enhanced in order to support hypermedia was XStream [link_on_notes], although other providers can also be used. VRaptor [link_on_notes], built on top of Spring [link_on_notes], is the framework that provides the infrastructure for enhancing controllers and content type negotiation.

On the client side, the Apache HTTP Client API [link_on_notes] is used to provide http communication with the server.

<h3> Other languages</h3>

There is a Restfulie C# client API [link_on_notes] already built and a Erlang port [link_on_notes], with a completely different approach in the server side due to its functional language characteristics, is being written.

<h2> Restfulie: ruby example</h2>

<h3> Configuring a Rails application</h3>

In order to support our examples, a simple rails application needs to be created, based in an coffee ordering system. An order is composed of a location (eat in or take out), it's status and items. Every item has a milk type, size and drink name.

In order to pay for the order, a payment is composed by it's amount, cardholder's name, card's number, it's expiry month and year.

<h3> Server side domain model</h3>

A representation of that Order model in Rails would define the order resource relations (its payment and receipt) and transitions (pay, cancel, retrieve and update) through the Restfulie API. One of the possibilities follows:

<pre>
class Order < ActiveRecord::Base
	
	acts_as_restfulie do |order, t|
	  t << [:self, {:action => :show}]
	  t << [:retrieve, {:id => order, :action => :destroy}] if order.is_ready?
	  t << [:receipt, {:order_id => order, :controller => :payments, :action => :receipt}] if order.status=="delivered"
		if order.status=="unpaid"
	  	t << [:cancel, {:action => :destroy}]
	  	t << [:pay, {:action => :create, :controller => :payments, :order_id => order.id}]
	  	t << [:update]
		end
	end
	
end
</pre>

<h3> Custom media type and hypermedia support</h3>

Restfulie knows how to provide resource's representations for clients willing a 'application/xml', 'json' and 'xhtml' content. Although the first two do not understand elements as hypermedia links, but as text, the representations can be built with such media types.

If one is to use its own custom media type, or a well known one as 'application/atom+xml', one option is to configure the model:

<pre>
class Order < ActiveRecord::Base

  media_type 'application/vnd.restbucks+xml'

	# acts_as_restfulie ommited for brevity

end
</pre>

In order to include Restfulie's default behaviour for controllers, one can simply invoke a include call:

<pre>
class OrdersController < ApplicationController
  include Restfulie::Server::Controller
end
</pre>

A simple request to an existing order, requiring such media type will show all components of Restfulie's server architecture taking place:

<pre>
HTTP 1.1
GET /orders/15
Accept: application/vnd.restbucks+xml
Savas, can you fix this request, I am not sure...
</pre>

<pre>
200 OK
<order>
	<created-at>2010-01-09T15:18:29Z</created-at>
	<id>250</id>
	<location>TO_TAKE</location>
	<status>unpaid</status>
	<updated-at>2010-01-09T15:18:29Z</updated-at>
	<cost>10</cost>
	<items>
		<item>
			<created-at>2010-01-09T15:18:29Z</created-at>
			<drink>latte</drink>
			<id>250</id>
			<milk>DOUBLE</milk>
			<order-id>250</order-id>
			<size>LARGE</size>
			<updated-at>2010-01-09T15:18:29Z</updated-at>
		</item>
	</items>
	<atom:link rel="self" href="http://localhost:3000/orders/250"/>
	<atom:link rel="cancel" href="http://localhost:3000/orders/250"/>
	<atom:link rel="pay" href="http://localhost:3000/orders/250/payment"/>
	<atom:link rel="update" href="http://localhost:3000/orders/250"/>
</order>
</pre>

<h3> Resource creation</h3>

Restfulie's default behaviour while creating a resource is to use its representation to create an object related to our domain and save it to the database.

If the server can not understand the media type sent or it does not matches the one described for the object to be created, application/vnd.restbucks+xml in our case, a 415 response is given.

If a resource creation is successful, Restfulie returns a 201 response with the resource location in the HTTP response headers:

<pre>
201 Created
Location: http://localhost:3000/orders/250	
</pre>

<h3> Resource retrieval</h3>

Restfulie will negotiate the resource media type to be returned according to the HTTP specification of the Accept header.
The default resource retrieval behavior is to locate the resource, return 404 if not found, and return the resource representation that best matches the desires of the client.

ETag and Last-modified headers are added by default on the response, therefore even simple Rails application could benefit from using Restfulie, even though they might not use content negotiation or hypermedia: Restfulie generate content modification related headers by default.

<h3> Resource removal</h3>

Because Restfulie's default behaviour is to allow resource removal, we have to override it and implement two different processes through the order URI that can be achieved via a DELETE request: cancel the order if its still unpaid or deliver the order if it has already been processed:

<pre>
  def destroy
    @model = model_type.find(params[:id])
    if @model.can? :cancel
      @model.delete
      head :ok
    elsif @model.can? :retrieve
      @model.status = "delivered"
      @model.save!
    else
      head :status => 405
    end
  end
</pre>

As usual, a request to this URI with the DELETE verb when the resource is not supporting it returns a 405.

<h3> Resource update</h3>

Restfulie's default resource update process is to read the content received in a PUT request body and use it in the same way that the create behaviour would, but updating the entire resource in the server.

A PATCH default implementation is not yet provided but on the roadmap for a upcoming release, with a simple modification to only update incoming information.

<h3> Client side content</h3>

The easiest approach to using Restfulie on the client side is to access ActiveSupport to_??? method based on hashes in order to generate your request body's content. The following examples show how to generate xml based content:

<pre>
def new_order(where)
  {:location => where, :items => [
              {:drink => "latte", :milk => "DOUBLE", :size => "LARGE"},
              {:drink => "latte", :milk => "DOUBLE", :size => "SMALL"}
                  ]}.to_xml(:root => "order")
end
def payment(value)
  {:amount => value, :cardholder_name => "Guilherme Silveira", :card_number => "4004", :expiry_month => 10, :expiry_year => 12}.to_xml(:root => "payment")
end
</pre>

When accessing a REST system entry point, one will usually invoke a create or get operation on a specific URI - the entry point itself. The following example shows how to send a HTTP POST request with 'application/vnd.restbucks+xml' content based on the order defined earlier:

<pre>
def create_order(where = "TO_TAKE")
  Restfulie.at('http://localhost:3000/orders').as('application/vnd.restbucks+xml').create(new_order(where))
end
</pre>

Restfulie response will automatically deserialize the response body, if possible, and allow the client to access the web response:

<pre>
	response = create_order
	puts response.web_response.code
	puts response.web_response.is_successful?
</pre>

<h3> Client side navigation</h3>

After deserializing a response body, Restfulie analyses its relations and allows the client to browse through the resource's relations and state transitions by invoking related methods.

Invoking well-understood relations as 'delete' will send a DELETE request, but others which are particular to our domain need to describe which http verb to use. For example, when creating a payment resource through a "pay" relation, one needs to describe it as a post request with some specific content type:

<pre>
	order = create_order
  order.request.as('application/vnd.restbucks+xml').pay(payment(order.cost), :method => :post)
</pre>

A well known rel is called self and links back to the resource, therefore using Restfulie, invoking the self method will refresh the resource - and make use of etag, last modified and 304 responses.

<pre>
	 sleep 20
   order.self.retrieve(:method => :delete)
   receipt = order.self.receipt
	puts "Did something go wrong with the server? #{receipt.web_response.is_server_error?}"
</pre>

<h3> Waiting for specific situations</h3>

In some situations, a client wants to poll the server until some specific relation is available. For example, the client is waiting for its order to be ready so it can pick it up.

One can check whether a relation is available by querying the Ruby object:

<pre>
   order = create_order
   order.request.as('application/vnd.restbucks+xml').pay(payment(order.cost), :method => :post)
   while !order.respond_to? :retrieve
     sleep 1
     order = order.self
   end
   order.retrieve(:method => :delete)
   receipt = order.self.receipt
</pre>

Restfulie sends the received Etag and Last-modified information back to the server and while the order has not changed, the server shall respond with 304. Restfulie will then return the object itself, working as a local cache to the application, losing latency but avoiding excessive bandwidth usage.

<h3> Client side domain mapping</h3>

Restfulie allows clients to use their own classes. We have used hashes as objects so far due to jeokkarak's support: our order and receipt were jeokkarak objects that answer to method calls as hash accesses.

If one wants to map a receipt to a local order model, it can create and map it in a similar way that Restfulie's server api would:

<pre>
class Order
	uses_restfulie
	media_type 'application/vnd.restbucks+xml'
end
</pre>

<h1> Conclusion</h1>



<h1> Misc things Guilherme might do if you want</h1>

JUST COMMENT
- 405 on update

IMPLEMENT AND COMMENT
- update conflict (missing, I should have done it)

MISSING
- should I add "Adaptable" clients (as per the chapter5 example with the big loop)?
- a simple atom feed section?
- a coming next: 'open search default behaviour for free' section
- partial update with PATCH








TODO LATER
- deixar o codigo mais bonito no controller
- method *take* could be on the restfulie controller
- clientside domain mapping should not require to_xml and media_type sending

</html>

Releases

No releases published

Packages

No packages published

Languages