Rated by the Jargon, Acronyms, and Buzzwords (JAB) index, the title of this article scores high, but all the terms do come together. This article examines several programming patterns, that together with GWT and Ajax, can produce a better user Web experience with faster response times. And don't worry if you aren't familiar with the JAB index: I just invented it!
Let's work through the equation in the title from right to left, starting with usability. The usual Web-oriented definitions call for easy-to-use sites that have clear screen layouts and workflow logic, don't require specialized training, and so on. In this case, I focus specifically on response time. Speed alone doesn't provide usability (although under-performing sites aren't very usable), but the tools I consider can make a speed difference without harming other aspects of your site.
Ajax allows a client to retrieve
data from the server in the background, providing a more streamlined feeling
to the user; a well-designed Ajax Web application can look and feel just like
a standard (installed) program. Before Ajax, all data requests implied
standing by, waiting for the server to answer. With Ajax, users can keep working, and the data will be invisibly fetched. (Actually, Ajax isn't truly
required; by using an
iframe appropriately, you can
manage the same effects, though in a more complicated way.) Being able to free
users from (some) delays is an important step toward more usable applications,
and that justifies including Ajax in the sum.
GWT applies Ajax transparently. A client application can use server-side servlets almost as if they were client-side servlets, which means that the client and server can (with some limits) share classes and code, allowing for "thicker" clients. Prior to GWT, client-server interactions were more complicated to program, and of course you couldn't use Java code for the client. GWT bridges that gap, making for easier development of highly usable sites.
Now, what's a pattern? In software engineering, a pattern represents a general, widely applicable solution for commonly occurring problems. It isn't a direct solution, but rather a "path" that leaves implementation details up to the programmer. A pattern allows developing software more quickly, because it provides a tested, proven scheme.
Patterns originated as an architectural concept, and comparisons with architecture are common. As an oft-cited example, a window is a solution to let the sunshine in, but that doesn't specify the exact shape or style of the window. There need not be a single pattern for a problem (a skylight or an open patio could also solve the lighting problem), and it's up to the designer to decide which pattern to apply. This article introduces several performance problems and provides GWT-appropriate solutions that you can use in your own Web sites.
I worked with a simple database (see Listing 1) that included the countries, regions, and cities of the world. I just needed large enough tables, and an open database (see the sidebar, "Building a large test database") provided about three million records, so that suited my needs. To follow the examples below, note that:
- Countries are identified by a code (such as US for the United States) and have a name.
- Countries have regions, identified by a (usually numeric) code, unique only within the country, and with a name.
- Cities are within a region of a country and have a (pure ASCII) name, an accented name (which might include foreign characters), a population (or 0, if unknown), a latitude, and a longitude. The city name is unique only within a region of a country; there are about three dozen cities named Springfield in the United States alone!
Listing 1. Database creation code
CREATE DATABASE world DEFAULT CHARACTER SET latin1 COLLATE latin1_general_ci; USE world; CREATE TABLE countries ( countryCode char(2) NOT NULL, countryName varchar(50) NOT NULL, PRIMARY KEY (countryCode) KEY countryName (countryName) ); CREATE TABLE regions ( countryCode char(2) NOT NULL, regionCode char(2) NOT NULL, regionName varchar(50) NOT NULL, PRIMARY KEY (countryCode,regionCode), KEY regionName (regionName) ); CREATE TABLE cities ( countryCode char(2) NOT NULL, cityName varchar(50) NOT NULL, cityAccentedName varchar(50) NOT NULL, regionCode char(2) NOT NULL, population bigint(20) NOT NULL, latitude float(10,7) NOT NULL, longitude float(10,7) NOT NULL, KEY `INDEX` (countryCode,regionCode,cityName), KEY cityName (cityName), KEY cityAccentedName (cityAccentedName) );
I developed a GWT project (see Downloads for complete source listings), including a simple menu (Figure 1) plus two equally simple Web forms: a Cities Creator (for adding new cities to the database—see Figure 2) and a Cities Browser (for paging through the cities in any country region—see Figure 3). I wrote the code in the simplest way I could, with minimal extras, because the idea to show patterns. I worked with GWT version 1.5.3 and MySQL version 5.0; development was done with Eclipse Ganymede running under OpenSUSE version 10.3 Linux®.
Figure 1. The main menu of the sample application, showing both available forms
Figure 2. The Cities Creator form allows you to add new cities to the database
Figure 3. If the user enters a duplicate city name, an Ajax background check gives a warning and highlights the field
An axiom of client-server computing is to check everything server side. (Even if data is validated before calling the server, changes brought by other users might invalidate your previously correct data. An originally available article might have gone just now out of stock.) However, you don't want to make users wait for a client-server round trip before learning about a simple mistake. The solution: Make an Ajax call in the background to a server-side checking routine; if there are errors, warn the user, highlight incorrect fields, and so on.
CitiesCreatorForm class and its
addDuplicateCityNameCheck method. Assume that the user should never
enter an existing city name. Given the server-side
cityExists service that checks for duplicates, add a
ChangeListener to the
cityName text box; if the user picks a country, region, and city
name, call the service and check if it's a duplicate at that time.
given code has a subtle problem, though. Suppose a nimble-fingered user enters
a (duplicate) city name but immediately realizes his mistake and fixes it.
Soon, he or she receives an alert stating that the (now correct) field is
wrong. A simple fix (see Listing 2) is given in the
CitiesCreatorForm2 class: Save the service
parameters and, on getting an answer, check whether the form still has the
same values as the parameters; if not, don't do anything.
Listing 2. Prevalidation pattern pseudo-code
create a new ChangeListener that will: get the form field values needed for the check if all fields are filled save the form field values call the server-side service to perform the check on callback: get the form field values again if the current values match the saved values, if there was an error, highlight the fields warn the user otherwise reset fields to normal assign the created ChangeListener to all involved form fields
The code-sharing pattern calls for implementing a client-side version of a class, then extending it for your server, which will be able to use all Java features. Because client-side code is only able to deal with its own, limited objects, two special methods are needed: a constructor that can receive a client-side object and use it to create a server-side object and a method that can produce a client-side object out of a server-side object.
classes show this pattern. The client-side code needs to implement the
IsSerializable interface so objects can be sent back
and forth between the client and the server. The
ServerCityData class can only be used on the server and includes
both special methods described above.
So far, GWT has helped me deliver more performance, but there is one area in which it could even make performance worse: caching. Whenever a browser requests a page, it first looks into its own cache. If it finds the needed results there, it won't call the server and simply provides the data. (Of course, many conditions must be fulfilled before something goes into the cache, but that's not relevant now.) The problem is that when GWT calls a servlet, it implements a Remote Procedure Call (RPC) by a non-cacheable Ajax procedure, so even if you repeatedly ask for the same data, the browser won't use its cache, and there will be a delay every time, as shown in Figure 4.
Figure 4. The Cities Browser lets you page through a region's cities
If your page needs the same (constant) information that it got earlier, you can enhance performance by setting up a local cache, as shown in Listing 3. Before calling the server, check if the needed data is already loaded and if so, skip the call. Of course, don't use a cache for information that changes often. If information can go stale over time, add a time stamp to avoid using old data.
Listing 3. Caching pattern pseudo-code
class_with_cache code: define class attributes for the cache (a hash map, array, whatever) set the cache to empty whenever new data are asked for: check if the asked data are already in the cache if so, get the data from the cache perform whatever needs be done with it otherwise, display an appropriate "loading" message call a server-side service to get the data on callback: put the data in the cache perform whatever needs be done with it
are three examples of this process in the provided code—all for
CitiesBrowser class. The simplest one has to do
with a list box (see the
CountryList class) showing
all countries. Its basic implementation called the server (to get the
countries list) for each object. The modified
CountryListWithCache class saves that data in a class variable so
that it can be shared by all objects; only the first object creation actually
implies a call to the server.
I also needed a regions list box (see the
RegionList class) whose contents should vary
depending on the current country. There are several thousand regions across
the world, and getting all of them isn't practical. To implement a cache (see
RegionListWithCache class) I used a hash map;
whenever the country changed (see the
method), I checked first if I had already gotten that country's regions.
The final example (see
CitiesGridWithCache) is a bit more complicated.
Regions can have too many cities, so information has to be paged. I
used a hash map as a class attribute, but had to construct a key that included
the country, the region, and the page start, as shown in the
Whenever lots of data needs to be sent from the server to the client, some kind of chunking is needed. If you could know in advance which information a user was going to ask for, you could use Ajax mechanisms to "get ahead of the game," asking for the data before it's required. You cannot always guess what a user is going to ask for, so there's the chance that you will be mistaken and bring over some unneeded data; that risk must be balanced with sure delays if you do not prefetch.
But be careful: Don't go overboard and prefetch everything in sight, or you'll make things worse! Since the time of limited-bandwidth dial-up modems, browsers have limited the number of client-server connections. That limitation even made its way into the Hypertext Transfer Protocol (HTTP) version 1.1 standard ("A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy.") If you make several requests to your host, only two of them will go out (in parallel), and the others will be queued for even longer-than-usual delays.
CitiesBrowserWithCacheAndPreFetching shows the needed changes to
implement this. First, change the
so that it won't always show on screen whatever data it has loaded: When
prefetching, you don't display anything. Second, whenever a page is shown (see
showCities method), prefetch the next one (a
logical guess) without showing it. And finally, whenever a user picks a
country and region, prefetch the first two pages just in case, as shown in
Listing 4. Note that if the code asks to prefetch an already-fetched page, the
implemented logic avoids making unnecessary, redundant calls to the server.
Listing 4. Prefetching pattern pseudo-code
class_with_cache_and_pre-fetching code: define class attributes for the cache (a hash map, array, whatever) set the cache to empty load_data method: check if the asked data are already in the cache if so, get the data from the cache perform whatever needs be done with it otherwise, display an appropriate "loading" message call a server-side service to get the data on callback: put the data in the cache if data were needed (as opposed to prefetched), perform whatever needs be done with it processing_data method: call the load_data method to get that data call the load_data method to get extra (prefetched) data
Suppose a processor-intensive task, like processing large amounts of XML or displaying lots of data. If the process takes too long, users receive a message like Firefox's "A script on this page may be busy, or it may have stopped responding. You can stop the script now, or you can continue to see if the script will complete." or the similar message in Windows® Internet Explorer®, "Stop running this script? A script on this page is causing Internet Explorer to run slowly. If it continues to run, your computer may become unresponsive." Even worse, if a user pays heed to the warning and stops the script, he or she will actually cancel your client-side program!
GWT provides a
setTimeout() method. The idea is to
do a bit of work and store values so the process can continue later (after a
timeout), freeing the processor in the meantime, as shown in Listing 5. Check
whether the process needs to go on; the user might have decided to page
forward or backward, and it wouldn't do for the former page data to show up.
Listing 5. Thread simulation with timers
define a class that extends Timer: define attributes so it can save its parameters define attributes so it can save local variables from run to run define attributes so it can save form field values on construction: save the received parameters initialize local variables for the process save the current form field values display a "loading" message run() method: if the current form field values match the saved values: execute some process, updating the local variables if there's still more work to be done schedule another process in a short while whenever you want to simulate a thread with a timed method: create an object of the new class above, with appropriate parameters execute its run() method
shows this pattern. The private
class extends the
Timer class. On construction, it
receives a list of cities and initializes an iterator to go through it; it
also saves the current country, region, and page to later check whether the
process must continue. The
run() method goes
through a few cities; if there are any more cities left, it schedules a future
run, which restarts the work wherever it was stopped, as Figure 5 shows.
Figure 5. The background cities are displayed midway through its work (some cities haven't been loaded yet)
For this solution to work smoothly, study the maximum amount of work that can be done at each step and the time interval to allow between steps. A large number of short steps might mean a more responsive machine, but it also means a longer wait until getting all the data. However, long steps can possibly lead to the "busy script" message, and that's also bad. Only experimentation will show the best compromise.
Deferred commands are a GWT-specific feature that
may provide an even better solution. Deferred commands are queued and executed
when the processor is free, as shown in Listing 6. The solution: Parcel the
process into short steps, but use a deferred command instead of a
Timer. GWT will decide when it can run the next step
of the calculation.
Listing 6. Thread simulation with deferred commands
define a class that extends IncrementalCommand: define attributes so it can save its parameters define attributes so it can save local variables from run to run define attributes so it can save form field values on construction: save the received parameters initialize local variables for the process save the current form field values display a "loading" message execute() method: if the current form field values match the saved values: execute some process, updating the local variables if there's still more work to be done return true, so it will run again shortly afterwards otherwise, return false (the job is done) otherwise, return false (situation changed) whenever you want to simulate a thread with a deferred command: create an object of the new class above, with appropriate parameters use the addCommand() to add your new object to the processing queue
CitiesGridWithCacheAndPreFetchingAndDeferredCommands class shows
this. The main differences with the timer solution are that commands are
queued and the
execute() method returns True if the
process must be sent back to the queue for another round.
This solution is more flexible than the Timer pattern, as it goes full speed if the user doesn't do anything but doesn't keep the machine from being responsive otherwise. Just don't do too much at a stretch.
|Full source code for this article||full_source_code.zip||24KB||HTTP|
- Jesse James Garrett first wrote about Ajax
in his February 2005 essay, "Ajax: A
New Approach to Web Applications."
- The Google Code GWT site is a
mandatory visit. There is also a good technical explanation about how GWT uses Ajax for RPC.
- There are many online sources for usability,
such as the Usability.gov site or
usability expert Jakob Nielsen's Alertbox columns.
- The Portland
Pattern Repository is a very up-to-date site on everything related to
patterns, and the 1994 book, Design Patterns: Elements of Reusable Object-Oriented Software
by the "Gang of Four" (Gamma, Helm, Johnson, and Vlissides) is a basic
reference and practically required reading.
- On the two-connections limit, the Open Ajax
Alliance site has a good discussion of the issue.
- ISO maintains the ISO 3166 family of codes
for countries and for regions (3166-2). FIPS has a list of region codes,
formed by the two-letter country code plus a two-letter region
code—for example, the American state of Illinois is
US18; note that 18 is the code instead of the common
- The developerWorks Web
development zone is packed with tools and information for Web 2.0
- Stay current with IBM technical events and webcasts.
- Browse the technology bookstore for books on these and other technical topics.
Get products and technologies
- If random (or almost
random) data is good enough for your purposes, there are several free
Structured Query Language (SQL) generators, such as GenerateData, DBMonster, Open Datagenerator, dgMaster, and Spawner Data Generator as well
as commercial solutions like DB Data
Generator and EMS Data
Generator. Note, however, that not all products offer the same
- Download the GWT, and try out
the code included with this article.
- Download Eclipse Ganymede, the standard
integrated development environment (IDE) for GWT.
- Download MySQL—either the
more recent Generally Available (GA) 5.1 version or the more time-tested
- Check out MaxMind's
free cities table.
Federico Kereki is a Uruguayan systems engineer with more than 20 years of experience developing systems, doing consulting work, and teaching at universities. He is currently working with a good jumble of acronyms: SOA, GWT, Ajax, PHP, and of course FLOSS! You can reach Federico at email@example.com.