mnot is talking my language...

2008-03-20 @ 00:45#

i read this today from Mark Nottingham and it made me very happy:

While there’s a nice internal logic to mapping HTTP methods to object methods, it doesn’t realise the power of having generic semantics...

...GET, PUT and DELETE all have well-defined semantics. So well-defined that they really shouldn’t need application-specific code; after all, they’re just manipulating state in well-known ways. In fact, I’d posit that you can specify the behaviour of any RESTful resource by describing a) the processing that POST does, and b) any side effects of PUT and DELETE.

the pseudo-code he offers up looks *very close* to the way i designed the exyus web engine. compare mark's example here:

@store_type("mysql") # tell the Resource what implements GET, PUT and DELETE
@acl("choose your ACL poision") # tell who / when access is allowed, per-method and finer-grained
class Person (Resource):
    store_format = PersonML
    def POST(self, representation):
        # operate on the store...
        return representation
    def PUT_effect(self, representation):
        # called IFF the presented representation is storable, 
        # but before it is available; raising an exception will back it out
        return status_representation
class PersonML(Format):
    translations = {
        'application/xml': (self.to_xml, self.from_xml),
        'application/json': (self.to_json, self.from_json),
    def to_xml(self, native_input):
        # do whatever you've got to do
        return xml_output
    def from_xml(self, xml_input):
        # do whatever you've got to do
        return native_output

to an example of a *complete* resource class initialization for a read/write resource that supports multiple media-types:

// user group data example
class UGData : XmlSqlResource
    public UGData()
        this.ContentType = "text/html";
        this.ConnectionString = "exyus_samples";
        this.LocalMaxAge = 600;

        this.AllowPost = true;
        this.AllowDelete = true;
        this.DocumentsFolder = "~/documents/ugdata/";
        this.RedirectOnPost = true;
        this.PostLocationUri = "/ugdata/{id}";
        this.UpdateMediaTypes = new string[]

        // set cache invalidation rules
        this.ImmediateCacheUriTemplates = new string[]


no methods to define. just create the URI pattern(s) and media-types. set up some rules for caching (including invalidation templates). then build XSL/XSD transforms for the selected media-types and for validating incoming entity bodies. slick, clean, and stable.

check out the XSLT that returns JSON (if requested by the client):

<?xml version="1.0" encoding="utf-8"?>
  2008-03-02 (mca)
  ugdata - transform list/item for JSON clients

<xsl:stylesheet version="1.0" xmlns:xsl="">
  <xsl:output method="text" encoding="utf-8"/>

  <xsl:param name="id" />

  <xsl:template match="/">
      <xsl:when test="$id!=''">{"member" : <xsl:apply-templates select="//member"  mode="item"/>}</xsl:when>
      <xsl:otherwise>{"members" : [<xsl:apply-templates select="//member"  mode="list"/>]}  </xsl:otherwise>

  <xsl:template match="member" mode="list">
    <xsl:if test="position()!=1">,</xsl:if>
      "id" : "<xsl:value-of select="@id"/>",
      "lastname" : "<xsl:value-of select="lastname"/>",
      "firstname" : "<xsl:value-of select="firstname"/>"

  <xsl:template match="member" mode="item">
      "id" : "<xsl:value-of select="$id"/>",
      "firstname" : "<xsl:value-of select="firstname"/>",
      "lastname" : "<xsl:value-of select="lastname"/>",
      "birthdate" : "<xsl:value-of select="birthdate"/>",
      "experience" : "<xsl:value-of select="experience"/>"

you can check out the details @ and