Transcript Document
Remote Procedure Calls in GWT
•
•
•
•
•
How they are implemented
How to use them effectively
Best practices and design patterns
Future directions and possibilities
Discussion
Introduction
• Your presenter: Rob Jellinghaus
• Contributor to GWT
– Refactored server-side RPC implementation in GWT
1.4
• Architect at SF startup
• http://robjsoftware.org
What is GWT RPC?
• Simple way for your GWT clients to call your
Java server code
• Looks a lot like regular Java procedure calls;
can pass complex objects easily
• Will skim over the basics
– GWT documentation is good
• Won’t address security
– Too large a topic
– Covered elsewhere at this conference
All Abstractions Leak
• Abstractions are great, but hidden details have
a way of surfacing
– Spolsky’s Law: All non-trivial abstractions, to
some degree, are leaky
• GWT RPC is no exception
– At least GWT is upfront about such things!
• Forewarned is forearmed
Asynchrony
• The biggest difference: GWT RPC returns immediately
• Normal procedure call:
Object result = myService.doWork();
doSomethingWith(result);
• GWT RPC:
myService.doWork(myCallback);
… wait a while …
public void onSuccess(Object result) {
doSomethingWith(result);
}
Asynchrony Is Your Friend
• The very definition of AJAX
– Old news to AJAX hackers, but unusual in the Java
world
• If your app isn’t waiting for the server, then
your users aren’t either
• Don’t work around it, embrace it!
– Workarounds are horrible and user-hostile
GWT RPC Lives in the Compiler
• RPC is implemented as a compiler extension
– See Ray Cromwell’s talk on “Generators”
• Enables very highly optimized RPC code
– Compiler looks at your interfaces and object types
– Emits tightly tuned encoding / decoding Javascript
• If GWT can’t compile it, you can’t send it
– Unlike RMI / Java serialization, where you can send
objects for which you don’t have source
Very brief example
• This class:
public class Entry implements com.google.gwt.user.client.rpc.IsSerializable{
private dto.domain.Blog blog;
private java.lang.String body;
…
}
• Gets this generated deserializer (details elided):
function dto_domain_Entry_1FieldSerializer_deserialize(streamReader, instance) {
dto_domain_Entry_1FieldSerializer_setBlog (instance,
com_google_gwt_lang_Cast_dynamicCast__Ljava_lang_Object_2I…
(streamReader.readObject__(), 17));
dto_domain_Entry_1FieldSerializer_setBody… (instance,
streamReader.readString__());
…
}
GWT serialization vs. others
• GWT serialization:
– Fully statically compiled, metadata-guided
– No class hooks
• Java serialization:
– Reflection-based
– readObject, writeObject hooks
• Java persistence serialization:
– Reflection-based, metadata-guided
– Bytecode proxies injected
Server integration: basics
• Simplest technique: RemoteServiceServlet
public interface MyServiceInterface { public Object
doWork(); }
public class MyServiceServlet extends RemoteServiceServlet
implements MyServiceInterface {
public Object doWork() { … }
}
• Works nicely out of the box
• Requires one servlet class per interface you expose
– Doesn’t work well to expose pre-existing components
– Fixed with my refactorings in GWT 1.4
Server integration: Spring
• Several Spring integrations exist
– George Georgovassilis & Rob Hanson’s GWT-SL
server library
– Chris Lee’s one-page integration
• Couples with Spring’s “handler” mechanism for
web requests
• Can expose pure POJOs (GWT-SL), or can
annotate service classes (Chris Lee)
Server integration: Seam
• Seam 2.0 has built-in GWT integration
– GWTService web resource
• Set your endpointURL to be “seam/resource/gwt”
• RPC requests route to a Seam component by interface
name:
@Name("org.jboss.seam.example.remoting.gwt.client.MyService")
public class ServiceImpl implements MyService
{
@WebRemote
public String askIt(String question)
{
return "42. Its the real question that you seek now.";
}…
}
Server integration: Seam/JSF + GWT
• Wrap your GWT module in a JSF component
• Drop it into a JSF page
• Route its RPC to any Seam component:
<!-- routes to the BlogService on the gwtBlog component -->
<bloglist:component id="main2">
<gwt:gwtListener serviceBean="#{gwtBlog}"/>
</bloglist:component>
• Only one problem… broken with Seam 2 at the
moment
– Anyone want to help?
Using GWT RPC Effectively
• We’ve covered the basics
• Now for best practices
– Responsiveness
– Asynchrony
– Serialization interactions
– API design and service orientation
Responsiveness: Balanced RPC
• Messages Want To Be Small
– Small messages are quick to process
– Low encoding & decoding overhead
• But Messages Want To Be Big
– Networks are slow
– Many messages = much exposure to network latency
• Balance your RPC
– Only pull data visible to the user; paginate on server
– Don’t build too many UI elements either
Asynchrony: No More Straight Lines
• Synchronous code might look like this
– (can’t use Java 5 yet in GWT 1.4):
Blog blog = blogService.getBlog();
List entries = blogService.getEntries();
for (int i = 0; i < entries.size(); i++) {
BlogEntry entry = (BlogEntry)entries.get(i);
TreeItem item = new TreeItem(entry.getTitle());
tree.addItem(item);
String body = blogService.formatText(entry.getId());
entry.setBody(body);
}
Fun with Inner Classes
• Anonymous inner classes split up the code inline:
Blog blog = null;
List entries = null;
blogService.getBlog(new AsyncCallback() {
public void onSuccess(Object result) {
blog = (Blog)result;
blogService.getEntries(new AsyncCallback() {
public void onSuccess(Object result) {
entries = (List)result;
}
});
}
});
• But gets very nested and tough to read
Callback Objects
• Break out the callbacks into helpers:
Blog blog = null;
blogService.getBlog(new AsyncCallback() {
public void onSuccess(Object result) {
blog = (Blog)result;
blogService.getBlogEntries(makeEntriesCallback());
}
});
public AsyncCallback makeEntriesCallback() {
return new AsyncCallback() {
public void onSuccess(Object result) {
List entries = (List)result;
…
• Makes the sequence more descriptive
Stateful Callback Objects
• Multiple RPCs in flight at once:
List entries = (List)result;
for (int i = 0; i < entries.size(); i++) {
BlogEntry entry = (BlogEntry)entries.get(I);
TreeItem item = new TreeItem(entry.getTitle()); tree.addItem(item);
blogService.fetchText(entry.getId(), makeEntryCallback(item));
…
}
public AsyncCallback makeEntryCallback(TreeItem item) {
return new AsyncCallback() {
public void onSuccess(Object text) {
item.setText((String)text); } } }
• Each callback knows what to do with its response
• Can extend this pattern to all kinds of sequences
Transactions
• Updates can sometimes require multiple server
calls
• Keep state in your client until commit time
– The less server state the better
– But can’t send too much state at once when
committing
• Can use “chained command” pattern in your
service
– Send a whole sequence of API calls
– Rather like offline sync in Google Gears
When Abstractions Attack
• GWT makes it easy to call your Java backend
• Many Java backends use Hibernate, JPA, EJB3…
• GWT abstraction: serialize object graph to Javascript
– Needs to see all the source
• JPA abstraction: load partial object graph lazily
– Create secret hidden $$CGLIB classes, private collections
• GWT + JPA: KABOOM!
– GWT can’t serialize a lazy persistence proxy or Hibernate
PersistentMap
– Spolsky’s Revenge
DTOs: Back to the Future
• Data transfer objects: intermediate object layer
• Copy your persistent objects into separate DTO
structure
– GWT only sees the DTO objects
– Explicit control over what gets sent
– Can limit features used in DTO objects (avoid
generics, annotations)
– Risk of lots of boilerplate code
Generated DTOs: Less Boilerplate
• Automatically generate DTOs at build time
– Codehaus JAM project: Java source walker
– Compile DTOs into a standalone GWT module
for (JField field : jClass.getFields()) {
String name = field.getSimpleName();
String type = field.getType().getQualifiedName();
if (type.startsWith("java.")) {
// skip over classes that aren't emulated by GWT…
• Reflection can populate DTOs
– Kind of an intermediate “serializer” from persistent objects to
DTO objects
– Hibernate4gwt project allows this (with merging, too)
Rocket Science: Customizing RPC
• GWT ServerSerializationStreamWriter
– Custom GWT class that traverses objects
– Can make subclass with special handling of persistent classes
• Null out lazy collections, load lazy proxy objects
– Risks breaking if GWT changes RPC implementation
public void serializeValue(Object value, Class type) throws
SerializationException {
…
else if (type == java.util.Set.class) {
Set hashSet = new HashSet();
if (value instanceof PersistentSet) {
PersistentSet persSet = (PersistentSet) value;
if (persSet.wasInitialized()){
hashSet.addAll(persSet);
}…
The Trouble with Merging
• Once you send your objects back up, you have to merge
them
• Seam / JPA stateful dogma says this is a weakness of
GWT and other rich clients
– Stateful apps keep persistence context around while user is
interacting; dirty objects tracked for free
• But persistence contexts die badly if commit fails
– Only solution is to abandon all your modified state!
• GWT APIs need to clearly identify what’s changed
– Simplifies the merge problem
– Arguably easier to recover from conflicts
– Scales better, too
DAOs are not APIs
• Tempting to just expose your persistence layer
• Don’t do this!
– Persistence operations are not a good API
• APIs should be intentional
– Should be tuned to application use cases
– Objects exposed should be client-meaningful
• May or may not be your domain objects
– Think services rather than data accesses
• Service-oriented backends are more scalable
Serializable != IsSerializable
• Java serialization is not GWT serialization
– Java serialization expects a very particular contract
– Hardcoded to binary / byte level; lots of existing
readObject, writeObject methods
– Those methods not necessarily compilable by GWT
– Java serialization not efficient in Javascript
• In GWT, “java.lang.Serializable” just means
“OK to send by GWT RPC”
– Does NOT mean “Will use existing readObject /
writeObject implementations”
The Home Stretch
• Cool tricks
• GWT 1.5
• Future possibilities
– Ranging from “sensible” to “wildly ambitious”
• Other interesting systems
• Conclusion
Preserialized objects
• You’ve got a bunch of initialization data
– You want to download it efficiently
– Maximum-speed startup, one round-trip
• iPhone apps, anyone?
• Serialize the data on the server, then save the
bytes
• Blast them out from cache, then deserialize
them explicitly
– In GWT 1.5: get SerializationStreamReader from
client-side RPC proxy
GWT 1.5: Java 5 for the win!
• Plan: full support for enumerated types, generic
collections
• No more @gwt.typeargs
– Typed collections get optimized RPC automatically
• Annotations are OK
– Not yet clear what GWT will use them for
– Compiler should be able to ignore annotations w/o
available source
• So @Entity, @Component, etc. won’t choke GWT
Possibility: RPC metadata
• Support for request/response headers in RPC
messages
• Use cases:
– Servers that use HTTP headers for authorization
– Support for Spring webflow / Seam conversations
• API totally undefined:
– Stateful instantiation of Service? Callback functions?
– Related to RPC cancellation / request control?
– Discuss on GWT Contributors forum
Possibility: JSON-style encoding
• GWT compiler generates current deserializers
– Pretty much the best Javascript functions can do
– But still not as good as JSON
• What if RPC payloads were deserialized by the
browser, as with JSON?
– Could be blazingly fast deserialization
– AND less client-side code
– But lots of issues to work out (see GWTC thread)
• Not high priority for GWT 1.5, but later…?
Possibility: Declarative data binding
• Right now RPC is strictly imperative
• Some modern frameworks (Seam) support
declarative data binding
– Refer to server data objects by name
– UI components bind to specific sub-objects
• Declarative UI support underway for GWT;
perhaps data binding a natural extension?
– Could also integrate some support for offline
synchronization?
– Pagination done “under the hood”?
Possibility: Generalized mappings
• Persistence mapping is very similar to client-server
DTO mapping
– In both cases, you have data spaces containing objects that
must be transferred and correlated
• Why not a unified answer?
– What if persistence mappings could be extended to define data
transfer behaviors to the client?
– And then further, to define client-side update sets and
synchronization behaviors?
– Attack the DTO problem at the framework level!
• See LINQ 2.0 work from Microsoft Research
Other interesting systems
• Good old RMI (Sun)
– Classloader problems much worse than widely known
• Caja (Ben Laurie, Mark Miller, Google)
–
–
–
–
Capability-secure Javascript
Mobile code, with non-broken sandboxes
Influenced by E (asynchronous capability messaging)
(GWT -> Caja scriptlets???)
• LINQ 2.0 (Erik Meijer, Microsoft)
– Automatically restructure sequential client app to be
asynchronous multi-tier app
– Integrate SQL queries, XML mappings, RPC
– MSIL -> Javascript (like GWT for .NET bytecode)
Thanks!
• Hope this was helpful
• Thanks to GWT team & Pearson
• http://robjsoftware.org/gwt2007
– links & further references
• Go forth and create great apps!
– And then post about them on the GWT groups!
– And then start contributing to GWT!!!