Elegant code splitting with GWT

16Feb10

Update February 17, 2010: I forgot to mention that if you need FIFO guarantees today and don’t mind losing some of the conciseness, Jarrod Carlson has a nice solution posted here. I plan on stealing his way of doing FIFO.


Since leaving Microsoft in September, I’ve been working almost exclusively with Google Web Toolkit (GWT). Now that I have spent some time with it, I think it’s one of the most exciting advances in web development in years.

I came up with a (possibly) novel way to abstract the code splitting facilities in GWT 2.0 and will try to share it as best I can in this post. (Thanks to my boss, JJ Allaire, for letting me share this code!)

Warning: This post assumes the reader is very familiar with GWT and code splitting, and the ideal reader will have already tried and failed to find a nice way to reuse code split logic. Seriously though, you’ve been warned!

The Problem: Reusing Code Splitting Logic

GWT 2.0 provides a mechanism for splitting your codebase so that some of your code and resources can be loaded on-demand rather than at page load.

The official GWT site recommends the Async Provider pattern as a way to split up your codebase. This is a great start, but you need an excessive amount of boilerplate for each module. Plus, callers now have to use a pretty tortured anonymous class based syntax to get at the API.

Because GWT effectively introduces one split point for each time GWT.runAsync appears in the program source, it’s not obvious how to get around cutting-and-pasting all of this boilerplate.

The Solution: Deferred Binding

Fortunately, GWT gives us the tools we need to get out of this mess. We’ll build on top of the same pattern, but use deferred binding (via my library) to generate the boilerplate at build time.


Terms of Use

I wrote this on company time but thanks to my benevolent employer, the source code and binaries in the library asyncshim.jar are public domain. But use at your own risk.

Prequisites

This library depends on Google Gin. You don’t have to actually use Gin within your project for it to work, but it won’t compile unless gin.jar is in the classpath. Really, though, if your GWT project is big enough to need code splitting, it is big enough to need Gin.

Installing the Library

Download asyncshim.jar and add it to your project’s classpath. Then add the following line to your project’s .gwt.xml file:

<inherits name="com.joecheng.asyncshim.AsyncShim" />

Getting Started

Now that you’ve got the library loaded and inherited, it’s time to remove some boilerplate. Here’s the example module from the Async Provider documentation:

public class Module {
  // public APIs
  public void doSomething() { /* ... */ }
  public void somethingElse() { /*  ... */ }

  // the module instance; instantiate it behind a runAsync
  private static Module instance = null;

  // A callback for using the module instance once it's loaded
  public interface ModuleClient {
    void onSuccess(Module instance);
    vaid onUnavailable();
  }

  /**
   *  Access the module's instance.  The callback
   *  runs asynchronously, once the necessary
   *  code has downloaded.
   */
  public static void createAsync(final ModuleClient client) {
    GWT.runAsync(new RunAsyncCallback() {
      public void onFailure(Throwable err) {
        client.onUnavailable();
      }

      public void onSuccess() {
        if (instance == null) {
          instance = new Module();
        }
        client.onSuccess(instance);
      }
    });
  }
}

We’ll start by deleting all the boilerplate, leaving behind only the public APIs:

public class Module {
  // public APIs
  public void doSomething() { /* ... */ }
  public void somethingElse() { /*  ... */ }
}

It’s worth nothing that “Module” can be almost any kind of class—it doesn’t need to extend or implement anything in particular. It can be a widget, a class from the JRE or GWT framework, etc. It can have lots of methods (even public ones) that you don’t want to be part of the public API. And in case it wasn’t obvious, it definitely doesn’t need to be called “Module”. All this is to say, if you haven’t been following the Async Provider pattern, hopefully there is still a class that’s already in your codebase that you can point to and say, “Yes, this is the perfect module thing for split point A”.

Next, introduce a new abstract class (it can be inner static if you want) that subclasses AsyncShim and specializes it to Module. Add abstract versions of the public API methods. And finally, create a singleton instance of it, using GWT.create.

public abstract class ModuleShim extends AsyncShim<Module> {
  public abstract void doSomething();
  public abstract void somethingElse();
}

Pretty simple. Now all your callers just need to get their hands on ModuleShim instead, and once they do, they can simply call the public API methods on it instead of directly on a Module instance. The first time a public API method is called, it will cause the split point code to load (if it’s not loaded already), an instance of Module to be instantiated, and the corresponding method on the new Module instance will be called. (On subsequent API method calls, the existing instance of Module will be reused.)

To create/initialize instances of ModuleShim, you have two options.

If the Module is Gin-injectable then just use Gin to inject ModuleShim. Done.

Otherwise, use GWT.create(ModuleShim.class) to get an instance of ModuleShim, then call the ModuleShim.initialize(Provider<Module>) method. The Provider<Module> can be as simple as an anonymous class that does “new Module()” (although if that’s all you were going to do, you could’ve just used Gin). You must ensure that initialize is called before ModuleShim is first used—otherwise the public API will fail, as no instance of Module will be available.

Slightly More Realistic Example

Here’s the Module-like class, the one we want to use as the locus of our split point. It’s a dialog box. There’s lots of code, but we’re not introducing any new methods that are going to go into the public API (though we could if we wanted to, mind).

public class ComposeMessageDialog extends DialogBox {

  @Inject
  public ComposeMessageDialog(Server server) {
    // whatever...
  }

  // lots and lots of code...
}

Here’s the shim we’re wrapping around it. Any code that could load before the split point should go through this shim and its public API. (Notice how one of the methods we’ve chosen to put in the public API takes a PositionCallback?)

public class ComposeMessageDialogShim
    extends AsyncShim {

  // Public APIs. These are from DialogBox, actually.
  public abstract void show();
  public abstract void setPopupPositionAndShow(
                         PopupPanel.PositionCallback callback);
  public abstract void hide();
  public abstract void hide(boolean autoClosed);

  // See below to learn about onDelayLoadFailure
@Override protected void onDelayLoadFailure(Throwable reason) { Window.alert("Error: " + reason.getMessage()); } }

And finally, here’s the constructor for a ButtonPanel class—it’s part of the main UI of our app (i.e. will load before the split point) and goes through the shim.

@Inject
public ButtonPanel(ComposeMessageDialogShim shim) {
  Button compose = new Button("Compose", new ClickHandler() {
    public void onClick(ClickEvent event) {
      shim.show();
    }
  });
  Button cancel = new Button("Cancel Compose", new ClickHandler() {
    public void onClick(ClickEvent event) {
      shim.hide();
    }
  });
  // ...
}

That’s all there is to it.

Going (Slightly) Deeper

Well, maybe not all. Your AsyncShim subclasses, e.g. ModuleShim, have some additional features besides loading Module and delegating calls to it.

Success/Failure Callbacks

You can override AsyncShim.onDelayLoadSuccess(T) and onDelayLoadFailure(Throwable).

onDelayLoadFailure will be called if GWT fails to download the code for this split point.

onDelayLoadSuccess is called after the shimmed type (e.g. Module) is successfully instantiated, with the new instance passed as a parameter. This is a handy place to finish whatever initialization needs to be finished before any of the public API methods actually fire. For example, if your Module is actually a Widget then you can have this method add the newly created Widget to the Panel where it should appear.

Prefetching

Prefetching is easy: just call forceLoad(true, null) on your shim. (See the source comments for an explanation of the arguments.)

Inheritance

If you have a bunch of code that you want to reuse between your different Shim subclasses (different split points), for example to have common error handling code, you can create a common Shim base class with common logic for onDelayLoadSuccess and/or onDelayLoadFailure. Inheritance should work as it normally does. Any public abstract void methods that appear in the inheritance hierarchy will become part of the public API (and you’ll get a compile error if the compiler doesn’t like the results).

Limitations

The shim causes public API methods to be executed asynchronously. As a result, all public API methods must return void. If callers need access to the result of a computation, then design your public API method to take a callback as a parameter (which is perfectly cromulent—public API methods absolutely can have parameters!).

Also, multiple calls to the public API methods are not guaranteed to be executed in the order they were called (i.e. no FIFO guarantee). This wouldn’t be hard to add, I just haven’t gotten around to it yet.

Hope That Helps…?

To be honest, it’s been harder to try to explain this library than to actually implement it in the first place. If you’re just not getting it from my explanation but like the idea of clean, reusable GWT.runAsync, drop me a comment below or on Twitter (I’m @jcheng).

About these ads


14 Responses to “Elegant code splitting with GWT”

  1. 1 Jarrod

    First of all, I applaud your efforts and you’ve definitely done a nice job of documenting your findings. I will offer a few comments on the solution, though.

    I like that you’ve taken the idea of my PresenterProxy (http://groups.google.com/group/google-web-toolkit/browse_thread/thread/6ec343860253c621/20ab002804a6ee6e) and expanded it to support most any class imaginable.

    However, a side effect of this solution is that dependent classes (your ButtonPanel, in this example) must now be updated to rely on the *Shim class instead of the original class used before the shim.

    One of the tenets of dependency injection is that you should be injecting dependencies according to interface, not implementation (see Martin Fowler’s explanation: http://www.martinfowler.com/articles/injection.html#InterfaceInjection). So your dependent classes shouldn’t have to change when your implementation changes, nor should they care what type of implementation they receive.

    So in that regard, while using the shim may provide a nice way to abstract some boilerplate, it does break your existing API contracts and it requires that you define new *Shim contracts that are likely to duplicate existing API contracts.

    If the underlying implementation in the shim is to be created and delegated to asynchronously, then it is true that each shimmed method must return void (similar to the RPC async interfaces), and that imposes some limitation on what we can readily abstract.

    But perhaps you are on to something with the use of deferred binding. However, maybe what we should be looking at doing with the deferred binding is utilizing a generator that can (at compile time) generate a unique subclass of the original class capable of performing the asynchronous grunt work automatically. Perhaps if I have some time I can toy with that idea…

  2. 2 Jim

    Would you like to compare GWT with Silverlight because you have a very strong background in .NET? I trust your judge.

    Jim

  3. 3 Mike

    Ah, Joe, sharing some love at the blog again, good stuff. Although my experience with GWT has been limited, so far, RT and I have been kicking the tires with GWT a bit lately and it just seems so promising. Now, with Google interactive chart tools, it could be that much more nicer for our potential uses. I haven’t had the need for code splitting yet, but from reading your post, I can already envision some of the pain it can be. I know your time is tight, but share away on GWT when you can. Thanks for the great content.

  4. @Jarrod, responded on the gwt-help list:
    http://groups.google.com/group/google-web-toolkit/msg/c4cc6880b1721f03

    @Jim, they are pretty different. To vastly oversimplify, I’d look to Flash or Silverlight for flashy or media-centric interfaces. You can definitely accomplish visual effects in Flash/Silverlight that are still not really feasible in HTML. But for most web apps, I think you’re better off sticking with HTML as much as possible; and in that world, I believe your UI complexity ceiling is higher with GWT than any other framework I have seen.

    @Mike, feel free to hit up me or Charles for GWT questions–we’ve both been swimming in it for quite a while now and have absorbed quite a bit of info.

  5. 5 Mike

    @Jim, although the install base for Flash is very high and Silverlight seems to be climbing, using Flash/Flex or Silverlight does carry the need for those platforms to be installed on the client. Personally, if I can do something within my web app that gives me my desired results without Flash or Silverlight, I’d do without every time. But, like Joe says, some things you just can’t do well yet without using Flash or Silverlight. In the past, I used the Flex charting packaging because it was untouchable for charting UI, at the time, but Google has Interactive Charts, now, many that are GWT integrated, that look like a great substitute. In case you were curious, you can see some examples here, http://code.google.com/apis/visualization/documentation/gallery.html

    Oh, and by the way, the last few times we investigated using Silverlight at work, the install scenario on the client for Silverlight left much to be desired. Something I hope MSFT has already fixed or will be fixing soon.

  6. 6 asianCoolz

    Hi @Joe, great article.
    1.can you post sample application consuming shime+async ?
    2. do u use gwt-presenter mvp ?

  7. did the comments get there?

  8. thanks.
    your tips will save us some pains.

  9. 9 K

    Maybe you can try com.google.gwt.user.client.AsyncProxy for the *same* purpose.

  10. @K:

    Thanks for the pointer, I hadn’t heard of that! Now I’m kind of annoyed I spent all this time duplicating functionality that was part of GWT since Dec ’08. The only advantage for my work is that it integrates easily with Gin–unless there is some way AsyncProxy can easily be used with @Inject constructors?

  11. I didn’t know about the AsyncProxy interface either. This should probably be better documented in GWT.

    What I’ve been using, though, is gin’s new AsyncProvider. It’s available in gin trunk since yesterday (r137). This makes it reasonably easy to have an entire class sit behind a split point.


  1. 1 Sticky: Overview « GWT / GAE best practices project
  2. 2 Dan Moore! » GWT widgets and code splitting, a match made in heaven
  3. 3 Structuring GWT modules for large applications | Summa Blog

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: