Medryx Observations

June 8, 2008

Configuration, Directories, and Builds for Javascript Web Applications (Part 2)

Filed under: dojo, javascript, oop — Tags: , , — maulin @ 12:30 am

In my first installment in this series, I described an overall file structure and configuration scheme that made it easy to develop and deploy javascript web applications. In this installment, I will describe the structure of my javascript library, how it lends itself to a loosely coupled architecture that is easily tested, and how it integrates with Dojo’s DOH unit testing system.

First lets look at the file structure:

As you can see, I have created my own package of javascript files called medryx, which I register using djConfig as I illustrated in part 1. Lets dive into some of the details of the medryx package:

  1. The first folder is called config. Anything that can change from deployment-todeployment is abstracted into various config files, that can be swapped out fairly easily. The “master” config is located in medryx/config.js. This file simply uses dojo.require() to build up the list of configuration “constants” that are available to the deployment. Other files need only add dojo.require(”medryx.config”) to get the current values of these constants.

    dojo.provide("medryx.config");
    
    dojo.require("medryx.config.base");
    dojo.require("medryx.config.rpc");
    

    A second benefit this method of declaring constants is that you can use a different set of constants for your tests, a second set for development and usability, and a third set for production. For example, my unit tests first call dojo.require(”medryx.config”), but then they explicity call dojo.require(”medryx.config.mock”), which effectively replaces the rpc related constants with the mock versions.

    Here is an example of what one of the “component” config files looks like:

    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"
        }   
    
    })();
    

    There is a nice trick here for those who haven’t seen this kind of notation before. Notice how we define an anonymous function, and immediately call it? This allows us to avoid having to declare any global variables, or to use an extremely verbose name for every constant. We declare a local variable called “config” that points to medryx.config. Then, all the “config.*” constants, are actually “medryx.config.*”. A second benefit is that when shrinking this code, “config” will be replaced with a shorter variable, wherease if you used the full “medryx.config” no replacement could occur. (Only local variables are replaced during shrinking.)

  2. The next folder to pay attention to is the “exceptions” folder. I always use my own exceptions (that extend Error) when throwing an error! Its much easier to debug, than searching for the cause of “Error” or “TypeError”. Especially when all this code is eval’d by javascript. (Yes, I know there are workarounds for the eval problem and lack of line numbers, but believe me, custom exceptions can really save you! And they are a cinch to create. All they really consist of is a declare statement and a custom constructor:

    dojo.provide("medryx.exceptions.UnimplementedAbstractMethodException");
    
    dojo.declare("medryx.exceptions.UnimplementedAbstractMethodException", Error, {
       constructor:function(unimplementedMethod) {
          this.name = "UnimplementedAbstractMethodException: " + unimplementedMethod;
          this.message = "Subclasses must implement: " + unimplementedMethod;
       }
    });
    

  3. Finally lets exam the “layers” of my javascript. As you can see I have several packages — dao, model, rpc, and widgets. While there is some dependence between the packages, most of the packages can be tested in isolation by creating mock version of their external dependencies. So lets look more closely at the layers:

    • Model: This layer contains the entities that the application will interact with. They are typically similar to (though not identical to) their server side counterpart (or even more roughly, they correspond each to a table in a database). There can be relational dependence in the model (i.e. a Person can belong to a Team).

      Now I’ve had people tell me this is overkill for javascript. But I think that its not much more effort to build, and makes everything more extensible and more testable. For example, when first building this application, we didn’t ever really care about who the people on a team were, other than displaying their names. So a naive implementation of this might be:

      dojo.declare("medryx.model.Team", null, {
         teamName:null,
         attendingName:null,
         upperLevelName:null,
         internName:null
      });
      

      But then someone comes along and asks us to create a team that can have two interns on it. Hmmm. Well, we could change internName to:

         //...
         internNames:[]
         //...
      

      and then enter an array of intern names. But then you’ll have to remember to change every call to internName and figure out what to do with it. And then what if they tell you some teams have two attendings? Now consider if we used a more fine grained object model right from the start.

      dojo.declare("medryx.model.Person", null, {
         name:null
         role:null
      });
      dojo.declare("medryx.model.Team", null, {
         teamName:null,
         members:[] , // an array of People
         getTeamMemberByRole:function(role) {
            //to-do
         }
      });
      

      Creating a “Person” class was really simple. There are just a couple lines of code. But now consider first how easy it is to extend who can be on a team. And consider if a requirement to display the pager number for each team member comes up. Just add the field to Person, and you are done!

      The most important feature of a model entiy is its constructor, since this is how we will instantiate and populate the model in the next layer — the Dao.

    • Data Access Objects: The Dao layer is “glue code” the connects our raw server-side services to our model. It provides the application with a common mechanism to “query” and obtain instances of entities that it needs, or lists of them.

      Consider again our “Team” example. The TeamsDao provides a getAll() method that will allow for custom filtering and sorting of the list of teams, and a getById() method that will get a team with a given id.

      It is the Dao’s responsibility to query the server and to cache the Team entities for use by the client. If the team had any editable properties, it would be the Dao that would save the object back to the server via its rpc. Obviously some calls into the Dao can be synchronous, but many are not. As a rule, I always return Deferred objects from Dao methods, so my client code does not need to know when to use synchronous access and when to use asynchronous access.

      In previous implementations of this pattern, I put direct xhr requests into the dao. But this “hard-coding” of my interaction with the server proved difficult to unit test, and made my web application much more at the mercy of which server side technology we were using to get the data. Adding in the next layer — the rpc — abstracts the interaction with the server to yet another layer, making this layer easier to test. In addition, we don’t have to create mocks of any of our Dao objects. This means that as we test parts of our application that are dependent upon the Dao, we can rest assured that any bugs we find are not because of a faulty mock dao.

    • Business Objects: The Dao layer is used almost exclusively by the next layer — the service layer, sometimes called the business layer. I use business because the word service is overloaded, and could mean RpcService or Service layer. In addition to interacting with the Dao, the service layer may call upon business methods of the rpc layer.

      This is where the “business” of the application is done — enforcing security, validating data, manipulating data, deciding whether an object should be saved, etc. This is the layer that most frequently communicates with the UI layer above.

    • RPC 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 interact with the server in some way. Note that this is the ONLY way that this web application communicates with the server!

      As I said in my previous post, I look at the SMD as a “contract” with your server. As long as you both stick to your contract the rest of how you work should be completely independent of one another. There is always some negotiation that has to occur when defining the terms of the contract, but once it is established, it makes development easier for both sides, a known, reliable, and testable deliverable for both sides, and allows for parallell development with neither side blocking the other’s progress in any way.

      If the SMD definitions are the contract, then the unit tests in this layer are its enforcement. By creating a solid set of unit testing of the SMD contract, you can essentially validate (or invalidate) any server implementation of the contract. Server guys want to move from windows with M$-SQL and ASP to J2EE and MySql? Go fo it! Just make sure these tests pass when you are done!

      A small note about how I declare my SMD’s. I declare each SMD as a class (as opposed to an SMD file). This lets me use dojo.require to get the definition. Its probably just a style choice, but it seems easier to me.

    • Widgets: This is the layer that everyone seems to be focused on. But really, as long as the other layers are all working well, then this layer becomes very straightforward to implement. Need to validate some fields in a form widget? Don’t! Just delegate that work to the business layer. You know the business layer works, because it has been thoroughly unit tested.

      I make just about every piece of my web applications into a widget. I use compound widgets if needed to group smaller ones together. But I never want to see any real amount of javascript in my html page. Just markup!

      This is definitely the hardest layer to test. But, its still possible. The piece that becomes more complicated is testing the “look and feel”. This is probably in the realm of usability and acceptance testing realm than true unit testing. Tools that you could use to help could be something like Selenium. More on that in my unit-testing post.

In this article, I’ve covered the most important elements of a layered approach to javascript web applications. Creating layers allows multiple people to simultaneously develop portions of the application without impeding the progress of others, by making simple stubs or mocks of the behavior that will be expected by the surrounding layers. It allows for easier unit testing, and ultimately makes it easier to fulfill ever-changing specifications and requirements. In my next post, we’ll dive into Dojo’s DOH and talk about how we will implement this “easy” unit testing.

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress