Blogs / Tech Blog

Data Model Change Eventing

One of the early architectural challenges that we faced in building the Palantir Finance product was coming up with a good design for firing events from data models to their listeners. There are many different concepts in our product such as charts, portfolios, and indices which are all maintained by different developers. Initially, each developer had their own system for firing events when a data model changed. This quickly became a drag on development as tools became more integrated because we had to learn each others’ event methodologies and translate between the different systems.

The solution was to select a single event firing system. We wanted something that was easy-to-use yet powerful enough to express all the changes that might be made to a data model. Java’s Property Change Support (PCS) was a good fit because it can support arbitrary events in a very lightweight fashion.

Read on for details of our implementation…

Property Change Support

Java’s PropertyChangeSupport class (PCS) basically allows an object to easily fire events consisting of 4 pieces of information:

  • source object – the thing that fired the event
  • property name – allows the listener to tell events for different things apart
  • old value – the old value for the property
  • new value – the new value for the property

PCS handles all the bookkeeping for adding and removing listeners and firing events. It is very useful for creating listenable models, but we wanted to make it just a little bit easier by having an abstract class that exposed the add/remove listener calls and took care of initializing PCS:

public abstract class AbstractListenableModel implements Serializable {

    private static final long serialVersionUID = 1L;

    private transient PropertyChangeSupport pcs;

    protected AbstractListenableModel() {
        this.init();
    }

    /**
    * Adds a property change listener to the model.
    */
    public final void addPropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.addPropertyChangeListener(listener);
    }

    /**
    * Removes a property change listener from the model.
    */
    public final void removePropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.removePropertyChangeListener(listener);
    }

    /**
    * Fires a property change event to listeners of the model.
    */
    protected final void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        this.pcs.firePropertyChange(propertyName, oldValue, newValue);
    }

    /**
    * Initializes transient fields during deserialization.
    */
    protected Object readResolve() {
        this.init();
        return this;
    }

    /**
    * Initializes transient fields.
    */
    private void init() {
        this.pcs = new PropertyChangeSupport(this);
    }
}

AbstractListenableModel is basically just a simple wrapper for exposing the functionality of PCS. By extending this abstract class, it’s very easy to create a listenable model:

public final class MyModel extends AbstractListenableModel {

    public static final String PROP_FOO = MyClass.class.getName() + ".Foo";

    private int foo;

    public int getFoo() {
        return this.foo;
    }

    public void setFoo(int foo) {
        //
        // The semantics of the following line are a little hard to unpack,
        // but it does exactly what it needs to do, and the tradeoff
        // for conciseness over immediate readability is worth it for
        // large models with lots of properties.
        //
        // First, the JVM starts to create a stack frame for the call
        // into firePropertyChange().  It begins binding parameter values
        // from the left to the right.  The pointer to the String contained
        // in PROP_FOO is passed in first, then the current value of
        // this.foo is passed in, then the expression
        //        this.foo = foo
        // is evaluated (setting this.foo to the new value of foo), which
        // returns the new value of foo.  All the parameters are then
        // passed down into firePropertyChange(), which checks whether
        // the oldValue is equal to the newValue.  If they're not equal,
        // it fires the event.  If they are equal, it ignores the event.
        //
        this.firePropertyChange(PROP_FOO, this.foo, this.foo = foo);
    }
}

In this example, MyModel contains a single property called foo. When the value of foo is changed, a property change event will be fired to listeners of the model.

You may notice that the value of PROP_FOO is prefixed by the name of the class. This ensure that naming collisions do not occur for scenarios in which the same listener is used to listen to multiple models which happen to use the same property name. This scenario becomes much more likely in the case of event bubbling, which I’ll talk about next.

Event Bubbling

Imagine a scenario in which we have a nested model:

Normally, if a listener needs to receive events from both models A and B, it will need to add itself as a listener to each individual model. While this solution would work, it’s a little cumbersome, especially when model B can get swapped out for model B’—the listener then has to keep itself synched to the internal state of model A. It would be nice if model A could just automatically forward all the events from model B (or B’) via its PCS support so that a listener only needs to attach itself to one model instead of multiple models. With a bit more code in AbstractListenableModel, this is possible:

public abstract class AbstractListenableModel implements Serializable {

    private transient PropertyChangeListener childModelListener;

    ...

    /**
    * Registers a child model to this model.
    */
    protected void registerChildModel(ListenableModel childModel, String propertyName) {
        childModel.addPropertyChangeListener(this.childModelListener);
    }

    /**
    * Initializes transient fields.
    */
    private void init() {
        ...
        this.childModelListener = new ChildModelListener();
    }

    /**
    * Listener for property change events fired from child models.
    */
    private final class ChildModelListener implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent event) {
            // This is where the bubbling happens
            pcs.firePropertyChange(event);
        }
    }
}

Now, whenever model B fires a property change event, this event will also be fired by model A. This makes it much easier for the listener to listen to events arbitrarily deep in the model hierarchy, because each event fired by a child model gets re-fired (bubbled) by all its ancestors. All you have to do is attach a listener to the root model and you’ll automatically receive events from all models in the hierarchy.

Note that the registerChildModel method above takes an unused propertyName argument. In the full implementation of this class, events with the provided property name are monitored. When an event with the provided property name is fired, childModelListener is detached from the old child model and attached to any new child model. This ensures that the listenable model is always listening to the current child models.

Events for Collections

Any model event support would not be complete without some consideration of how to handle collections such as sets and lists. To solve this scenario, we created specialized collection classes called ListenableModelSet and ListenableModelList. These collections hold AbstractListenableModels as their elements and fire events whenever their contents change. Since the changes to collections can vary widely, the solution we came up for communicating collection changes with full fidelity is basically to fire events with a copy of the old set as the old value and the new set as the new value. Listeners can then diff the old and new values to determine exactly what changed if necessary. Additionally, each ListenableModelSet or List adds a ChildModelListener to all of its children (themselves AbstractListenableModels), thereby ensuring that events are bubbled from all models in the collection.

Conclusion

Just as we saw with the Adapter piece of the MVA triad, when we componentize the Model piece there are huge gains to be had. Once we started using a base Model class and a consistent eventing infrastructure (PropertyChangeSupport), we could add features that made coding across our entire application a lot more pleasant.

Other Blogs