»
March 04, 2010
»

Models Library Current Status

This post is about Models — the GWT library which hopefully will greatly help to build client-server communication, model properties-to-view binding and in application architecture in general by simplifying all boilderplate code needed to manage downloaded object graphs, presenting them to users and allowing users to act on them. What I have currently is initial implementation of KVC, KVO, Bindings, ForwardingModel, ListController, ObjectController and DataSource (with DataStore). Nothing are release ready. Controllers and DataSource are so badly implemented I’m a bit ashamed to show them.

The main purpose of post (aside from loud-thinking) is to try to get some feedback before API is locked. So if you have some comments, critique or just want to say hi, feel free to mail me to ampatspell@gmail.com.

Lets start from lowest level API — Key-Value Coding and Key-Value Observing (the terms come from Foundation framework in Cocoa). Basically this is easy to use property change observation and property value setting by name support for classes. In whole Model library context the classes are model objects either fetched from DataSource (server) or created locally.

Observing

The basic model property change observation looks like this:

User user = GWT.create(User.class);

ObserverRegistration registration = asReflection(user).observe("address.country", 
  String.class, 
  new Observer<String>() {
    public void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, 
                              String oldValue, 
                              String newValue) {
      // called after value change
    }
  });

registration.remove();

ObservableReflection reflection = ((Observable) entry).getReflection();

Observer<T> callback is called when either of following are called:

  • user.setAddress(newAddress);
  • user.getAddress().setCountry("New country");

Callback is not called with initial value. See bindings why this is not needed.

Operation can be one of SET, INSERT, REMOVE, CLEAR and so on (SET for properties, others for List operations).

IndexSet is Range like class for defining List operation indexes (list.addAll(int index, E element) notifies observers about INSERT operation and IndexSet with single index).

asReflection(Object) is statically imported helper method what just casts user instance to Observable.

Value setting by keypath

The second low-level building block is Key-Value Coding that allows to set Observable object properties not only by using setters directly but also by key path. “address.country” in previous code snippet is key path.

Lets look at small example using the same user entity:

asReflection(user).setValueForKeyPath("address.country", "Zeebaburg");

this is the same as calling user.getAddress().setCountry("Zeebaburg") (with null checking) but can be used to dynamically set values for arbitrary keys (properties).

GWT.create

Of course for both of those things to work GWT generator is used. This is the reason why GWT.create() is required and so this code will only work in GWT runtime and unittests run with GWTTestCase. Currently I don’t have Java reflection and cglib based implementations for Observable but it can be created to support standalone JUnit unittests. For now I’m ignoring this while I’m not finished with API design.

Bindings

Next low-level API is Bindings. This feature allows to “bind” two targets (BindingTarget) so if one of them is changed, the other is notified with new optionally converted value. Also when binding is created initial value is set from “left” target to “right”.

Currently I have three notable binding targets:

  • ObservableTarget — for Observable models
  • HasValueTarget<T> — for view bindings
  • HasTextTarget — for view bindings

Others are easy to write.

As you might guessed this main purpose is to bind model properties to GWT Widgets. But actually it turns out that binding ObservableTarget to other ObservableTarget is as important as Widget targeted ones.

Lets look at two low-level Bindings usage examples:

Observable to Observable:

bindings.bind(
  observable(list, "selection", BlogEntry.class), 
  observable(selected, "content", BlogEntry.class)
);

Observable to HasValue

bindings.bind(
  observable(model, "key", String.class), 
  hasText(view.key())
);

// transformer is required because binding target types differ
// otherwide the code won't compile
bindings.bind(
  observable(model, "status", Integer.class), 
  hasText(view.status()), 
  new StatusToStringArrayTransformer()
);

"bindings" variable is instance of Bindings which allows to create and bind new bindings (and store them in DeferredCollection which allows concurrent modifications by deferring adds, removes, clear after iterator has finished) and unbind them with one bindings.clear() call.

The binding itself is created like this:

Binding<L, R> binding = new Binding<L, R>(left, right, transformer);
binding.bind();
// ...
binding.unbind();

Bindings and Binding by themselselves doesn’t require GWT.create at least now but ObservableTarget needs Observable of course.

Forwarding

The last low-level tool is Forwarding which is Observable proxy object that represents other Observable object while trying to be invisible for its clients. This is sometimes needed for current DataSource API implementation and I think this won’t change.

To create Model (Observable) and it’s Forwarding instance we start with 2 classes. GWT.create doesn’t take parameters so Forwarding must be additional class (or better yet interface. But for now it is class.)

public class BlogEntry implements IsModel {
  
  String title;
  
  public String getTitle() {
    return title;
  }
  
  public void setTitle(String title) {
    this.title = title;
  }
  
}

public class BlogEntryForwarding extends BlogEntry implements IsModel, IsForwarding {
}

Now we can GWT.create them and use:

BlogEntry model = GWT.create(BlogEntry.class);

BlogEntry proxy = GWT.create(BlogEntryForwarding.class);
// asForwarding is just "safe" cast to Forwarding w/ assert

Forwarding proxyForwarding = Forwardings.asForwarding(proxy);
proxyForwarding.setForwardingTarget(model);

// the same for model observers (except setForwardingTarget change)
Observables.asReflection(proxy).observe("title", String.class, new Observer<String>(){
  void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, 
                     T oldValue, T newValue) {
                    
    // 1st property change
    assertEquals(null,       oldValue);
    assertEquals("Title #1", newValue);
    
    // 2nd property change
    assertEquals("Title #1", oldValue);    
    assertEquals("Title #2", newValue);
    
    // result of setForwardingTarget. Old model is _not_ notified about this change obviosly
    assertEquals("Title #2", oldValue);
    assertEquals("New Title", newValue);
  }
});

// model property change

model.setTitle("Title #1");
assertEquals("Title #1", proxy.getTitle());

// proxy property change

proxy.setTitle("Title #2");
assertEquals("Title #2", model.getTitle());

// proxy target change

BlogEntry newModel = GWT.create(BlogEntry.class);
newModel.setTitle("New Title");

proxyForwarding.setForwardingTarget(newModel);

And that’s it. We can move on to Model Controllers.

Model Controllers

Before we talk about Model controllers I need to draw a distinction between Observable and Model. First Model is Observable, second it has status represented as int bitfield (yes, not EnumSet or whatever. This I’m almost certain should be called premature optimization). Status is observable property.

Ok, finally model controllers.

Now API has 2 (really badly implemented) ModelControllers:

  • ListController
  • ObjectController

Both should be created using GWT.create to enable observation support for them.

ListController has “content” and “selection” (only single selection currently). Both properties are observable, both publicly available. When ListController content changes, selection is set to null.

ObjectController has observable “content” property.

// bind ListController.selection to ObjectController.content
bindings.bind(observable(list, "selection", BlogEntry.class), observable(selected, "content", BlogEntry.class));

// observe ObjectController.content changes
asReflection(selected).observe("content", BlogEntry.class, new Observer<BlogEntry>() {
  public void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, 
                            BlogEntry oldValue, BlogEntry newValue) {

    if (newValue != null)
      ds.fetchDetails(newValue);
  }
});

Also both controllers allows Presenters (or whatever else) to add itself as Target. List controller target has callbacks to:

  • clear – called when list is cleared (maybe speeds up DOM modifications, haven’t checked)
  • create – called when Widget for new list controller entry should be created. It is passed to bind afterwards
  • bind – called when Widget needs to be rebound to new entry (only bound, no rebinding yet)
  • remove, insert and other things not exposed yet

Here is one ListController to UI Target implementation (declared in Presenter):

blogEntries.addTarget(new ListControllerTarget<BlogEntry, BlogEntryListView>() {

  public void clear() {
    view.clearListEntries();
  }

  public BlogEntryListView create() {
    return view.addListEntry();
  }

  public void bind(Bindings bindings, BlogEntry model, BlogEntryListView entry) {

    bindings.bind(
      observable(model, "title", String.class), 
      hasText(entry.subject())
    );
    
    bindings.bind(
      observable(model, "published", Boolean.class), 
      hasText(entry.marker()), 
      new BooleanToPublishedStatusTransformer()
    );

    // when rebind will be supported, HandlerRegistration will be managed by ListController
    entry.selectionClickHandlers().addClickHandler(new ClickHandler() {
      public void onClick(ClickEvent clickEvent) {
        if (view != null) {
          int index = view.getListEntryIndex(clickEvent);
          blogEntries.setSelection(index);
        }
      }
    });
  }
});

hasText is statically imported method which asks HasText and returns HasTextTarget.

While this ListControllerTarget interface is too specific for controller-view binding it suffices for now.

From example we can see how easy is to bind to list entries and how terrible current bindings api look. For Model-* related bindings I’m planning to implement easier to use and reuse API. See below.

ObjectController Target interface is similar:

registration = selection.addTarget(new ObjectControllerTarget<BlogEntry>() {
  public void bind(Bindings bindings, final BlogEntry model) {

    // general info

    bindings.bind(
      observable(model, "key", String.class), 
      hasText(view.key())
    );
    
    bindings.bind(
      observable(model, "status", Integer.class), 
      hasText(view.status()), 
      new StatusToStringArrayTransformer()
    );
    
    bindings.bind(
      observable(model, "title", String.class), 
      hasText(view.title())
    );

    // 
    bindings.bind(
      observable(model, "hasDetails", Boolean.class), 
      hasBoolean(view.showDetails())
    );
      
    // details

    bindings.bind(
      observable(model, "body", String.class), 
      hasText(view.body())
    );
    
    bindings.bind(
      observable(model, "created", Date.class), 
      hasText(view.created()), 
      new DateToStringTransformer()
    );
    
    bindings.bind(
      observable(model, "published", Boolean.class), 
      hasText(view.published()), 
      new BooleanToPublishedStatusTransformer()
    );
    
  }
});

Called when “content” is changed. null value will have separate callback also create callback will be here to support switching UI from null placeholder to actual view.

For general Target API — specific user Target API I’m thinking of something along those lines:

registration = selection.addTarget(simpleBindableTarget(new SimpleBindableTarget<BlogEntry(){
  public void onBind(Bindings bindings, BlogEntry model) {
    /// ...
  }
}));

where simpleBindableTarget is statically imported method that takes simplified, more targeted to usage interface and implements general ObjectControllerTarget.

The other approach would be just using ObjectController observation directly (still by using some simpleBindableTarget as a helper) and to bind UI against ObjectController itself (which would forward events to and from its content). Not yet decided.

So far we have observable models and controllers that helps to map model graph and selection to UI components. Now I’ll try to describe DataSource and Model state.

DataSource

As stated previously, Model has state integer. It has the following values:

int READY = 1 << 1;
int DIRTY = 1 << 2;
int BUSY = 1 << 3;
int ERROR = 1 << 4;

int LOADING = 1 << 5;
int SAVING = 1 << 6;
int DESTROYING = 1 << 7;
int REFRESHING = 1 << 8;

int NEW = 1 << 9;
int DESTROYED = 1 << 10;

When model is locally created (transient), it has “NEW | DIRTY” state, when it is currently loading from DataSource (async), the state is set to “BUSY | LOADING”. The state is observable, so view can show “Please wait” while loading, refreshing or deleting model objects.

DataSource has a bit different approach managing asynchronous operations. Basically all DataSource methods are seemingly synchronous. Lets look how the list fetching happens:

public class BlogEntryDataSource extends DispatchDataSource<BlogEntry> {

  public List<BlogEntry> fetchAll() {
    return fetchList(
      new GetBlogEntriesAction(), 
      new DispatchDataSourceCallback<GetBlogEntriesResult, DataSourceList<BlogEntry>>() {
        public void prepare(DataSourceList<BlogEntry> model, GetBlogEntriesResult result) {
          populateList(model, result.getEntries());
        }
      });
  }

}

As we can see from previous code snippet, fetchAll() returns immediately. It returns empty ListModel<BlogEntry> instance which can be set as a content to some ListController. Also the list implements IsModel so it has state property. It is set to “BUSY | LOADING”. When asynchronous call returns, DispatchDataSourceCallback#prepare is called with Result (Command pattern) and the same list what method returned previously. Now its time to add entries to the list. When it happens, List → ListController → Presenter → UI chain is invoked so new entries automatically appears in view.

In similar fashion also single models are fetched:

public class BlogEntryDataSource extends DispatchDataSource<BlogEntry> {

  public BlogEntry fetchLatestEntry() {
    return fetchSingle(
      new GetLatestBlogEntry(), 
      new DispatchDataSourceCallback<GetBlogEntryResult, BlogEntry>() {
        public void prepare(BlogEntry model, GetLatestBlogEntryResult result) {
          populateModel(model, result.getBlogEntry());
        }
      });
  }

}

Returned BlogEntry has all properties set to null and state “BUSY | LOADING”. All would be fine but this way if fetchLatestBlogEntry() is called multiple times, multiple BlogEntry instances for one actual blog entry row/entity would be created. And not always unique model primary key is known prior fetching it (as in this example).

So here comes small trick – instead of returning actual BlogEntry, the BlogEntryForwarding proxy is returned with null or dummy BlogEntry as target. When async returns, DispatchDataSourceCallback#populateModel(BlogEntry, GetLatestBlogEntryResult) is called and it updates BlogEntry key what can be checked for uniqueness in local DataStore. If model was already fetched previously, its properties are updated with new ones, BlogEntryForwarding proxy target is changed to that previous model instance but latest fetched is simply discarded. This process also works for each list item fetched.

Key comparison is done using IsModel#getKey() method what must be implemented by each model.

If model key is known prior fetching (fetchBlogEntryByKey(String key)), DataSource returns model instance without creating a proxy.

The benefits are:

  • zero or one model instance for each key (not counting possible proxies)
  • subsequent fetches will automatically notify view about updated model properties
  • DataSource can transparently cache models

The things that needs to be implemented are:

  • predicates/query names so list fetching can be cached (“fetchLatestEntry” – DataStore should know that “latestEntry” is fetched already before fetching it and not only after fetch find out that model with given key already exists in DataStore. This also applies to lists (“fetchAllEntries” with GAE DataStore cursor or Hibernate/JDBC page number)
  • fetch v.s. refresh (DataSource should be aware of difference)

Prettier Bindings For UI

While basic binding api can look so technical, bindings from observable/model to view should have special approach. Firstly bindings are both target type agnostic, for model-view bindings left side is always model.

Previously I had model-view bindings what looks like this:

binder.model(User.class).with(model(new ModelBuilderDelegate<User>() {
 public void bind(ModelBinder<User> b, User model) {

   b.property("key", String.class).
     to(hasValue(view.getKey())).
     to(hasText(view.getKeyValue()));

   b.property("name", String.class).
     to(hasValue(view.getName())).
     to(hasText(view.getNameValue()));

   b.property("login", String.class).
     to(hasValue(view.getLogin())).
     to(hasText(view.getLoginValue()));

   b.model("address", Address.class).with(model(new ModelBuilderDelegate<Address>() {
     public void bind(ModelBinder<Address> b, Address model) {

       b.property("street", String.class).
         to(hasValue(view.getStreet())).
         to(hasText(view.getStreetValue()));

       b.property("country", String.class).
         to(hasValue(view.getCountry())).
         to(hasText(view.getCountryValue()));

       b.model("details", Details.class).with(model(new ModelBuilderDelegate<Details>() {
         public void bind(ModelBinder<Details> b, Details model) {

           b.property("description", String.class).
             to(hasValue(view.getAddressDescription())).
             to(hasText(view.getAddressDescriptionValue()));

         }
       }));
     }
   }));
 }
}));

Now I haven’t touched that code yet but here is that test form running (also with list handling): http://models.latest.ampatspell-test.appspot.com/

Few notes about validation

While gwt-pectin and nearly all other binding frameworks also handle validation I feel that validation support should/must be separated from model-view bindings simply because it would be great to run the same validation definition in server-side. I deem it very useful. Also if validators are not instantiated directly, few validators what can not be run in GWT, can be skipped there and only run at server-side.

bones-validator does that (but API is far from great).

public class UserValidationModule extends AbstractValidationModule<UserDto> {

  @Override
  public void prepare(UserDto o) {
    field("name", o.getName()).with(Unique.class); // runs only in server side
    field("name", o.getName()).with(NotBlank.class);
    // allowBlank() is EmailValidator instance method call for this particular field
    field("email", o.getEmail()).with(Email.class).allowBlank();
  }

}

If model-view bindings are aware of validation (if needs to be), I think it can be done while keeping bindings and validation APIs clean.

 
Internet Explorer 6
Are you serious?