What Has Erlang Ever Done For Me? Part II - Web Services & Webmachine

What Erlang taught me about... Writing Web Services

  • Erlang
  • Programming
  • Web Services
  • Webmachine
  • Webmachine for Ruby
Posted on

What Has Erlang Ever Done For Me? Part II - Web Services & Webmachine

What Erlang taught me about... Writing Web Services

Rob Westwood

Following on from our introduction to this series, we consider a radically different way of looking at writing Web services.

It is pretty well understood how you would write a Web service endpoint in most frameworks. Generally they are based on a Model-View-Controller pattern, with URL routes mapping to code to execute. If all you are returning is a simple web page, then that is quite straightforward. But perhaps you want to do more. Maybe you want to return JSON or XML depending on the client. Decide what to do based on an ETag. Return different media types based on negotiation. Mark whether a resource is Missing (404) or Gone (410). It can get confusing relatively quickly.

This way of thinking also muddies the resource-oriented nature of HTTP. Because a lot of our logic normally ends up in some form of Controller it is very easy to lump together concepts that belong on different resources. Moreover, as we are writing code that feels like actions, it easy to fall in the habit of writing code that is more like an RPC endpoint than a resource.

Webmachine arose from some work at Basho. To quote:

How has Erlang actually helped us? … We’ve also been able to create an entirely new design of webserver that makes writing truly RESTful webservices simple and quick. We’ve been able to troubleshoot problems by quickly opening shell connections to the running webservers and learning exactly what is going on. … In short, we’ve been able to iterate quickly over lots of proposed solutions to problems and choose based on experience, rather than weak assumptions and hearsay.

In other words, the different nature of Webmachine follows directly from the use of the Erlang language.

The result of that thinking is a framework which makes managing the richness of the HTTP protocol easier. But it does make describing what Webmachine is, harder. It can be viewed in a variety of ways:

  1. As a toolkit for REST applications
  2. As an application layer that adds server-side HTTP semantic awareness (this is how Basho describe it)
  3. As a set of functions over a HTTP resource to determine its state changes in response to events
  4. As an executable model of the HTTP protocol (which is a more understandable way of making then previous point)
  5. As a way of breaking down Web services to make the behaviour easy to understand, reason about, and test
  6. As a set of constraints to keep a developer honest in using the HTTP protocol in the way it meant to be used

An Example

That all sounds quite nebulous, so let’s look at an example. Webmachine has been portedand acted as an inspiration for frameworks in other languages. As this a series on how Erlang helps non-Erlang programmers like me, we’ll be using webmachine for Ruby.

Let’s consider a set of resources which represent people. Creating such a resource is easy

require 'webmachine'

class PersonResource < Webmachine::Resource
  def to_html
   '<html><head></head><body>Hello, person!</body></html>'
  end
end

we just then need to wire it up to the application

my_webservice = Webmachine::Application.new do |ws|
  ws.routes do
    add ['people', :person_name], PersonResource
  end
  ws.configure do |config|
    config.ip = '0.0.0.0'
    config.port = 8008
  end
end

my_webservice.run

which says to bind to port 8008 on all network adapters and pass all requests of the form /people/{person_name} onto our defined resource, which will return a simple impersonal greeting.

Notice that although my route says people, I’ve called my resource PersonResource as it only represents a particular person. If we want to implement a resource representing a collection of people, then we would define a separate resource class PeopleResource, and wire it up in the routes by calling add['people'], PeopleResource.

In our organisation, there are only two people, peter and rob; therefore we should only return a greeting if the name matches. To do so, we override the #resource_exists? method on the Resource.

class PersonResource < Webmachine::Resource
  def resource_exists?
    case request.path_info[:person_name]
    when 'peter', 'rob'
      true
    else
      false
    end
  end
end

and sure enough, requesting an invalid person returns a 404, just as it should:

$ curl -IsL http://localhost:8008/people/fred
HTTP/1.1 404 Not Found 

This is the way that we implement all functionality in webmachine; at each point when the HTTP protocol says that we need to make a decision we can supply code that implements the logic we desire. If the default behaviour is fine then we need do nothing. This is what we mean when we say that it is an executable model of the HTTP protocol.

As a second example, perhaps we need the ability to return html, json or xml based on the setting in the accept header. We will also personalise our greeting. All we need to do is to override the content_types_provided method to say what content types are supported. For each content type we will say what method should be called to get the data to return (which we name to_html, to_json and to_xml).

require 'active_support'
require 'active_support/builder'
require 'active_support/core_ext/hash/conversions'
require 'json'

class PersonResource < Webmachine::Resource
  def content_types_provided
    [
      ['text/html', :to_html],
      ['application/json', :to_json],
      ['application/xml', :to_xml]
    ]
  end

  def to_json
    { :name => request.path_info[:person_name] }.to_json
  end

  def to_xml
    { :name => request.path_info[:person_name] }.to_xml(:root => 'person')
  end
end

Testing our changes

$ curl -sL -H "Accept: application/xml" http://localhost:8008/people/rob 
<?xml version="1.0" encoding="UTF-8"?>
<person>
  <name>rob</name>
</person>

Benefits

The example that I’ve given is obviously very simple, and just shows 2 of the 37 callbacks available for customising behaviour. Even with this very simple example we can see how our assertion that webmachine was a way of breaking down Web services to make the behaviour easy to understand, reason about, and test.

  • There’s no fluff - everything the resource needs to do is up-front and central
  • Forced decomposition - every method is clearly focussed and requires very little code - no more 100+ line controller methods!
  • Simple structure - once you get to know the methods it’s dead easy to read as some level of consistency is enforced between resources and between developers
  • Easy testability - simple, short methods having single responsibilities makes testing a breeze.

Embracing Constraints

The benefits that we have gained have come about from embracing the constraints that we have been put under by webmachine. If we need to decide whether or not a resource exists, then we put the code in the resource_exists? method. At a later date, if there is an issue that resources are incorrectly returning 404s then another developer will know to also look at the resource_exists? method.

Similarly, although it is quite possible to abuse the HTTP protocol via webmachine - if you want to implement a destructive GET for example, then there’s nothing that will stop you - you are forced to reason about the problem in a very HTTPesque way. For example, using a traditional framework it’s very easy to only return 200/301/404/500 status codes. It could be the case that 202 (Accepted) might be correct, or maybe 409 (Conflict). Using webmachine encourages you to think as to what will be appropriate for clients.

Just the naming of the constructs encourages you to think about things differently. We noted that writing code in a controller encourages the mixing of concerns and a slide towards RPC-endpoint thinking. With webmachine, the mere fact that we are creating a resource encourages us to take a resource-oriented approach. If we find ourselves using the same resource class for multiple routes, then it immediately flags that we may be mixing concerns and whether creating additional resource would be a better-structured way of doing things.

Summary

Webmachine is highly opinionated. If you try and use webmachine in a way in which it is not intended to be used, then you will be in for a world of pain. On the other hand, it can be a clean and simple way for creating Web services in a way that honours the HTTP protocol and RESTful design principles.

Even if you decide that not to use webmachine or one of it’s derived frameworks, then knowing about it can be helpful when writing controllers in more traditional framework. The next time you write a controller, consider whether you are mixing concerns and whether 2 (or more) controllers would be more appropriate. You may think about whether using HTTP headers would be a better way of content control rather than query parameters, or consider whether other response codes would make more sense in your context.

What Has Erlang Ever Done For Me? - All Articles

These links will be updated as each article is published

  • Part I: Introduction
  • Part II: Web Services & Webmachine
  • Part III: The Actor Model
  • Part IV: Error Handling
  • Part V: Upgradeability