A Mock RPC Service
I’m tired of server side frameworks. There, I’ve said it. Take your pick — Struts, Tapestry, SpringMVC, php, ASP, ASP.NET. No matter which you choose, I’ve always felt like server side scripting to create web pages and flow in web applications seemed like an obvious violation of seperation of concerns.
But aside from the conceptual argument, I’ve always felt like building pages with these languages was more time consuming than it should be. Edit your page, make sure the server is running, make sure the database is running, make sure you are hitting the database in a known state… blah blah blah. It can take minutes to see the smallest of changes.
As I have grown more and more enamored by extreme programming and unit testing, creating a very rapid cycle of test, code, and test again was of utmost importance. And its just hard to do with heavy weight frameworks.
Now don’t get me wrong, obviously you need server side code. But I think server side code is for managing the model and the business logic. Leave the UI to the client!
So, if you are reading this, I am probably preaching to the choir. But I had to get that off my chest. Now, for the real business of this post. With RPC, we have the ability to use the server to simply expose services to the front end. And with dojo rpc, it is very easy to connect to these services in a very agnostic manner.
Okay, so now you can ditch the heavyweight frameworks for doing things they are not very good at, and use them for things they are good at. So I’ve addressed the SoC issue. (There’s loads of information about using JSON RPC out there, so I won’t repeat it all here.) But what about the extreme programming issue? We want to be able to write client side unit tests and develop against them. (I’ll be posting about my use of Dojo DOH for unit testing sometime soon.) This means not having to wait for the server, or worry about the server in any way.
Well, if you write a web application whose only connection to the server is via RPC, couldn’t you just change the type of RPC Service you are using, and create a “Mock” RPC that appears to talk to a server, but doesn’t? Couldn’t it then always return the data you want, in a known state, without ever having to make any changes on the server? And wouldn’t this then mean that you could test this application in a completely disconnected state? YES!
So here’s how I am doing it.
First, we create a data access layer for all communication with the server. This data access layer does little more than enforce some client side business logic, and delegate to an RpcService. For example:
dojo.declare("medryx.dao.AbstractDao", medryx.AbstractBase, {
_service:null,
constructor:function() {
this._service = new medryx.config.rpcService(medryx.rpc.TeamsService);
},
getById:function(id) {
return this._service.getById(id);
}
});
(I will address handling all the Deferred objects in a future post).
So this is a reasonable start. I could probably just change my SMD for the service to point to some static files that have a static JSON response object, and just ignore the parameters. For example:
dojo.provide("medryx.rpc.TeamsService");
medryx.rpc.TeamsService = {
serviceType:"JSON",
serviceUrl:"someStaticTeamFile.json",
methods:[
{
name: "getById",
parameters: [{
name: "id",
type: "integer"
}
]
}
“someStaticTeamFile.json” could be simple text file with the json of a simple response to my service request. However, f I get the same response when the id=1, id=2, or id=null, its not going to feel like the app is really working until I connect to a real server. And what should I do if I want to add more methods to the service?
The SMD is really what I see as a CONTRACT with the server-developers. You promise to adhere to it, and they promise to adhere to it, and beyond that you never need to know how they are working, or what technology they are using. And vice-versa. This is especially true I think for the “methods” property of the SMD. So I would like to avoid changing that.
For a while, I thought my only way around this was to create a “mock” service, using server side code that could interpret the post request and return a different static file for each request, depending on the parameters. That too would have been better than hitting a live database, but it still was slower than I wanted.
And then it hit me. What if we mock the JsonService altogether? This mocked service is a static JSON file that contains all the functions defined in the SMD, and accepts the same parameters described in the SMD. Each function returns the object to the service. So lets look at this code, and you’ll get an idea of what I am talking about:
dojo.provide("medryx.rpc.MockJsonService");
dojo.require("dojo.rpc.JsonService");
dojo.declare("medryx.rpc.MockJsonService", [dojo.rpc.JsonService], {
/**
* this overwrites the bind in the original service.
* it loads the page in the service url first.
* the page should have be javascript object
* that has the method that accepts the given
* params and returns the result object
* @param {Object} method
* @param {Object} parameters
* @param {Object} deferredRequestHandler
* @param {Object} url
*/
bind: function(method, parameters, deferredRequestHandler, url){
var def = dojo.xhrPost({
url: url||this.serviceUrl,
timeout: this.timeout,
handleAs: "json-comment-optional"
});
def.addCallbacks(this.resultCallback(deferredRequestHandler, method, parameters), this.errorCallback(deferredRequestHandler));
},
/**
* this will apply the method and params of the request
* to the received object, and then pass the results
* to the callback of the deferredRequestHandler
*
* @param {Object} deferredRequestHandler
*/
resultCallback:function(deferredRequestHandler, method, parameters) {
var tf = dojo.hitch(this,
function(mock){
var obj;
if (dojo.isArray(mock)) {
mock = mock[0];
}
if (!mock[method]) {
obj = {id:'unknown', error:{code:-1, message:"Method not implemented."}};
} else {
obj = mock[method](parameters);
}
if(obj.error!=null){
var err;
if(typeof obj.error == 'object'){
err = new Error(obj.error.message);
err.code = obj.error.code;
err.error = obj.error.error;
} else {
err = new Error(obj.error);
err.id = obj.id;
err.errorObject = obj;
}
deferredRequestHandler.errback(err);
} else{
deferredRequestHandler.callback(this.parseResults(obj));
}
});
return tf;
}
});
So now, I alter my SMD just a bit:
dojo.provide("medryx.rpc.TeamsService");
medryx.rpc.TeamsService = {
serviceType:"JSON",
serviceUrl:"MockTeamService.js", //note the change
methods:[
{
name: "getById",
parameters: [{
name: "id",
type: "integer"
}]
}
]
}
and then create MockTeamService.js as:
[{
getAll: function(){
var ret = {};
ret.result = this.teams;
ret.id = 1;
return ret;
},
getById: function(id) {
var ret = {};
ret.id = id;
for(var i in this.teams) {
if (this.teams[i].id == id) {
ret.result = this.teams[i];
break;
}
}
return ret;
},
teams: [{
id: 18,
name: "Adult Triage",
color: "#C5C3C3",
abbreviation: "T",
category: "Triage Teams",
categorySortIndex: "0",
pagers: [{
type: "Attending",
id: "4900",
number: "216-6338 x4900"
}]
}
//... snip... add as many teams as you want here
]
}]
The final piece is making some generic configurations that can be swapped in and out for dev versus prod:
dojo.provide("medryx.config.mock");
dojo.require("medryx.rpc.MockJsonService");
(function() {
var config = medryx.config;
config.rpcService = medryx.rpc.MockJsonService;
config.rpc = {
userService: config.libUrl + "/mock/MockUserService.js",
teamService: config.libUrl + "/mock/MockTeamService.js"
}
})();
and last but not least, I change the “hardwired” information to these config variables:
In TeamsDao change
this._service = dojo.rpc.JsonService()
to
this._service= new medryx.config.rpcService()
and change the serviceUrl of the SMD to medryx.config.teamService from its previous hardcoded “MockTeamService.js”.
So what have we accomplished? Now that all the glue code has been written, each time we want to create or add to a new service, we simply add to or create more “Mock” services that implement the SMD contract. Then we just use them as we would a dynamically loaded server side service. And when you want to switch to the server-side service, simply change you config file (or better yet, swap in the new one).
Questions? Comments? Let me know!
[...] Services: I talk about my RPC layer extensively in this post. But briefly, this is basically a set of SMD files that are used to create services that [...]
Pingback by Medryx Observations » Blog Archive » Configuration, Directories, and Builds for Javascript Web Applications (Part 2) — June 8, 2008 @ 12:36 am
how we can test the methods involve server communication e.g if we use xhrGET() in our method for talkin to server then how we can test these using D.O.H??
Comment by Ricky — February 11, 2009 @ 8:15 am
Отлично,несогласен с предыдущими блоггерами
^..^ Bye
Comment by Flueklipsuisa — June 20, 2009 @ 10:17 am