Webmachine describes resources on top of HTTP to provide RESTful APIs. This is pretty much what every modern web framework states, right?
After writing some good amount of controllers on MVC frameworks you realise that it's complicated to keep a clean controller without using callbacks/middlewares to handle things like:
On Rails code, for example, It's common to find before_filters (before_action on Rails 4) describing behaviour that should happen before handling the resource itself. Here's an example of discourse's ApplicationController:
class ApplicationController < ActionController::Base
# ...
before_filter :set_current_user_for_logs
before_filter :set_locale
before_filter :set_mobile_view
before_filter :inject_preview_style
before_filter :disable_customization
before_filter :block_if_readonly_mode
before_filter :authorize_mini_profiler
before_filter :preload_json
before_filter :check_xhr
before_filter :redirect_to_login_if_required
# ...
end
These callbacks will run doing assorted things on pretty much every controller on the app. Just by looking on the name of the callbacks you can notice some redirection, authorisation, authentication and content representation logic. One important thing is that the order here matters and you will probably hit lots of issues if you change the order of the before_filters. There's also the problem that unit tests are complex as you need to test each callback indirectly.
Could it be less complicated? Of course!
Webmachine describes the request handling as a state machine where HTTP status codes are end states. It implements the HTTP protocol as a collection of functions that will describe the flow into this state machine. Below you can see the initial state and some transitions:
Here's an example from webmachine-ruby
:
class OrderResource < Webmachine::Resource
def allowed_methods
["GET"]
end
def content_types_provided
[["application/json", :to_json]]
end
def to_json
order.to_json
end
private
def order
@order ||= Order.new(params)
end
def id
request.path_info[:id]
end
end
This simple resource you will get automatically the following status:
Accept
header that tolerates application/json
;Now let's handle when the order does not exist adding the resource_exists?
method to the resource:
class OrderResource < Webmachine::Resource
def allowed_methods
["GET"]
end
def content_types_provided
[["application/json", :to_json]]
end
def resource_exists?
order
end
def to_json
order.to_json
end
private
def order
@order ||= Order.new(params)
end
def id
request.path_info[:id]
end
end
Now 404 Not Found will be given if resource_exists?
returns falsy value.
Let's now add authorisation and authentication:
class OrderResource < Webmachine::Resource
include Webmachine::Resource::Authentication
def allowed_methods
["GET"]
end
def content_types_provided
[["application/json", :to_json]]
end
def resource_exists?
order
end
def is_authorized?(authorization_header)
basic_auth(authorization_header, "My Application") do |username, password|
@user = User.auth!(username, password)
!@user.nil?
end
end
def forbidden?
order.allow?(@user)
end
def to_json
order.to_json
end
private
def order
@order ||= Order.new(params)
end
def id
request.path_info[:id]
end
end
And again easily we will get:
is_authorized?
returns falsy value;forbidden?
returns falsy valueThis is, in my opinion, a scalable way of describing your resources as the code will be as complex as the resources are. The framework already takes care of the nuances of the HTTP protocol keeping the code clean and extremely simple to unit test.
The original implementation of Webmachine is in Erlang but you can find webmachine in multiple programming languages: Erlang, Ruby, Clojure, Javascript, etc ...
There's also a neat presentation by Sean Cribbs: Resources, For Real This Time (with Webmachine)