I have the following Java servlet that performs what I call the "Addition Service":
public class AdditionService extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
// The request will have 2 Integers inside its body that need to be
// added together and returned in the response.
Integer addend = extractAddendFromRequest(request);
Integer augend = extractAugendFromRequest(request);
Integer sum = addend + augend;
PrintWriter writer = response.getWriter();
writer.write(sum);
}
}
I am trying to get GWT's RequestFactory to do the same thing (adding two numbers on the app server and returning the sum as a response) using a ValueProxy
and AdditionService
, and am running into a few issues.
Here's the AdditionRequest
(client tier) which is a value object holding two Integers to be added:
// Please note the "tier" (client, shared, server) I have placed all of my Java classes in
// as you read through the code.
public class com.myapp.client.AdditionRequest {
private Integer addend;
private Integer augend;
public AdditionRequest() {
super();
this.addend = 0;
this.augend = 0;
}
// Getters & setters for addend/augend.
}
Next my proxy (client tier):
@ProxyFor(value=AdditionRequest.class)
public interface com.myapp.client.AdditionRequestProxy extends ValueProxy {
public Integer getAddend();
public Integer getAugend();
public void setAddend(Integer a);
public void setAugend(Integer a);
}
Next my service API (in the shared tier):
@Service(value=DefaultAdditionService.class)
public interface com.myapp.shared.AdditionService extends RequestContext {
Request<Integer> sum(AdditionRequest request);
}
Next my request factory (shared tier):
public class com.myapp.shared.ServiceProvider implements RequestFactory {
public AdditionService getAdditionService() {
return new DefaultAdditionService();
}
// ... but since I'm implementing RequestFactory, there's about a dozen
// other methods GWT is forcing me to implement: find, getEventBus, fire, etc.
// Do I really need to implement all these?
}
Finally where the magic happens (server tier):
public class com.myapp.server.DefaultAdditionService implements AdditionService {
@Override
public Request<Integer> sum(AdditionRequest request) {
Integer sum = request.getAddend() + request.getAugend();
return sum;
}
// And because AdditionService extends RequestContext there's another bunch of
// methods GWT is forcing me to implement here: append, create, isChanged, etc.
// Do I really need to implement all these?
}
Here are my questions:
AdditionService
(in shared) references DefaultAdditionService
, which is on the server, which it shouldn't be doing. Shared types should be able to live both on the client and the server, but not have dependencies on either...ServiceProvider
be a class that implements RequestFactory
, or should it be an interface that extends it? If the latter, where do I define the ServiceProvider
impl, and how do I link it back to all these other classes?ServiceProvider
and DefaultAdditionService
? Do I need to implement all 20+ of these core GWT methods? Or am I using the API incorrectly or not as simply as I could be using it?If you want to use RF as a simple RPC mechanism [*] you can (and you are right: only ValueProxy
s), but you need something more: ServiceLocator
s (i.e., GWT 2.1.1).
With ServiceLocator
you can simply put your service implementation (like your servlet) into a real service instance, instead into an entity object (as you will use only ValueProxy
s, with no static getXyz()
methods) as required by the RF protocol. Note the existence also of Locator
s, used to externalize all those methods from your server-side entities: not needed if you use ValueProxy
everywhere.
A ServiceLocator
looks something like (taken from official docs):
public class DefaultAdditionServiceLocator implements ServiceLocator {
@Override
public Object getInstance(Class<?> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
You need to annotate your DefaultAdditionService
also with a locator param, so RF knows on what to rely when it comes to dispatch your request to your service. Something like:
@Service(value = DefaultAdditionService.class, locator = DefaultAdditionServiceLocator.class)
public interface com.myapp.shared.AdditionService extends RequestContext {
// Note here, you need to use the proxy type of your AdditionRequest.
Request<Integer> sum(AdditionRequestProxy request);
}
Your service will then be the simplest possible thing on Earth (no need to extend/implement anything RF-related):
public class com.myapp.server.DefaultAdditionService {
// The server-side AdditionRequest type.
public Integer sum(AdditionRequest request) {
Integer sum = request.getAddend() + request.getAugend();
return sum;
}
}
If you mispell sum()
or you do not implement a method declared in your RequestContext
you will get an error.
To instantiate RequestContext
s you need to extend the RequestFactory
interface, with a public factory method for com.myapp.shared.AdditionService
. Something like:
public interface AdditionServiceRequestFactory extends RequestFactory {
public com.myapp.shared.AdditionService createAdditionServiceRequestContext();
}
All your client calls will start from this. See the docs, if not already.
Now, RF works by totally separating the objects your want to pass from client (using EntityProxy
and ValueProxy
) and server (the real objects, either Entity
values or simple DTO
classes). You will use proxy types (i.e., interfaces whom implementations are automatically generated) everywhere in client/shared tier, and you use the relative domain object (the one referenced with @ProxyFor
) only on server side. RF will take care of the rest. So your AdditionRequest
will be on your server side, while AdditionRequestProxy
will be on your client side (see the note in the RequestContext
). Also note that, if you simply use primitive/boxed types as your RequestContext
params or return types, you will not even need to create ValueProxy
s at all, as they are default transportable.
The last bit you need, is to wire the RequestFactoryServlet
on your web.xml
. See the docs here. Note that you can extend it if you want to, say, play around with custom ExceptionHandler
s or ServiceLayerDecorator
s, but you don't need to.
Speaking about where to put everything:
Locator
s, ServiceLocator
s, service instances, domain objects, and RequestFactoryServlet
extensions, will be on your server-side;RequestContext
, RequestFactory
extensions and all your proxy types will be on the shared-side;RequestFactory
extension and use it to obtain the factory instance for your service requests.All in all... to create a simple RPC mechanism with RF, just:
ServiceLocator
;RequestContext
for your requests (annotated with service and locator values);RequestFactory
extension to return your RequestContext
;RequestContext
(like simple DTO
s), just create client proxy interfaces for them, annotated with @ProxyFor
, and remember where to use each type;Much like that. Ok, I wrote too much and probably forgot something :)
For reference, see:
[*]: In this approach you shift your logic from data-oriented to service-oriented app. You give up using Entity
s, ID
s, version
s and, of course, all the complex diff logic between client and server, when it comes to CRUD
operations.