Implementing GWT RequestFactory service for non-entity requests

1.3k views Asked by At

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:

  1. Is my "tier" strategy correct? Have I packaged all the types in the correct client/shared/server packages?
    • I don't think my setup is correct because 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...
  2. Should 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?
  3. What about all these methods in 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?
  4. Where does service locator factor in here? How?
1

There are 1 answers

9
Andrea Boscolo On BEST ANSWER

If you want to use RF as a simple RPC mechanism [*] you can (and you are right: only ValueProxys), but you need something more: ServiceLocators (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 ValueProxys, with no static getXyz() methods) as required by the RF protocol. Note the existence also of Locators, 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 RequestContexts 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 ValueProxys 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 ExceptionHandlers or ServiceLayerDecorators, but you don't need to.

Speaking about where to put everything:

  • Locators, ServiceLocators, service instances, domain objects, and RequestFactoryServlet extensions, will be on your server-side;
  • The RequestContext, RequestFactory extensions and all your proxy types will be on the shared-side;
  • client side will initialize the 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:

  • create your service along with ServiceLocator;
  • create a RequestContext for your requests (annotated with service and locator values);
  • create a RequestFactory extension to return your RequestContext;
  • if you want to use more than primitive types in your RequestContext (like simple DTOs), just create client proxy interfaces for them, annotated with @ProxyFor, and remember where to use each type;
  • wire everything.

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 Entitys, IDs, versions and, of course, all the complex diff logic between client and server, when it comes to CRUD operations.