Medryx Observations

July 24, 2008

JsonRestStore retires MedryxORM

Filed under: MedryxORM, dojo — Tags: , , , — maulin @ 5:46 am

When I was first designing the MedryxORM, I had a need for a data store that could perform advanced functions that no store in Dojo 1.1 had available. However with the addition and significant improvement of dojox.data.JsonRestStore, I am happy to say, that I think, after only one month, I can safely retire MedryxORM. This is a good thing. I always opt for off-the-shelf over custom solutions. And, you get the whole dojo communtiy supporting you when you use an official dojo.data store.

In the last month, I’ve worked with Kris Zyp — the Json guru at Dojo — and I really think the JsonRestStore has come a long way to solving many problems with complicated client side data management. First off, let me make this very clear — despite its name, JsonRestStore does not require you to use a RESTful data source or web service. Any web service will do — RPC, or home-grown. You just plug in your services into the JsonRestStore, and the store handles the rest. I actually think its name is its biggest flaw. Perhaps it should simply be JsonStore, so that more people would use it… but I digress.

So here are the features of JsonRestStore that have allowed my to say goodbye to the EntityManager and MedryxORM:

  1. Unique Object Identity: One of my requirements was that if the server sent down an entity, I wanted to be sure that however I accessed that entity, that I got the same entity. So whether I used store.getValue() or item.foo, or item.getFoo(), or item.getBar().getFoo(), I always want the same entity. In my design, there was therefore one store that handled all kinds of entities. This proved a bit clunky, because to access anything from the store via fetch, you had to provide an “entityClass”. JsonRestStore accomplishes this same goal without the clunkiness. With JsonRestStore you create one store for each entityClass. Behind the scenes, JsonRestStore uses dojox.rpc.JsonRest to be sure object identity is maintained. This means that you need a store for each entityClass, but that makes it crystal clear what you are querying for when you do a fetch. Overall, a better design IMHO.
  2. Conversion of JSON hashes to first-class JS objects, allowing for custom functions and business logic to be implemented within entities: If I have an entity, I want to, for example be able to create a toString() method on it, and each time an entity of that type created, it should inherit the toString(). The server should not worry about javascript methods, so sending it in the native JSON data is not the right solution for lots of reasons. My EntityManager used the “class metadata” to determine the class of information being retrieved from the server, and instantiating each object from the metadata, and then mixing in the server data into the object instance. JsonRestStore has a similar “class metadata” concept, but it is more standardized — and based on JsonSchema. If you provide a schema, and it has “prototype” property, then any methods/fields in the prototype are used when the entity is instantiated. Since standardization is a good thing, and since JsonSchema is emerging in the JS world as a standard, I think this is probably a better approach than the custom metadata that was created for MedryxORM — despite the fact that it was loosely based on EJB3/Hibernate.
  3. Built-in laziness: Use store.getValue() and you lazy load items for free. There is a little bit of learning on how to handle laziness on the server side, but its pretty straightforward (and I hope to write a post about it soon).
  4. Client side filtering/sorting: JsonRestStore allows you to cache query results on the client and then filter them and sort them seamlessly on the client. This safely retires my “named queries”, and is both more efficient and more extensible.

Honestly, the only thing missing from JsonRestStore is de-novo support for javabean style getters and setters that also give you free laziness, and support for dot-path notation in getValue(). I’ve added this with a simple additional class called BeanStore that extends JsonRestStore, and I have submitted BeanStore to Dojo to see if they think it is broadly applicable enough to include in dojox.data. We’ll see.

I have to say, the most irritating part of all of this is that I lose 2 weeks of work. But I can always look back at how intricately I now understand dojo.data and JsonRestStore and know the time was not completely wasted. In my next post, I hope to share with you my understanding of JsonRestStore and its use.

June 24, 2008

MedryxORM and dot-path notation

Filed under: MedryxORM, dojo, javascript — Tags: , , — maulin @ 7:05 pm

Another nifty feature of MedryxORM is dot-path support. Since this ORM is all about relationships beween domain objects, it only makes sense to give the you access to traversing your relationship paths. This is used in two places primarily: getValue and properties. Let me give you a couple examples.

Lets say you are making a table of patients. You want to display their name, room number, and insurance. The object model looks something like:

In javascript, you would represent this model as:

 
dojo.declare("model.BaseEntity", null, {
	id:new medryx.orm.Identifier()
});
 
dojo.declare("model.Patient", model.BaseEntity, {
	firstName:new medryx.model.Property({label:2}),
	lastName:new medryx.model.Property({label:1}),
	visit:new medryx.model.Association({associationClass:"model.Visit"})
 
	getDisplayName:function() {
		return this.getLastName() + ", " + this.getFirstName();
	}
 
	queries:[
		{name:"AllPatients", properties:["*", "visit.*"]}
	]
});
 
dojo.declare("model.Visit", model.BaseEntity, {
	room:new medryx.model.Property(),
	bed:new medryx.model.Property(),
	insurance:new medryx.model.Association({associationClass:"model.Insurance")
});
 
dojo.declare("model.Insurance", model.BaseEntity. {
	companyName:new medryx.model.Property(),
	groupNumber:new medryx.model.Property()
});
 
entityManager.registerClass("model.Patient", "Patient");
entityManager.registerClass("model.Visit", "Visit");
entityManager.registerClass("model.Insurance", "Insurance");

So after running all this code, you now have a managed model that you can query (assuming you have set up your perisistenceService) and display the results, lets say in a grid. So lets define a grid:

	var view = {
		cells:[
			[
				{name:"Name", field:"displayName"},
				{name:"Room", field:"visit.room"},
				{name:"Bed", field:"visit.bed"},
				{name:"Insurance", field:"visit.insurance.companyName"},
		]]
	};
	var grid = new dojox.grid.DataGrid({
		store:entityManager,
		query:{},
		queryOptions:{entityClass:"Patient", namedQuery:"AllPatients", properties:dojo.map(view.cells[0], function(column) {
			return column.field;
		})},
		structure:[view]
	}, dojo.byId("patientsGridNode");

And that is it. So what have we done here?

As you can see, I’ve declare 3 simple classes. Each has some simple properties, and for kicks, I’ve declared a simple getDisplayName() method on my Patient.

I’ve defined a named query that get all patients from the server, and for each, returns all properties of the Patient and the patient’s associated Visit. (Note that this means the server will return the id of the insurance association, but not any attributes of the insurance company (i.e. the companyName and the groupNumber are not returned.)

Next we declare a simple dojox.grid.DataGrid. (This uses the grid model from Dojo 1.2, where DojoData has been removed and essentially plunked into DataGrid itself.)

As you can see, the “field” name in the view attached to the grid’s stucture uses “dot-path” notation to get to the desired field to display. Since our query is returning Patient’s, we need to navigate from the patient to the desired display field. This fails gracefully, retuning null if any part of the path does not exist.

Now lets look at the queryOptions in the grid. First, see how there is no query? I don’t absolutely have to have one — which means by default I will show all patients. The queryOptions are used to tell the entityManager which query to run. We then send in a new list of properties for the entityManager. Since we know that we will certainly need all the displayed fields in our data set, we can simply map() the view into a list of properties that we would like to instruct the entityManager to fetch.

Here is where things get really nice. Without any work on your part, the entityManager will have only made one call to the server when all of this is done, and it will have all the data you need to display your grid.

Now lets say you wanted to create a “detail” view that allowed people to see the patient’s insurance groupNumber if they clicked on the insurance information. Simple!

dojo.connect(grid, "onRowClick", function(e) {
	var patient = grid.getItem(e.rowIndex);
	alert("GroupNumber: " + patient.getVisit().getInsurance().getGroupNumber());
});

Notice how there is no query here, or anything else for you to write! You just use your getter, and you get lazy initialization of the groupNumber for free!

MedryxORM Part 2 — Server Queries, Client Queries and Named Queries

Filed under: MedryxORM, dojo, javascript — Tags: , — maulin @ 5:28 am

In my last post, I talked about a system I have built for managing domain objects on the client side, and making communication with services that communicate with the server more opaque to the average developer.

In this post, I would like to further describe this system, which for the time being I have called MedryxORM. Aside from the “free” lazy loading and relationship management you get with this system, you get a simplified method of handling queries. While there is a wrapper that allows for usual “fetch” access, there are several advanced features you get with the ORM.

First, lets look at what is different from a usual fetch and a fetch done from the ORM. Since the ORM manages multiple domain object classes (Managed Classes), you must specify the “root” class of your query. This is analogous to the “FROM” statement in SQL. The results are always a list of Managed Entities of the root class of your query.

Using the native api, you run a query as follows:

entityManager.query("Patient", {query:{lastName:"Smi*"}, sort:["lastName", "firstName"]});

and using the dojo ReadApi you would do:

entityManagerStore.fetch({query:{dischargeDate:null, lastName:"Smi*"}, sort:["lastName", "firstName"], queryOptions:{entityClass:"Patient"});

(I’ve left out some of the details for brevity).

So far, nothing to earth-shattering. But the ORM gives you a couple of neat features that can simplify your queries. Queries can be either client-side or server-side. A client-side query is always a subquery of a server-side query that has already been executed. So you can do filtering and sorting of the server-side query without having to re-query your server or marshall more data back and forth. This is quite useful for most queries — up to a point. If you have an enormous number of records that make server-side paging necessary (i.e., your server query does not return the entire set of records), then client queries don’t work. But this is really an exception to many circumstances. In my world, I will usually have on the order of a couple hundred patients and a couple dozen physicians as my major queries. I can easily load all that data into the client on a single call. But your results may vary. :-)

So how do you set this up? In a further attempt to keep end-developer (i.e. page developer and perhaps widget-developer) usage as simple as possble, we have created a concept of “Registered Queries”. This is similar to PreparedStatements in Java or Stored Procedures in SQL. Or, perhaps even more closly, this resembles NamedQueries in Hibernate.

A Registered Query is simply a query that is created when the Entity Manager is first configured. Its just another field in the mapping of an entity. You register a query in one of two ways:

entityManager.registerQuery("Patient", "AllPatients", {query:{dischargeDate:null}, sort:["lastName", "firstName"]});

or you can include a “queries” property on your mapping:

dojo.declare("model.Patient", null, {
	firstName:null,
	lastName:null,
	//snip...
	queries:[
		{name:"AllPatients", parameters: {query:{dischargeDate:null}, sort:["lastName", "firstName"]}}
	]
});

Once you have done this, then executing your query becomes even easier:

entityManager.query("Patient", {query:{lastName:"Smi*"}}, "AllPatients");

This will first load all patients from the server with dischargeDate = null, and then filter them on the client to all those whose lastName starts with “Smi”. Suppose you then want to filter on a different name, or sort in a different order? Just run query() again, with your new parameters. This also enables easy client-side paging.

To use the ReadApi, you add a registeredQueryName parameter in either your keywordArgs or query args or query options, and it will map correctly to the EntityManager’s query method.

entityManager.fetch({query:{lastName:"Smi*"}, queryOptions:{entityClass:"Patient", root:"AllPatients"}});

Since the EntityManager is built on the concept of sparsely-populated objects that get their more complete representation lazily or eagerly if you know you will be using them, all query operations also support a “properties” queryOption. This is similar to the properties valuein loadDeferred described in the previous post. You provide a list of properties that you know you will be using. If this is a server-query, then this is passed to the server during the request. However, if this is a client query, and the properties passed are different than those in the server query, then the results of the query are iterated, and each object that is missing a required property has those properties eagerly fetched from the server. This is transparent to the end user. (In fact, if your sub-query contains fields that are not yet loaded from the server, then this loads those in one request prior to running the sub-query). This functionality all assumes that your server can handle a query where the identifier field is passed an array of id’s to load.

June 20, 2008

An Object-Relation-Mapper (ORM) for Javascript? Well, kind of…

Filed under: MedryxORM, dojo, javascript — Tags: , , , , , , , , — maulin @ 7:13 am

A while back, I sent a post to the Dojo discussion forum regarding lazy loading and relationships between entities. The very simple response by Alex:

stores don’t “do” references…

Aha! That was the part I hadn’t figured out. Ever try to open a phillips-head screw with a flat-head screwdriver? It felt a bit like that. Over time, I came up with many solutions, but never a comprehensive one that I could use in multiple projects. I came up with several design patterns, and perhaps some re-usable code, but it all still felt like a hack.

My path towards a more comprehensive solution to managing related entities came from the server world with a similar, but more lightweight relational model I started to explore with this post I made a little while back. Well, many all-nighters later, I think I have a 0.1 alpha version of a cool system that makes a relational, lazy enabled, dirty-checking javascript object model a piece of cake. And there is plenty of sugar to make it fun to use. Oh, and its dojo.data compatible.

(As an aside, I was excited when I first read about the JsonRestStore.. Indeed, it probably does a lot of this same work. So it may be an issue of style. I also don’t know a whole lot about JSON referencing, and my referencing model (to be documented) seemed a lot easier to translate on the server side. But as I said, I need to learn a lot more about it. I hope to start here.)

Having done quite a bit of work with Java in the past, I am big believer in Object-Relational-Mappers on the server side. I have used Hibernate most extensively, and have even dabbled with PHP-based ORM’s like Propel. I like the idea of creating a model, and letting the system figure out how to persist it.

In the client-side world of Javascript, obviously a full-fledged ORM would be overkill. But I think an analogy of an ORM on the server-side — which maps relational-database information to domain-objects — can be made. The only difference is that you are mapping your server communication to your domain objects. I’m not sure what to call this yet, but I am open to suggestions. For now, I’ll borrow “ORM”, though technically that is not what this is. Perhaps client-server object mapper? (CSOM?)

What is MedryxORM?

MedryxORM is an extension of the Dojo toolkit that allows for simple communication between your client-server services and your javascript domain model. It consists of an EntityManager that handles all the mapping of your simple javascript-objects to your javascript-services and can be used to query and persist your domain objects. Similar to Hibernate, this system aims to be as unobtrusive to your usual coding as possible, but provides access to several ways in which you can optimize the performance of your application once you have it working, making refactoring of your application for performance a much simpler process. There are several “shortcuts”, that make using the system easier, but may be a bit more intrusive on your usual coding practices, so they are optional.

Enough jargon. Lets do some coding, and then I’ll reveal how it all works. Since I am a physician, my domains almost always deal with medical records, so I’ll use that as an example. Lets say that we want to build an application that shows a table of patients. You can then drill-down on patient details like insurance information, clinical information, etc. You can add simple notes to the patient, and perhaps assign a responsible physician/team of physicians. You can then page the appropriate responsible physician.

So lets first describe out domain objects in javascript. Here is a model.Patient:

dojo.declare("model.Patient", null, {
  //id
  id:null,
 
  //simple properties
  fullName:null,
  initials:null,
  medicalRecordNumber:null,
  birthdate:null,
  gender:null,
 
  //associations
  visit:null
 
  toString:function() {
    return this.fullName || this.getFullName(); //I'll explain this later.    
  },
 
  getAge:function() {
  //snip... uses birthdate to calculate an age
  }
});

And here is a model.Visit:

dojo.declare("model.Visit", null, {
  //id
  id:null,
 
    //simple properties
  accountNumber:null,
  admissionDate:null,
  admissionTime:null,
  dischargeDate:null,
    dischargeTime:null,
  admittingDiagnosis:null,
    bed:null,
  room:null,
  ward:null,
 
  coverageNotes:null,
 
  //associations
  team:null,
 
  //collection of insurances
  insurances:null,
 
  getLengthOfStay:function() {
    //snip -- calculates number of days in the hospital based on the admission date.
  }
});

Well, you get the idea. Nothing very exciting here. But now let’s “register” these class with the EntityManager:

 
entityManager.registerClass({
  entityClassName:"model.Patient",
  alias:"Patient"
});
 
entityManager.registerClass({
  entityClassName:"model.Visit",
  alias:"Visit"
});

I’ll get into mapping and configuring the EntityManager in just a minute. But first lets jump ahead and see how you can use this:

 
var patient = new Patient(10);
alert( patient.getFullName() + " is " + 
      patient.getAge() + " years old!" );

Now, as long as their is a patient whose id = 10 on the server, then this will all *just work*. There is NO other necessary calls to xhr, or rpc, or REST, or anything else (Though those things happen behind the scenes, depending on how you configure your EntityManager.)

This might be even cooler:

 
var patient = new Patient(10);
 
alert ( "Uh-oh, " + patient.getFullName() + " has been here for " + 
    patient.getVisit().getLengthOfStay() + " days!" );

Again, there is no further coding required. And because you map your server data to your javascript objects, you can actually use methods declared on those objects as you normally would. This example would result in two calls to the server — one to get the patient, and the next to get the visit (though that would not actuall occur until the getLengthOfStay() method called getAdmissionDate()). But that is a default behavior. You could give the server some “hints” about how you will be using the Patient, and let it send you all the information you need on the first call. By default, the code above would behave use synchronous data access methods, but its a very simple tweak to convert this exact same code to friendly asynchronous calls. But all in due time…

Now, admittedly, this is a contrived use. How often will you be instantiating an object with an identifier and expecting it to connect to some backend? But a more realistic use-case would be to use the fetch/query methods of the EntityManager (using fetch if you want to use dojo.data style access, or query if you want to use my Deferred-style access). Once you make the query, your results are all instances of your object model, with all of the appropriate relationships initialized in all the appropriate ways.

var deferred = entityManager.query({query:{entityClass:"Patient", fullName:"Smi*"});
deferred.addCallback(function(patients) {
  dojo.forEach(patients, function(patient) {
 
    document.write ( patient.getFullName() + " is " 
        + patient.getAge() + " years old!" );
 
  });
})

And in terms of persistence, you can choose from a number of different options. Some people prefer the DAO design pattern, others prefer ActiveRecord. I think this (sort of) gives you both. Or you can choose to completely forget about persistence, and let it all happen automagically (flushing on unload).

 
var patient = new Patient(10);
patient.setMedicalRecordNumber("123456");
patient.save();

or…

 
var patient = new Patient(10);
patient.setMedicalRecordNumber("123456");
 
entityManager.saveEntity(patient);

or…

var patient = new Patient(10);
patient.setMedicalRecordNumber("123456");
 
entityManager.save(); //flush all dirty records to the server

You are guaranteed to have only a single instance representing each persistent entity. So, this works fine (not that it has much purpose):

var patient = new Patient(10);
 
var visit = patient.getVisit();
 
var deferred = entityManager.query({query:{entityClass:"Visit", patient:10}});
deferred.addCallback(function(results) {
    assertTrue ( visit === results[0] ) ;
});
 
//or...
 
var visitId = visit.getId();
var newVisit = new Visit(visitId);
 
assertTrue ( visit === newVisit );

I’ll get into the details of optimization, caching, and other goodies later. Now that you’ve seen a little of the magic, lets dive into how its all configured.

At the core of the EntityManager is the class metadata — or “mapping”, inspired by Hibernate. You can specify your mapping in two ways — as a JSON object or “natively”. Your choice metadata is essentially stylistic, and is equivalent to the Java argument of Annotations based configuration versus XML based configuration.

To start with, lets define some terms, to make writing this a bit easier:

  • Managed Class: any class that is registered in the EntityManager
  • Managed Entity: an instance of a managed class
  • Managed Property: any property on your Managed Class that is managed by the EntityManager (Having unmanged properties in your ManagedClass is certainly allowed, but keep in mind that the EntityManager will totally ignore unmanaged properties.)

There are several types of ManagedProperty’s already built, and you are free to create your own to implement novel behavior.

  • medryx.orm.Identifier: in this implementation, only numeric identifiers are allowed, and there must be one and only one Identifier per Managed Class
  • medryx.orm.Property: used for any property of a Managed Class that is not an association — usually scalar values like numbers, strings, Dates, etc.
  • medryx.orm.Association: any association to another Managed Entity. Note that this must be an association to a Managed class, otherwise you use Property above
  • medryx.orm.Collection: a collection of Managed Entities. Again, this is inteded to be used for associations to elements of a Managed class only.

The details of each of the ManagedProperty types will be more fully discussed a bit later. Let look at a native mapping for a Patient. You could either create a subclass of patient, or just change your Patient class itself, which is the approach I will show here for simplicity.

dojo.declare("model.Patient", null, {
  //id
  id:new medryx.orm.Identifier(),
 
  //simple properties
  fullName:new medryx.orm.Property(),
  initials:new medryx.orm.Property(),
  medicalRecordNumber:new medryx.orm.Property({readOnly:false}), //this is a mutable field (not in real life, but for this example it is)
  birthdate:new medryx.orm.Property({converter:medryx.orm.converters.DateConverter}),
  gender:new medryx.orm.Property(),
 
  //associations
  visit:new medryx.orm.Association({associationClass:"model.Visit"});
 
  toString:function() {
    return this.fullName || this.getFullName(); //I'll explain this later.    
  },
 
  getAge:function() {
  //snip... uses birthdate to calculate an age
  }
});

and Visit is altered as follows:

dojo.declare("model.Visit", null, {
  //id
  id:null,
 
    //simple properties
  accountNumber:new medryx.orm.Property(),
  admissionDate:new medryx.orm.Property(),
  admissionTime:new medryx.orm.Property(),
  dischargeDate:new medryx.orm.Property(),
    dischargeTime:new medryx.orm.Property(),
  admittingDiagnosis:new medryx.orm.Property(),
    bed:new medryx.orm.Property(),
  room:new medryx.orm.Property(),
  ward:new medryx.orm.Property(),
 
  coverageNotes:new medryx.orm.Property({readOnly:false}),
 
  //associations
  team:new medryx.orm.Association({associationClass:model.Team}),
 
  //collection of insurances
  insurances:new medryx.orm.Collection({associationClass:model.VisitInsurance}),
 
  getLengthOfStay:function() {
    //snip -- calculates number of days in the hospital based on the admission date.
  },
 
  getRoomNumber:function() {
    return this.getWard() + "-" + this.getRoom() + "-" + this.getBed(); //always use getters to allow lazy initialization if needed
  }
});

Okay, so you are almost done with your configuration. (Obviously I’ve left model.Team and model.VisitInsurance out for brevity, but they are just as simple to “annotate”).

The next step is to plug in your PersistenceService. MedryxORM defines a PersistenceService interface that looks like:

dojo.declare("medryx.orm.PersistenceService", medryx.AbstractBase, {
 
    /**
     * the deferred calls back with an object:
     * {
     *    total: total number of records available for this query, or -1 if unknown
     *    items:array of items
     * }
     * returns a deferred (always)
     * 
     * @param {Function | String} entityClass
     * @param {Object} kwArgs (see ReadApi... still need to document queryOptions)
     * @param {Boolean} sync
     */
    $fetch:null,
 
    /**
     * returns a deferred (always) that callsback with 
     * the id if successful
     * 
     * if no id is null or < 0, then this is a put, otherwise it is
     * a post
     * 
     * @param {Function | String} entityClass, 
     * @param {Integer} id, 
     * @param {Object} propertiesToPersist
     */
    $save:null,
 
    /**
         * similar to WriteApi deleteItem
         * 
         * returns a deferred (always)
         * 
         * @param {Function | String} entityClass, 
     * @param {Integer} id
         */
        $deleteItem:null,
 
    /**
     * given an entiyClass, id, and properties, full load an instance 
     * that guarantees that each property in properties is initialized
     * (or if no properties are specified, then some contract
     * between client and server about "default" loading has
     * been fulfilled)
 
     * @param {Function | String} entityClass
     * @param {Integer} id
     * @param {Array of Strings} properties -- this is a list of properties that the 
     * server GUARANTEES will be present in the returned entity. The server *MAY* and 
     * frequently *WILL* return more than just these properties, and the EntityManager 
     * will handle that case appropriately and consume all the information the server sends
     * each item in the array may be either a property name, a dot-notated association property
     * or a *
     * for example: ["*", "visit.accountNumber", "visit.team.*"] would load all the properties
     * of the entityClass with id, the visit association with the account number loaded, and 
     * the team of the visit with ALL of its properties loaded. 
     * @param {Boolean} sync
     */
    $load:null
});

We also provide an RpcPersistenceService that gets rid of sync handling for you. You could easily create an SMD to your server that meets the requirements of this interface.

You provide a default PersistenceService to the EntityManager. If you do not specify a specific PersistenceService for a given Managed Class, then the default PersistenceService is used. However, it may be easier for you to implement a seperate SMD for each Managed Class, so you can supply a seperate PersistenceService for a particular class (or for every class). My server can easily interpret the “entityClass” and determine what to send back to the client, but if you wanted to make your server more generic, you may choose to ignore the entityClass on the server and let that mapping occur on the client. It would be extremely easily to build a REST PersistenceService (and I will probably do that when the need arises) into which you can adapt a Rest service to fit the PersistenceService API.

Let make this a bit less abstract. As I said, my server is pretty smart, so I it knows the right thing to do with entityClass. So I have a single PersistenceService that I specify when I “startup” my EntityManager (i.e. instantiate it).

ormPersistenceServiceSMD = {
  serviceType:"JSON-RPC",
  serviceUrl:"mySmartServer.php",
  methods: [
    {
      name: "load"
      parameters:[
        {name:"entityClass", type:"string"}
        {name:"id", type:"integer"}
        {name:"properties", type:"array"}
      ]
    },
    //snip... you get the idea
  ]
}

Then I can plug that into my EntityManager with a couple of wrappers:

var rpcPersistenceService = new medryx.orm.RpcPersistenceService(new dojo.rpc.JsonService(ormPersistenceServiceSMD));
var entityManager = new medryx.orm.EntityManager(rpcPersistenceService);
 
entityManager.registerClass({entityClass:"model.Patient", alias:"Patient"});
entityManager.registerClass({entityClass:"model.Visit", alias:"Visit"});
 
//and now everything above works!
 
var patient = new Patient(10);
patient.getVisit().setCoverageNotes("Some new coverage notes");
 
patient.save();

Woo-hoo! I obviously have a LOT of work to do in terms of documentation. But this is a start! Please, tell me what you think!!! I’ve posted the source on Google code. Maybe if I get lucky, some Dojo folks will take an interest, and we could be incubated as a dojox project! One can dare to have dreams, right?!

Powered by WordPress