Thursday, March 12, 2009

Relaxing constraints on GWT.create()

Recently I've been playing around with some GWT ideas that really cry out for a more liberal deferred binding system. Currently, GWT imposes the restriction that deferred binding can only happen through the GWT.create() method. There's a couple of problems with this:


  1. Can't narrow type signature for custom library

  2. Can't create a method to decorate the returned result

  3. Can't parameterized or override the bindings at callee site



To illustrate this, I've compiled some motivation examples.

RPC requests signed by OAuth



interface MyService extends RemoteService<MyServiceAsync> { ... }
// example call
OAuth.withSignature(MyService.class).method1(arg1, arg2, asyncCallback);



public class OAuth {
@GwtCreate // method must be static, class parameter must be final
public static <S, T extends RemoteService<S>> S withSignature(final Class<T> service) {
// GWT.create with non-literal ONLY allowed
// if enclosing method is @GwtCreate and
// variable statically resolvable to literal param
S async = GWT.create(service);
((ServiceDefTarget)async).setRequestBuilderCallback(new OAuthRequestBuilderSigner());
return async;
}
}

Currently today, it would look like this:

MyServiceAsync foo = GWT.create(MyService.class);
RequestBuilder rb = foo.method1(arg1, arg2, asyncCallback);
OAuthRequestBuilderSigner.sign(rb);
rb.send();


The tight coupling at the callsite also makes it difficult to swap out implementations easy (like using AuthSub, or one of the other 4 Google Friend Connect auth techniques). An alternative is to make a separate subtype of each with a custom generator, e.g.

MyServiceOAuth extends MyService, GWT.create(MyServiceOAuth.class)

I'd argue that the above gets cumbersome with multiple services and multiple authentication types. I've taken some liberties above by adding a type parameter to RemoteService to make the return type statically resolvable, as well as allowing a global callback mechanism on ServiceDefTarget for RequestBuilder override. Note that type-safety is ensured, one can't call this method with something that is not a RemoteService, and it will be flagged at edit-time in your IDE.

An EasyMock library for Hosted/Web Mode



Subscriber mock = GMock.mock(Subscriber.class);
publisher.add(subscriber);
//...
GMock.replay(mock);

public class GMock {
// selectively override module binding rules ONLY for this method
@GwtCreate(generator=com.gmock.rebind.GMockGenerator.class)
public static <T> T mock(Class<T> toMock) {
return GWT.create(toMock);
}
}


In this method, the mock() method acts like GWT.create() except that it overrides the current binding rules, forcing the specified generator.

GWT Exporter



Exporter.export(Foo.class);

public class Exporter {
@GwtCreate
public static <T extends Exportable> void export(Class<T> exportable) {
ExporterImpl ximpl = GWT.create(exportable);
ximpl.export();
}
}

Note, the type safety, one can't try to Export a non-exportable class. This will be flagged at edit time in the IDE.

GIN/Guice dependency injection



Processor pimpl = Gin.inject(Processor.class, TestProcessorModule.class);

public class Gin {
@GwtCreate(generator = com.google.gin.rebind.GInjectorGenerator)
public static <T, S extends AbstractGinModule>
T inject(Class<T> interf, @GwtCreateParam Class<S> module) {
return GWT.create(interf, module);
}
}

This one is more controversial, but allows GWT.create() to be parameterized by literal metadata that is available to the generator. This is semantically equivalent to what we have today:

@GinModule(TestProcessorModule.class)
interface MyTestInjector extends GInjector {
Processor getProcessor();
}

but without the need to actually write the interface.

Combing compile-time and run-time parameters


Final example, mix-and-match both compile-time variables and run-time variables:


OAuth.withSignature(MyService.class, debugMode ? "/debugService" : "/MyService").method1(arg1, arg2, asyncCallback);

public class OAuth {
@GwtCreate // method must be static, class parameter must be final
public static <S, T extends RemoteService<S>>
S withSignature(final Class<T> service, String endPoint) {
// GWT.create with non-literal ONLY allowed if enclosing method is
// @GwtCreate and variable statically resolvable to literal param
S async = GWT.create(service);
((ServiceDefTarget)async).setRequestBuilderCallback(new OAuthRequestBuilderSigner());
((ServiceDefTarget)async).setServiceEntryPoint(endPoint);
return async;
}
}

No comments: