Shifting from actions to data model

In traditional development when describing how the user interacts with the system we often describe it in terms of actions.

the user types in the search field, selects some options and clicks on the button to get search results

This requirement becomes the following piece of code

$("search_button").click(function () {
  var searchString = $("search_field").val();
  $("[type=checkbox]").each(function () {
    // business logic to handle multiple, maybe conflicting, options
  });
});

This approach work well in some cases, but it doesn’t scale when the business logic gets complex and the data behind the application starts to grow.
The biggest problem is that business logic is spread across the application in multiple callbacks.

With this article we’ll see an example of reasonable size where the application is not described by user actions but only by it’s data model.
A key prerequisite is UI data binding a design pattern in which UI elements (widgets) are bound to the application model.

The application is used to assign counters inside the food court of a shopping mall. These shops might have constraints that prevent them from being placed one next to the other.
As we’re focusing on the data model let’s start writing the data model configuration bean
Thinking in OOP we have 3 concepts, the food court, a counter and a slot in the mall’s court. These three concepts are described by ariadoc.guides.mall.Beans.

Portion of bean definition, it highlight the fact that our data model can be a complex structure with circular references since a slot has references to neighbouring slots.

"Slot" : {
  $type : "json:Object",
  $description : "A single slot",
  $properties : {
    "usable" : {
      $type : "json:Boolean",
      $description : "Whether this slot can be used or not",
      $default : true
    },
    "counter" : {
      $type : "Counter",
      $description : "Counter assigned to this slot"
    },
    "neighbours" : {
      $type : "json:Array",
      $description : "List of neighbouring slots",
      $contentType : {
        $type : "Slot",
        $description : "Neighbouring slots"
      },
      $default : []
    },
    "position" : {
      $type : "json:Integer",
      $description : "Position in the court"
    }
  }
}

As a second step we design the UI, it has a Module Controller that delegates the data model creation to a Data class. This class will be the only responsible of data manipulation according to our business logic.
For the sake of the example we generate some random data.
We can see the whole code in this commit

The key part is the function getViewData inside ariadoc.guides.mall.Data. This method adds a single recursive listener on the whole data model to be notified of any change in the model.

getViewData : function () {
  var data = this.internalData;

  // Recursive listener on the datamodel
  aria.utils.Json.addListener(data, null, {
    fn : this.stateChange,
    scope : this
  }, false, true);

  return data;
}

Let’s now define our businees logic.
Having a recursive listener on the whole data model, ariadoc.guides.mall.Data.stateChange is called anytime an user action triggers a change in the data model.

The rules we impose are:

  • Total number of people in a shop is always the sum of employees and owners
  • A closed counter cannot have neighbours
  • A pizza shop cannot have a neighbour with more than 10 people

When we allocate a counter to a slot, we assume that the assignment is valid, so any neighbour that doesn’t respect one of the rules is removed from the court and the newly assigned slot is marked as locked.

This logic is implemented in this commit

With this approach we achieved to

  • separate the presentation layer from business logic that is sole responsibility of a single class.
  • unit testable logic. Every user action results in a data model change triggering stateChage. In our unit test we can emulate any action just by passing the correct parameters to this method without having to simulate or fire browser events.

This approach is also a starting point for more complex business logic. stateChage can be converted into a simple dispatcher distributing the data change handling to several independent dependency classes.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>