Translate

Thursday, March 7, 2013

REST vs RPC in Web Service Development

Ive been implementing web services using lightweight web service protocols such as XML-RPCJSON-RPC and (my favorite although proprietary) YAML-RPC for some time now.

I had some discussions last year about web service development using REST, a concept I had heard about for some time, but I did not see the advantages of it.  I knew that it had become the predominate web development architecture model, but for some reason I could not see the advantages of REST over pure lightweight RPC approaches.

My first experiences with an RPC protocol over HTTP was with SOAP, which is a technology I've grown to strongly dislike; now I consider SOAP a horrible enterprise technology since it's cumbersome and expensive to implement and often suffers from serious compatibility issues between different vendor implementations (Qore also has SOAP support - both server and client, however I would recommend avoiding it if you can).   My great appreciation for the lightweight RPC over HTTP protocols listed above is to no small degree based on my dislike of SOAP, so when hearing and reading about REST, I could not see that it would be as great a leap as from SOAP to XML-RPC for example.

(As a footnote Wikipedia lists XML-RPC as the precursor to SOAP, which makes SOAP an absolutely classic case of overengineering by committee - since from my point of view XML-RPC with all its faults is vastly superior to SOAP.  Also here is a link which humorously sums up my thoughts on SOAP: http://harmful.cat-v.org/software/xml/soap/simple)

However, finally I've gotten into REST a little deeper, and having developed an initial RestHandler for the Qore HTTP server, the advantages of a REST architecture are completely clear and compelling.

Using REST forces you to have a logical and straightforward organization of your web service development.  Each type of object will have it's own URL, and (in the Qore RestHandler at least) it will have a dedicated class to handle requests to objects of that type.

For example, in the Qore RestHandler, if you register a class with the name "workflows" at the root level of your REST handler, then a request like

   GET /workflows

will be directed to your workflow class's get() method, which will handle the request.

Classes (and therefore URLs) can be stacked, so a request like

   GET /workflows/1/instances/2?action=stop

Will be directed to the appropriate class's "getStop()" method.   If the appropriate method name (derived from the HTTP method name in the request and any action verb given as a URI argument) is not implemented in the class, then the RestHandler returns a user-friendly 400 response.

This easy and logical hierarchical organization of the objects corresponding to the URLs along with the simple dispatching to handler methods of objects of the appropriate class makes implementing the REST service trivial in Qore.

Additionally, the RestHandler transparently takes care of deserializing request bodies based on the HTTP Content-Type header and serializing any response body based on the client's HTTP Accept header.

This also makes the REST approach superior to the lightweight RPC protocols mentioned above.

It's easy to see why REST is the dominant web service architecture; Qore's RestHandler will continue to be under active development for the upcoming release of Qore (0.8.8), and I hope it will be useful for others as well.

Saturday, March 2, 2013

Class Inheritance vs Class Wrapping

Qore supports "standard" class inheritance for polymorphism, but also supports another construct that may be unique to Qore.  In Qore there is the possibility of "wrapping" a class and providing a compatible interface with the wrapped class without using inheritance.

There are several advantages and also some drawbacks to this approach.  Let me first explain what I mean by "wrapping" a class and also explain how it's done in Qore.

To "wrap" a class, you embed an object of the desired class as a member of the new class and implement a methodGate() method that redirects method calls from outside the class to unimplemented methods.  Additionally a memberGate() method can be implemented to redirect accesses to unknown members from outside the class and return an arbitrary value for the member access.

For example, you can wrap a class to provide logging for all method calls or provide external thread synchronization to an unsynchronized class.  The following is an example that does both.

class MySocket {
    private {
        Socket $.sock();
        Mutex $.m();
    }

    static log(string $fmt) {
        vprintf(now_us().format("YYYY-MM-DD HH:mm:SS.us Z") + ": " + $fmt, $argv);
    }
    
    any methodGate(string $method_name) {
        MySocket::log("MySocket::methodGate() method called: %y args: %y\n", $method_name, $argv);
        $.m.lock();
        on_exit $.m.unlock();

        return callObjectMethodArgs($.sock, $method_name, $argv);
    }
}

This can be very useful for testing and for other purposes, requires very little typing to implement, and its purpose is clear from the implementation (even without comments).  Additionally, this method is completely independent of the definition of the wrapped class.

The main disadvantage is that it doesn't claim compatibility with the Socket class, so you cannot pass an object of class MySocket to code that expects a Socket object.

Currently there is no way in Qore to wrap a class in this way and to remain type-compatible (i.e. to appear to be a subclass of the wrapped class).  To remain type-compatible in this way, every method of the class has to be explicitly wrapped as above, and if the wrapped class has any method interface changes (for example, new method), the subclass has to changed accordingly.

However despite the drawbacks, it's still a very useful construct.   Possibly a future version of Qore will allow such wrapped classes to claim class compatibility with the wrapped class and therefore address the typing issues.