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.