Server Side Only Code in GWT
From Zanecorpwiki
Contents |
A Partial Solution
First off, there is no good solution to embedding server only code in GWT-compiled objects. There should be and you'll probably need to spend a few hours convincing yourself of this fact because it really is quite incredible, but--as of 2.0--it's true.
What you need to do is first put all the server code into a static class that takes the real object (or data from the real object) as an argument. You then create two implementations of this class:
package com.zanecorp.example.server;
public class Foo {
static void doSomething(ModelObject obj) {
obj.cosineValue = Math.cos(obj.angle);
}
}
and
package com.zanecorp.example.server;
public class Foo {
static void doSomething(ModelObject obj) {
// nothing to do
}
}
You could have a common interface or something, but there's no need. What you end up doing is getting the server code to compile with one class, and the client code with another. This may seem strange, but remember that GWT turns all your Java into JavaScript, so there's no class collision or anything like you might think. You're just providing a placeholder so the limited GWT compiler doesn't choke.
You'll probably want to segregate these static libraries in their own package because you'll need special handling for the client side of things. You end up with a pair of directories like:
/your/project/home/src/com/zanecorp/example/server /your/project/home/src/com/zanecorp/example/cserver/com/zanecorp/example/server
I put client side slugs implementations in a "cserver" package for "client version of server libs". In the normal, Java compile side of things, you'll want to include the 'server' package and exclude the 'cserver' directory. Assuming the GWT module file in the example directory, you add a line like:
<super-source path="cserver" />
This tells the java compiler to treat the 'cserver' directory as a "root" directory. So if yo have code like:
package com.zanecorp.example.model;
import com.zanecorp.example.server.Foo;
public class ModelObject {
...
public void someMethodOnlyUsedOnServer() {
Foo.doSomething(this);
}
...
}
then the GWT compiler will look under 'cserver' and find the slug implementation 'com.zanecorp.example.server.Foo'. In Java land (on the server side) you'll compile with the "real" Foo.
Viola, a somewhat hacked but workable solution.
Alternatives?
It wouldn't be too hard to write your own pre-processor, and some have done this... however I haven't found anyone sharing the code. In fact, as I write this and as far as I can tell, this is the only step by step guide on the Internet.
Why is this So Hard?
At it's core, GWT takes Java code and compiles it into JavaScript. Because of this, there's limitations on the what classes can be used. For instance, neither java.lang.Math nor java.util.TimeZone can be used in client side code.[notes 1]
This can create real problems when, for instance, you want to use your data/model objects on the client and the server and you'd like to to use log4j in the objects, or you need to include logic to deal with time zones. Even though this code may only (and can only be) executed on the server, you can't have it in the same class because not only does GWT disallow arbitrary classes,[notes 2] but there's no support for "server side" methods.
What most developer's really want (and have been asking for) is something like:
@ServerOnly
public void aServerMethod() {...}
GWT apologists try to say that either this would be hard or is unnecessary. As to whether or not it would be hard... well, I don't know the guts of GWT, but I've written lots of compilers and I can't think of any reason why it would be hard. In fact, it would certainly be pretty simple. I'm sure there are some edge cases, but that's fine. If it works 95% of the time and can be made to fail gracefully in the other cases, then there's much gained and nothing lost.
From what I've read on the forums, it seems the real issue is a strong belief that the feature is unnecessary. Apparently, the Google recommended "best practice" is to architect your entire object model around the arbitrary limitations of the GWT compiler by:
- creating parallel client and server code bases
- creating function-style libs to process the limited objects on server side[notes 3]
- create data transfer objects (DTOs)
I'm very skeptical for the simple fact that in most any other context, doing any of these things would be considered not only poor design, but directly contrary to all other modern best practices. The need is so clear, and the current "solutions" so bad, I really just can't understand why things are the way they are or why there's so many defenders willing to pretend that such bad ideas are a good thing.[notes 4]
Why is this Important?
You may very well have a significant GWT app and never run into this problem. In my case, two factors combined to create a need for this.
First, GWT (and with some reason) has very poor time zone support on the client side. Second, we need to do a lot of time zone reconciliation.
We already had a 'makeSerializeSafe' method which converted Hibernate[notes 5] list and map classes into something GWT could deal with. This was the perfect place to put the time zone code so that the client would get the TZ information already figured out. However, the java.util.TimeZone class is not handled by GWT, so we applied the above trick to isolate and swap the code.
Notes
- ↑ And the GWT TimeZone util is not even a poor substitute.
- ↑ The scope of classes you can</me> use is relatively narrow given what Java developers are used to. Some of this makes perfectly good sense. You don't want the client to go off spawning sockets or things like that, but math, calendar, time zones, and the entire reflection API are out. It's entirely possible that the scope will expand in the future--as far as I know, there's nothing fundamental about the current limitations--but for the time being, you have to tread very lightly.</li>
- ↑ The static lib approach is probably the best of the worst and the one I describe in the first part of the article.</li>
- ↑ Not that any of the ideas are always bad, but in the context of Java, OOP, abstracted services, the idea that you should bend app code around deficient infrastructure when the infrastructure could easily be fixed is a bit mind boggling. The project I'm currently working on had DTOs, which I've cleaned out because they were a huge maintenance problem.</li>
- ↑ I'm not a fan of Hibernate. I see the advantages, but also has a lot of drawbacks. But it's what was inherited.</li></ol>


