Some RPC With Your JsonRestStore

By on January 29, 2009 3:53 pm

REST is a powerful architecture because of its loose coupling design that facilitates a high level of interoperability. However, even the dissertation that defines REST states that one of the trade-offs of REST is that it “degrades efficiency, since information is transferred in a standardized form rather than one which is specific to an application’s needs”. That is, you might be able to achieve more efficient operation using a mechanism that goes beyond REST’s uniform interface, that is more specific to your application. This comes at the price of a loss of interoperability and should only be used if necessary, but with this in mind, let’s look at a good way to use RPCs that integrate with Dojo‘s REST module, the JsonRestStore. This approach builds on the REST architecture, while allowing exceptions as needed.

With this method, we will use JSON-RPC to send RPC requests to a server. The JSON-RPC calls are integrated with our REST architecture by using the URL of the target resource/object as the URL for the JSON-RPC request. Let’s send a request to send an email to one of our friends. A targeted JSON-RPC call might look like:

POST /Friend/dylan

{"method": "sendEmail",
 "params": ["Hi there"],
 "id":"call0"
}

One of the advantages of this approach is that it allows for a very object-oriented approach to RPC. JSON-RPC by itself only provides a flat procedural calling mechanism, but by targeting the calls to resource-associated URLs, the RPCs have a target object and can trigger a true object-oriented method call.

Inheritance in JsonRestStore

Now, let’s look at how we can set up a JsonRestStore so we can easily make RPC requests through methods. First, the JsonRestStore allows you to add functions on a prototype object that will be inherited by all items in the store. The prototype object is defined by the schema object that can be passed to the JsonRestStore on construction. For example, we could define a function that would calculate the full name of a person:

var friendSchema = {
	prototype: {
		getFullName: function(){
			return this.firstName + ' ' + this.lastName;
		}
	}
}
var friendStore = new dojox.data.JsonRestStore(
	{target:"/Friend/", schema: friendSchema});

And now this function will be inherited by the items in our friendStore:

var newFriend = friendStore.newItem({firstName: "Abraham", lastName: "Lincoln"});
newFriend.getFullName() -> "Abraham Lincoln"

This functionality is covered in more detail in our post about the effective use of JsonRestStore.

Now, we will create a function that can add a function that triggers the JSON-RPC request when called (understanding the internals of this function is not critical, you may want to simply copy it):

function addRpcFunction(schema, name){
	schema.prototype[name] = function(){
		// execute a JSON-RPC call
		var deferred = dojo.rawXhrPost({
			url: store.target + store.getIdentity(this),
			// the JSON-RPC call
			postData: dojo.toJson({
				method: name,
				id: callId++,
				params: dojo._toArray(arguments)
			}),
			handleAs: "json"
		});
		deferred.addCallback(function(response){
			// handle the response
			return response.error ?
				new Error(response.error) :
				response.result;
		});
		return deferred;
	};

}
var callId = 0;

Now we can easily add our sendEmail function to the prototype of the schema, as well as any other methods we want:

addRpcFunction(friendSchema, "sendEmail");
addRpcFunction(friendSchema, "poke");

Triggering an RPC is now as simple as a method call on an item from the store:

friendStore.fetchItemByIdentity({
	identity: "dylan",
	onItem: function(item){
		item.sendEmail("Hi there");
	}
});

And this will trigger the HTTP request containing the JSON-RPC call we described at the beginning of the article:

POST /Friend/dylan

{"method": "sendEmail",
 "params": ["Hi there"],
 "id":"call0"
}

The server can then easily find the associated resource and call the server-side sendEmail method with the resource as the target. The server could then respond with:

POST /Friend/dylan

{"result": true,
 "error": null,
 "id":"call0"
}

The client method call will return a Deferred object. This object can be used to get the return value from the RPC call when it finishes (it will execute asynchronously):

item.sendEmail("Hi there").addCallback(function(result){
	// with the server response above, the result value will be true
});

All of this is done with an intuitive combination of REST and RPC, using the widely adopted JSON-RPC format. Persevere is one server that supports this approach, allowing JavaScript functions on the server to be executed via JSON-RPC requests with persisted objects as the targets. However, it is straightforward to implement this mechanism yourself, as the JSON-RPC specification is very simple.

Summary

Adhering to a uniform interface as prescribed by REST and defined by the HTTP specification will certainly afford maximum interoperability. However, if your application needs to use specific functions for maximum efficiency, or for operations that don’t fit well with REST (sending an email is a good example, since it is awkward to model as a state change), JSON-RPC is a great tool for sending specific RPC using a format with a high degree of adoption. Using targeted JSON-RPC allows RPCs to build on the REST architecture and work in conjunction with it. This combination provides an object-oriented technique for remote calls. Many of the benefits of REST can realized, while the freedom to perform more customized operations is still available.

Comments

  • Great post, Chris!

    But doesn’t this almost warrant a new type of store (or service interface)? It’s great we’re not confined to pure REST URLs, and that additional functions can be added in addition to create/update/delete (via addRpcFunction) — but why limit this great idea by trying to cram it into a model that doesn’t work for it?

    It seems that this should be broken out into some more generic ServiceStore that can do REST as an option, but that has it no more required than any other method of server interaction.

  • @Laura: I am not sure I understand what you are asking for in the context of a generic store. The point of this post was that RPC can piggyback off of REST for providing a target for the method calls. Without that combination, JSON-RPC is simply a flat map of procedure calls. We already have support for that with Dojo, and we already have support for turning those method calls into a store with ServiceStore.

  • Why not just make an e-mail a resource and POST that content type to dylan?

    POST /Friends/dylan
    {’email’:{‘subject’:’hi dylan’, ‘body’:’feel free to reply whenever’}}

    There’s generally not a need to open up server actions and run a protocol (RPC) inside a protocol (HTTP). E-mail, especially, is just another resource type, not an “action” that falls outside of standard HTTP methods.

  • @Benjamin: You certainly can try to stick to a more RESTful approach, that is fine. The advantage of RPC is that it the communication can be clearly derived from and bound to the method signatures of the client and methods with minimal effort; no extra effort is required to define a new resource that maps to the methods involved. As pointed out, even Roy Fielding’s dissertation suggests that REST is not always the most appropriate tool. The point of this article isn’t really to dictate when to use REST and when to use RPC, just to show how that can be used in an integrative fashion with JsonRestStore in situations where you might find each to be appropriate within an application.

    Just out of curiosity, do you see your suggestion as being any more RESTful than stating JSON-RPC in the terms of POSTing a new “call” resource representation to dylan (rather than POSTing a new “email” resource representation)? Both seem to have the same effect on intermediaries.

  • Cedric Hurst

    I believe that a “call” or “email” resource would make sense as a child of the “dylan” resource, but I do agree with @Benjamin that the “sendEmail” unnecessarily breaks pure RESTful grammar by containing both a resource “email” and an action “send”. The disadvantage of this is that it makes sibling actions such as “Friends/dylan/getEmail” and “Friends/dylan/deleteEmail” ambigious. By providing a “/Friends/dylan/email” resource, it becomes a bit clearer:

    GET /Friends/dylan/email = get a list of emails sent to dylan
    POST /Friends/dylan/email = send a new email to dylan
    DELETE /Friends/dylan/email = delete an email from dylan
    etc…

    Alternatively, we could do something like:

    GET /Email/dylan = get a list of emails sent to dylan
    GET /Email/george/from/dylan = get a list of emails sent to george from dylan
    POST /Email/dylan = send an email to dylan
    DELETE /Email/3678 = delete email #3678

    That being said, I could see it being useful to provide RPC-like “shortcuts” on the client-side to reduce the number of RestStores a Dojo developer would need to interact with while maintaining the RESTfulness of the server-side API.