Realtime Data with ZK
So, we've decided instead of GWT, we're going to use ZK for our disgustingly dynamic UI. I was a bit nervous about switching over because I was really enjoying GWT and getting huge amounts of work done with it in nearly no time. However, our UI guy thought ZK would make it easier for us to get stuff done quickly. As far as I was concerned, I was at terminal velocity. I couldn't think of stuff as fast as I could make it...but whatever, I'll give it a shot.
A ZK Story
I'd just finished up all of my work on our entity property change notification framework. In unit tests, I've demonstrated all kinds of notification scenarios, and I'd built our groups in such a way that they could use these notifications to automatically track membership changes just by looking for property changes on criteria. Nobody can see that stuff, though, so I needed a UI.
I made a simple ZK widget that would show the value of a property, and then subscribe to changes to that property to automatically reflect the new values on their arrival and brought it up in the UI. Because the persistence layer wasn't hooked into my change notification framework at that point, I used a tool I'd written that generated arbitrary changes for a specific property of a specific object and used that for initial tests. I could get a label up on the screen and watch it transform as the notifications were sent.
Of course, we need more magic in the label, because you can display properties that are really properties on objects related to the object you're looking at. For example, we want to display the name of the owner of a device, which requires us to get the service from the device to which the owner is attached, and then get the customer from that. In dealing with changes for displaying this name, I need to know when the device's service changes, the service's owner changes, or the owner's name changes. If just the name changes, I can update the UI with it directly, but if the actual owner owner changes, or the service changes, I have to tear down my subscriptions and subscribe to the correct objects.
Once the persistence layer and the notification layer got glued together (thank you aspectj), I was ready to make my label bidirectional. That went pretty well, and I could now edit stuff in one browser and see it in another. Yay magic.
The best part is that that whole thing looks like this:
<live-value-edit-label entity="${someObject}" prop="service.customer.name"/>
It's easy enough to use that I imagine they're going to be all over the place.
Implementation Junk
So, because it's going to be all over the place, I wanted to see what would happen if I but a bunch of them on a screen. In short, nothing good.
In zk, the widgets for a given display are stored on the server and can be manipulated there. However, there's a caveat: You can only manipulate displayed properties of widgets during an event handler (i.e. during an AJAX call of some sort). The easy way to do this is to have the server-side-only code (e.g. the property change callback) modify the state of the object, and set up a timer on the UI that will poll your object to see if anything has changed on it.
Once you get a hundred or so of these things on the screen, your computer will catch fire. I had two browsers running on the same UI, each using as much CPU as they could and still leave my server with the 16% it needed to be saying, nothing's changed
hundreds of times a second.
Obviously, I needed a new strategy
This morning, I realized I could solve the entire problem (and a few related problems I didn't mention) with a single timer and a DelayQueue
. The timer is fired once a second and makes an AJAX call that gets all of the ready tasks from the DelayQueue
and processes them within the context of the event handler.
Whenever something on the backend occurs that the widget decides should produce a visible change in the UI, it grabs the DelayQueue
and adds a new task to do whatever manipulation it wants. This manipulation looks pretty much exactly like what you want to do in an event handler, but with a little stuff above it and below it to make it deferred.
The net effect is that the number of items on the screen no longer affects how the polling scales. If nothing changes (which is the common case), nothing is done. I could see about 1% on the server, less than 1% on Safari, and about 8% on Firefox (which was doing some other stuff that could account for the extra).
Demo
The following demo shows two browser windows manipulating customer names in a list of devices. The same customer appears many times, and each place the customer shows up on the UI must be updated when it changes. You can see small delays when looking side-by-side because of the polling frequency, but I'd expect nobody would notice in normal operation.
Of course, you'll need javascript and flash in order to see this. Whatever you're to look at this doesn't have one or the other, so just imagine two browsers side-by-side showing the same data. When you chane it on the left, it updates on the right.