Updated 2011-02-10: I was wrong about optimistic locking. It was there in early versions of RequestFactory but never shipped in GA releases.
RequestFactory was already available in GWT 2.1 but really becomes usable starting with GWT 2.1.1.
RequestFactory is "an alternative to GWT-RPC for creating data-oriented services." It's been initially designed for CRUD on entities (domain objects with an identifier and version), but as of GWT 2.1.1 can actually also be used as a general-purpose RPC mechanism.
The Basics
With RequestFactory, you have a set of domain objects and services on the server-side, and proxy and stub interfaces on the client-side, using annotations and naming conventions to map ones to the others. Note that a single server-side object can have several different mappings on the client-side.
Whichever layout you have on the server-side, RequestFactory clearly separates domain objects (that only have properties) from services (methods acting on those objects) on the client-side: there's not necessarily a 1:1 mapping between your client-side proxies and service stub interfaces and your server-side domain objects and services.
As you could have guess from the above, RequestFactory makes extensive use of code generation: all you have on your client code are interfaces, whose implementations will entirely be generated when compiling your application from Java to JavaScript.
Domain objects
There are two kind of domain objects: entities and value objects. The difference is that entities have an identifier (and RequestFactory also mandates that they have a version, more on this later), and then RequestFactory can be used to update an existing object; while value objects are just that: values with properties, with no particular identity. Each one maps to a distinct base interface on the client-side: EntityProxy
and ValueProxy
, respectively. Apart from that, all a (client-side) proxy is allowed to have are property accessors (getters and setters).
The crux of RequestFactory, compared to classical RPC, are entities. On the client-side, RequestFactory will monitor your calls to property setters and only send a diff to the server, where the setters will be re-applied. This is why having an identifier and version are so important: the identifier allows RequestFactory to load the object so it can apply the diff to it, and the version is used to detect changes and fire update events (see below). When working with objects having many properties, and/or with many such objecs, this can significantly reduce the amount of data that goes over the wire (which also means RequestFactory is well-suite for mobile devices), while on the other hand, value objects are always sent with all their properties. Last, but not least, events will be dispatched on the client-side each time a domain object is persisted, updated or deleted on the server (it obviously only concerns entities, not value objects, and only those that have been sent in or returned from a given request).
Services
Services are methods. A method can take as argument and/or return domain objects (entities or value objects) or simple values. Most common such methods are those that implement CRUD on entities, but if you only use value objects and simple values you have a general-purpose RPC mechanism.
RequestFactory supports many use cases, as a service method can be (on the server-side) either a static method, an instance method on a service object, or an instance method on a domain object.
Putting it all together
Keep in mind that RequestFactory makes use of code generation, so you'll always start (in your client code) by GWT.create()
ing a class, or rather in this case, an interface. This interface has to extend RequestFactory
, and define no-args methods that return service stubs. Those method are factories: each call will return a new instance (hence the name of the interface: RequestFactory
).
A service stub is an interface extending RequestContext
and defining service methods. The interface has to be annotated with @Service
or @ServiceName
to map it to a server-side class. Those service methods have the same signature as their server-side counter-part, with the exception of the return type: it will be either Request<T>
if the method lives in the server-side class referenced by the @Service
/@ServiceName
annotation, or an InstanceRequest<P,T>
if the method is on a domain object (of the type mapped toP
on the server-side). In any case, the T
type is the actual return type of the method. If you have instance methods on the server-side service object, you have to provide a ServiceLocator
class in the @Service
/@ServiceName
annotation; RequestFactory will instantiate that class to then ask it for an intance of the service class; otherwise, all methods declared as Request<T>
will have to be static methods on the server-side class. As for the difference between @Service
and @ServiceName
, the former uses class literals that have to be available at compile-time while the latter uses class names; if you use the @Service
interface, the RequestFactoryGenerator
will automatically validate that the declared client-side methods actually map to existing server-side methods.
The RequestFactoryGenerator
has found the service stub interfaces by examining the methods of the RequestFactory
root interface, and it'll similarly find the domain objects' proxy interfaces looking at the arguments and return types of the service methods.
A proxy is an interface that extends either EntityProxy
for an entity, or ValueProxy
for a value object, and define accessors for the object's properties (you don't have to provide a getter/setter pair for each property, you can have a getter without a setter; you however cannot have a setter without an associated getter). The interface has to be annotated with @ProxyFor
or @ProxyForName
to map it to a server-side class (and similarly, the RequestFactoryGenerator
will validate at compile-time that declared property accessors actually map to existing server-side peers if you use the @ProxyFor
annotation, but won't if you use the @ProxyForName
).
For entities, RequestFactory will have to load them by their identifier (so it can apply the diffs sent from the client before landing them to the service methods). To do so, it will either ask a Locator
(that it will first instantiate, of course) or fallback to using a static method named findXxx
(where Xxx
is the name of the entity class). The Locator
will also provide the entity's identifier and version, defaulting getId
and getVersion
accessors respectively.
All those interfaces have to be available to your client code, and also compiled and deployed to your server, as RequestFactory will need the @Service
, @ProxyFor
et al. at runtime (also note that the validation that is performed at compile-time if you use @Service and @ProxyFor
will be done at runtime with all four annotations). For this reason, they're often placed in a sub-package called shared.
OK, but how to use it now?
The first thing to do is to declare a servlet mapping for the RequestFactoryServlet
at path /gwtRequest
(this is the default, it can be changed). That's all you have to do on the server side.
On the client side, you'll start by GWT.create()
ing your RequestFactory
root interface, and then immediately initialize()
it before you can use it. The initialize()
method expects an EventBus
where the EntityProxyChange
events will be dispatched, and an optional RequestTransport
. If the RequestTransport
is omitted, a DefaultTransport
will be used, with the default servlet path of GWT.getHostPageBaseURL() + "gwtServlet"
(in other words, this is where you have a chance of changing those default values).
Next you'll create a RequestContext
(service stub) by calling one of the methods you defined on the RequestFactory
root interface. As for the RequestFactory
interface, the name RequestContext
is not innocent: a RequestContext
is a kind of builder for your request, this is where you'll queue entity property setters and service method calls. It represents the context in which things are done before you send them all in one bach request to the server.
On that context, as I said above, you'll be able to queue entity property setter calls. For that, you first have to edit()
a proxy, as these are immutable by default (this holds true for value objects too, even though their property setters are not monitored like entities' ones). A proxy can only be edited by one RequestContext
at a time; it however doesn't matter how many time you call edit()
on the same proxy. Note that edit()
doesn't update the given proxy in-place, it instead returns a new object. If you want to create a new proxy instead of editing an existing one, you can use the RequestContext
's create()
method. The returned object is mutable so you don't have to edit()
it before you modify it.
You'll also be able to queue service method calls. To do that, you'll simply call the method on the service stub, passing the appropriate arguments. If you're calling an InstanceRequest<P,T>
, you'll also have to call using() on the returned InstanceRequest
to provide the object instance on which to call the method. You can further refine the invocation using the with()
method which controls which properties of the returned entities will be sent back to the client (this helps reduce the response payload size), and the to()
method where you'll give a Receiver<T>
to actually receive the method's returned value.
When you're done setting up your RequestContext
, you'll fire()
it to actually send everything to the server. One thing to remember is that whichever the order you called them, operations (property setter calls on entity proxies) will always be processed before invocations (service method calls). This means you can create()
an object, save()
it right away and then starts modifying it; when processed on the server, the object will first be applied its setters, then the save()
method will be called. However, the relative order of operations and invocations is respected (i.e. if you call setFoo
before setBar
, then setFoo
will be applied before setBar on the server too; and similarly for service method calls).
You can pass a Receiver<Void>
to the RequestContext.fire()
method, and it will be called after the Receiver
s for each service method invocation. Note however that its onFailure
will be called only in case of general failure, so in most case only its onSuccess
method will be called, whether the distinct invocations are successful or not.
As a shortcut, you can also call a fire()
directly on a Request
object; and passing a Receiver
to the fire()
method is equivalent to calling to()
and fire()
sequentially. I'd only recommend doing this if you have only a single invocation in your RequestContext
, though.
What's next?
In the following article(s), I'll deal with the changes between GWT 2.1.0 and 2.1.1, how RequestFactory compares to GWT-RPC, JSR 303 validation, integration with the GWT Editor framework, ServiceLayerDecorator
s (or how to tweak the server-side processing, for example to integrate nicely with Guice or Spring) and other advanced techniques.