GwtRpcCommLayer - Extending GWT RPC to do more than just basic serialization
Would you like to have unit tests for your server code which exercise the same call sites which are used by your users? Would you like to do batch processing using these same RemoteService APIs, without having to drive a web browser? If so, this article is for you. Jeff McHugh, from Segue Development, has offered to share his work, which builds on the RPC capabilities that ship with GWT.
Extending GWT RPC to do more than just basic serialization
If are you currently working with GWT and require serialization, this article might be a great interest to you. GWT RPC is an API provided by the GWT SDK that makes client/server communication easy to implement and support. I developed GwtRpcCommLayer, a library that sits on top of of GWT RPC, that:
- makes performance-testing (a.k.a. stress-testing) simple to execute without any changes to your existing code
- makes unit-testing possible from within a command-line scripting framework such as Maven or Ant
- makes it very possible to have a “web service” type interface to your backend server, but instead of having to restrict access to just browsers, the very same servlet code can support Java clients implemented in Swing, AWT, or other servlets
This post will explain how GwtRpcCommLayer makes this possible. The entire codebase is available for download and can be used with your GWT projects.
Background
As you already know, the GWT SDK contains a very powerful serialization mechanism that can be easily leveraged to make AJAX RPC calls. This serialization mechanism is called GWT RPC. The API reflects the overall spirit of Java's RPC mechanism, which allows you to call remote objects' methods just as if they were running within the local JVM. In either case, there's no need to handle any of the marshaling/un-marshaling (serialization/de-serialization) details. That is all taken care of by the underlying framework of the API.
What makes GWT RPC so powerful is that it enables Java developers to be more productive, by allowing them to stay grounded with their existing implementation model, i.e. no conversions to and from JSON, XML, CSV. If you already have objects in your model such as UserAccount, LineItem, etc., you don't need to spend the extra effort building a mapping to and from JSON or XML.
Taking advantage of GWT RPC requires no additional libraries outside of the standard GWT SDK, and it is straightforward to use, making it a great solution for serialization.
GwtRpcCommLayer
My extension to GWT RPC takes advantage of the underlying foundation of how GWT RPC is designed, but swaps out the standard GWT “serialization engine” with one that uses simple Object serialization via java.io.ObjectStream.
The design of the code-base is naturally separated into two distinct parts:
- Client side code – code executes within a JVM (replacing the JavaScript Engine), which could be a command-line app, Swing/AWT app, ANT task, intermediate servlet, etc.
- Server side code – code executes on your server, within your servlet container, and fits seamlessly within your existing servlet code
The following requirements were part of the design goals when I started working on the project:
- The syntax of usage should exactly mirror the calls the developer is already using in his/her client side application. Examine the following client-side code:
UserAccount ua = gwtRpcServerImpl.getUserAccount('x@xyz.com');
when using GwtRpcCommLayer on the client side, things should work the same:UserAccount ua = gwtRpcCommLayerServerImpl.getUserAccount('x@xyz.com'); - The footprint on the server side must be as close to invisible as possible. The existing server methods had to be left untouched.
- The developer must be able to utilize this codebase without making any changes to existing servlet code. GwtRpcCommLayer could be added retro-actively, without any need for servlet modification.
All three of these requirements were met, which will hopefully encourage developers to not only try this library out for themselves, but perhaps even contribute to the development of further enhancements and functionality.
How it works
The basic premise of the design follows the same design pattern used by GWT RPC, but instead of using a delimited/ASCII format, I simply swapped that out with Java's standard Object serialization. Since both GWT RPC and Java RPC mandate that Objects intended to be serialized must implement the java.io.Serialization interface, the process was straightforward.
Additionally, I took took advantage of the Java Reflection API which allows for runtime compilation of “proxy” classes. Using reflection, it is possible to shield the developer from any awareness of the “serialization engine”, which satisfied one of my three requirements: purity of syntax.
Below is a very basic diagram of (#1) how the GWT RPC interaction works and (#2) how the GwtRpcCommLayer interaction works. Keep in mind that from the developer's point of view, nothing changes in terms of how the code executes, both on the client and server side.
Server Implementation
In order to take advantage of GwtRpcCommLayer, you can choose one of two options. Both options work well. The choice comes down to style more than anything else.
Option 1
Create an instance of GwtRpcCommLayerServlet in your web.xml file. Specify your own servlet as an initialization parameter:
<servlet> <servlet-name>GwtRpcCommLayerServlet</servlet-name> <servlet-class>gwtrpccommlayer.server.GwtRpcCommLayerServlet</servlet-class> <init-param> <param-name>GwtRpcCommLayerServletImplClass</param-name> <param-value>example.server.GreetingServiceImpl</param-value> </init-param> </servlet>
Choose option 1 if you don't want to alter your servlet classes.
Option 2
Servlet classes which extend com.google.gwt.user.server.rpc.RemoteServiceServlet should instead extend gwtrpccommlayer.server.GwtRpcCommLayerServlet. Since GwtRpcCommLayerServlet itself extends RemoteServiceServlet, all the same methods are available to your servlet and your server code will continue to operate normally. Upon review of the associated source code, you will observe that the main service method has been overridden to provide branch logic to handle both normal GWT RPCs and the newly supported GwtRpcCommLayer RPCs.
Client Implementation
Client implementation is simply a matter of instantiating a “proxy class” and making the same remote service calls your client code would normally make. Here is a simple example:
//STEP 1: create the URL that points to your service/servlet URL url = new URL("http://127.0.0.1:8888/example/greet"); //STEP 2: create an instance of GwtRpcCommLayerClient GwtRpcCommLayerClient client = new GwtRpcCommLayerClient(url); //STEP 3: ask the client for a reference to the client-side proxy of your remote service GreetingService stub = (GreetingService) client.createRemoteServicePojoProxy(GreetingService.class); //STEP 4: any call you execute against this proxy will ultimately execute on your servlet String echoResponse = stub.greetServer("hello world");
As you can see from the above fictitious service, it only takes 3 lines of code to gain access to your remote service and it will behave just as if these were regular GWT RPC calls.
Usages
- Encapsulate the above calls in some more sophisticated threading and you should be able to see how easy it is to create a “stress test” for your server code.
- Encapsulate the above calls into unit tests and you can suddenly perform a client/server unit test for each and every exposed remote method.
- It should also be obvious that this class lends itself to doing backend processing. Perhaps you have the need to do batch processing using a CSV file. Instead of having to develop another interface (or web service), you could instead simply use your existing codebase and existing servers.
- As time advances for a particular project, you might develop the need to expose some of these services to a client other than a browser. Your codebase can easily serve as a starting point for that, hopefully cutting down on your implementation time.