📜 ⬆️ ⬇️

Using the Command Pattern to Organize RPC Calls in GWT

In his last year's performance in the framework of Google I / O, Ray Rayan told the audience how to properly design the architecture of more or less large GWT projects. One of his recommendations is to use the Command pattern (pattern) to orginize RPC services. In this article I will try to briefly highlight this approach using the simplest GWT application as an example. For dispatching RPC calls, the gwt-dispatch GWT-Dispatch library will be used. I just want to warn you that this article is a symbiosis, conceptualization and compilation of several sources ( GWT-Dispatch Getting Started , GWT MVP Example ). Consider it as a quick start guide on how to build GWT applications correctly. All material is designed taking into account the fact that the server implementation of RPC services is also performed in Java.


It is no secret that in the development of smaller applications, design patterns (patterns) are in a hurry. Patterns are a kind of recipe for solving specific case cases. You can start in the study and application of design patterns with the Patterns on Wiki and further delve into the appropriate books, articles, works, etc.
In short, the Command pattern (Command) allows you to perform specific implementations of the Command interface (Action, etc.) through a unified interface.


')
Regarding GWT RPC, the use of this approach will allow you to have one RPC service call interface (dispatcher) and transfer the object of the corresponding action (command) to it.

Connection of necessary libraries


So, to implement RPC interaction in a project using commands, we need to connect additional libraries to the project:

To connect these libraries to the project, simply put the gin-1.0.jar, guice-2.0.jar, guice-servlet-2.0.jar and gwt-dispatch-1.0.0.jar files into WEB-INF / lib and add them to the Build Path project. The configuration of the GWT-module with the connected modules looks like this for me:
<? xml version ="1.0" encoding ="UTF-8" ? > < module rename-to ='rpc_command' > < inherits name ='com.google.gwt.user.User' /> < inherits name ="com.google.gwt.inject.Inject" /> < inherits name ="net.customware.gwt.dispatch.Dispatch" /> < entry-point class ='net.pimgwt.client.RpcCommandEntryPoint' /> < source path ='client' /> </ module > * This source code was highlighted with Source Code Highlighter .
  1. <? xml version ="1.0" encoding ="UTF-8" ? > < module rename-to ='rpc_command' > < inherits name ='com.google.gwt.user.User' /> < inherits name ="com.google.gwt.inject.Inject" /> < inherits name ="net.customware.gwt.dispatch.Dispatch" /> < entry-point class ='net.pimgwt.client.RpcCommandEntryPoint' /> < source path ='client' /> </ module > * This source code was highlighted with Source Code Highlighter .
  2. <? xml version ="1.0" encoding ="UTF-8" ? > < module rename-to ='rpc_command' > < inherits name ='com.google.gwt.user.User' /> < inherits name ="com.google.gwt.inject.Inject" /> < inherits name ="net.customware.gwt.dispatch.Dispatch" /> < entry-point class ='net.pimgwt.client.RpcCommandEntryPoint' /> < source path ='client' /> </ module > * This source code was highlighted with Source Code Highlighter .
  3. <? xml version ="1.0" encoding ="UTF-8" ? > < module rename-to ='rpc_command' > < inherits name ='com.google.gwt.user.User' /> < inherits name ="com.google.gwt.inject.Inject" /> < inherits name ="net.customware.gwt.dispatch.Dispatch" /> < entry-point class ='net.pimgwt.client.RpcCommandEntryPoint' /> < source path ='client' /> </ module > * This source code was highlighted with Source Code Highlighter .
  4. <? xml version ="1.0" encoding ="UTF-8" ? > < module rename-to ='rpc_command' > < inherits name ='com.google.gwt.user.User' /> < inherits name ="com.google.gwt.inject.Inject" /> < inherits name ="net.customware.gwt.dispatch.Dispatch" /> < entry-point class ='net.pimgwt.client.RpcCommandEntryPoint' /> < source path ='client' /> </ module > * This source code was highlighted with Source Code Highlighter .
  5. <? xml version ="1.0" encoding ="UTF-8" ? > < module rename-to ='rpc_command' > < inherits name ='com.google.gwt.user.User' /> < inherits name ="com.google.gwt.inject.Inject" /> < inherits name ="net.customware.gwt.dispatch.Dispatch" /> < entry-point class ='net.pimgwt.client.RpcCommandEntryPoint' /> < source path ='client' /> </ module > * This source code was highlighted with Source Code Highlighter .
  6. <? xml version ="1.0" encoding ="UTF-8" ? > < module rename-to ='rpc_command' > < inherits name ='com.google.gwt.user.User' /> < inherits name ="com.google.gwt.inject.Inject" /> < inherits name ="net.customware.gwt.dispatch.Dispatch" /> < entry-point class ='net.pimgwt.client.RpcCommandEntryPoint' /> < source path ='client' /> </ module > * This source code was highlighted with Source Code Highlighter .
  7. <? xml version ="1.0" encoding ="UTF-8" ? > < module rename-to ='rpc_command' > < inherits name ='com.google.gwt.user.User' /> < inherits name ="com.google.gwt.inject.Inject" /> < inherits name ="net.customware.gwt.dispatch.Dispatch" /> < entry-point class ='net.pimgwt.client.RpcCommandEntryPoint' /> < source path ='client' /> </ module > * This source code was highlighted with Source Code Highlighter .
  8. <? xml version ="1.0" encoding ="UTF-8" ? > < module rename-to ='rpc_command' > < inherits name ='com.google.gwt.user.User' /> < inherits name ="com.google.gwt.inject.Inject" /> < inherits name ="net.customware.gwt.dispatch.Dispatch" /> < entry-point class ='net.pimgwt.client.RpcCommandEntryPoint' /> < source path ='client' /> </ module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < module rename-to ='rpc_command' > < inherits name ='com.google.gwt.user.User' /> < inherits name ="com.google.gwt.inject.Inject" /> < inherits name ="net.customware.gwt.dispatch.Dispatch" /> < entry-point class ='net.pimgwt.client.RpcCommandEntryPoint' /> < source path ='client' /> </ module > * This source code was highlighted with Source Code Highlighter .


Organizing a specific team on the GWT side


I will illustrate the creation of a team with an example of creating one RPC call. Its essence will be simple as a door: send the parameter read from the input field to the server method and get an answer. Everything. Oh yeah, display the response received on the UI. The demo project also contains a call that receives an array of fake DTO objects from the server. His code will not be discussed in this article. If interested, I can provide it further in the comments.
To create an RPC command, you need to create a class that implements the Action interface:
  1. package net.pimgwt.client.rpc;
  2. import net.customware.gwt.dispatch.shared.Action;
  3. @SuppressWarnings ( "serial" )
  4. public class SingleRequestAction implements Action <SingleRequestResult> {
  5. private String param;
  6. public SingleRequestAction () {
  7. }
  8. public SingleRequestAction (String param) {
  9. this .param = param;
  10. }
  11. public String getParam () {
  12. return this .param;
  13. }
  14. }
* This source code was highlighted with Source Code Highlighter .

As you can see, nothing complicated. This command encapsulates a parameter that will be passed to the server. The only interesting point here is an indication that the result of the execution will be an object of the class SingleRequestResult:
  1. package net.pimgwt.client.rpc;
  2. import net.customware.gwt.dispatch.shared.Result;
  3. @SuppressWarnings ( "serial" )
  4. public class SingleRequestResult implements Result {
  5. private String resultMessage;
  6. public SingleRequestResult () {}
  7. public SingleRequestResult (String resultMessage) {
  8. this .resultMessage = resultMessage;
  9. }
  10. public String getResultMessage () {
  11. return this .resultMessage;
  12. }
  13. }
* This source code was highlighted with Source Code Highlighter .

which also encapsulates data that will “arrive” to client code.
At the moment, preparations on the client side are over. It's time to take toasting coffee beans that will work on the server. By the way, our server will work on Google App Engine.

Server implementation of RPC services


The GWT-Dispatch library provides tools for organizing dispatchers for both client and server parts.
Let's start the server-side configuration with the servlet manager that will handle the RPC calls:

  1. package net.pimgwt.server;
  2. import net.customware.gwt.dispatch.server.service.DispatchServiceServlet;
  3. import com.google.inject.servlet.ServletModule;
  4. public class DispatcherServletModule extends ServletModule {
  5. @Override
  6. protected void configureServlets () {
  7. serve ( "/ rpc_command / dispatch" ) .with (DispatchServiceServlet. class );
  8. }
  9. }
* This source code was highlighted with Source Code Highlighter .

The DispatcherServletModule class is ServletModule . It rewrites the parent method configureServlets() , which matches the RPC-URL of the servlet manager implementation that is provided by GWT-Dispatch. By default, the URL that will be listened to by the dispatcher is based on the application_name / dispatch scheme.
Now we implement a handler that will be tied to the command declared on the client side ( SingleRequestAction command):
  1. package net.pimgwt.server;
  2. import net.customware.gwt.dispatch.server.ActionHandler;
  3. import net.customware.gwt.dispatch.server.ExecutionContext;
  4. import net.customware.gwt.dispatch.shared.ActionException;
  5. import net.pimgwt.client.rpc.SingleRequestAction;
  6. import net.pimgwt.client.rpc.SingleRequestResult;
  7. public class SingleRequestHandler implements ActionHandler <SingleRequestAction, SingleRequestResult> {
  8. @Override
  9. public SingleRequestResult execute (SingleRequestAction action, ExecutionContext
  10. context) throws ActionException {
  11. return new SingleRequestResult ( "You are entered:" + action.getParam ());
  12. }
  13. @Override
  14. public Class <SingleRequestAction> getActionType () {
  15. return SingleRequestAction. class ;
  16. }
  17. @Override
  18. public void rollback (SingleRequestAction action, SingleRequestResult result,
  19. ExecutionContext context) throws ActionException {}
  20. }
* This source code was highlighted with Source Code Highlighter .

The command handler implements a generic interface, the parameterization of which indicates the command and its result. In this case, these are SingleRequestAction and SingleRequestResult respectively. The ActionHandler interface also ActionHandler the implementation class to provide the execute(), getActionType() and rollback() methods, whose names speak for themselves. In the code above, for such a simple command as the SingleRequestAction the rollback action in case of failure is simply left blank. Nothing to roll back.
The result of the execute() method execution is a SingleRequestResult object, to which we simply write the text of the response that will be passed to the caller (client) side.
Well, the getActionType() method should return a reference to the class of the command to which the handler is attached. This is necessary so that the dispatcher can correctly call the necessary handler, and not some other one.
In addition to directly dispatching and providing the Action and Result interfaces, the GWT-Dispatch library also provides integration with Google Guice. This integration allows you to register command handlers in a Guice context:
  1. package net.pimgwt.server;
  2. import net.customware.gwt.dispatch.server.guice.ActionHandlerModule;
  3. public class RpcCommandHandlerModule extends ActionHandlerModule {
  4. @Override
  5. protected void configureHandlers () {
  6. bindHandler (SingleRequestHandler. class );
  7. //. . .
  8. }
  9. }
* This source code was highlighted with Source Code Highlighter .

Let's tie it all together using the GuiceServletContextListener class, which will “listen” to what is happening from the outside and react when the / rpc_command / dispatch request comes from the client and start the handler of the corresponding command:
  1. package net.pimgwt.server;
  2. import com.google.inject.Guice;
  3. import com.google.inject.Injector;
  4. import com.google.inject.servlet.GuiceServletContextListener;
  5. public class RpcCommandGuiceConfig extends GuiceServletContextListener {
  6. @Override
  7. protected Injector getInjector () {
  8. return Guice.createInjector ( new RpcCommandHandlerModule (), new DispatcherServletModule ());
  9. }
  10. }
* This source code was highlighted with Source Code Highlighter .

The GuiceServletContextListener class GuiceServletContextListener provided by the Guice framework as a means of integrating it with Java Servlets. The code above will perform all the necessary injections (injects) in the right places. Thus, we have the GWT-Dispatch and Guice integration chain with Servlets closed.
The last step that is needed in order for all of this to play as a single ensemble is to specify the necessary listener in the web.xml file and the corresponding query filter:
  1. <? xml version = "1.0" encoding = "UTF-8" ? >
  2. <! Doctype web-app
  3. PUBLIC "- // Sun Microsystems, Inc.//DTD Web Application 2.3 // EN"
  4. "http://java.sun.com/dtd/web-app_2_3.dtd" >
  5. < web-app >
  6. < filter >
  7. < filter-name > guiceFilter </ filter-name >
  8. < filter-class > com.google.inject.servlet.GuiceFilter </ filter-class >
  9. </ filter >
  10. < filter-mapping >
  11. < filter-name > guiceFilter </ filter-name >
  12. < url-pattern > / * </ url-pattern >
  13. </ filter-mapping >
  14. < listener >
  15. < listener-class > net.pimgwt.server.RpcCommandGuiceConfig </ listener-class >
  16. </ listener >
  17. < welcome-file-list >
  18. < welcome-file > index.html </ welcome-file >
  19. </ welcome-file-list >
  20. </ web-app >
* This source code was highlighted with Source Code Highlighter .
GuiceFilter configured to filter all requests that fall on the server side of the client. Naturally, in the url-param-instructions, you can specify your URL pattern to listen. How to do this I will not say, these are obvious things and they are not related to the subject matter.
The server part is ready. It now remains to associate the RPC calls with the client code with the dispatcher.

Dispatching commands in GWT code


For calls to RPC commands in the GWT code, the interface is DispatchAsync . You can implement this interface as you wish, for example, as a dispatcher who can cache the results obtained earlier. For the demo project, I chose the “boxed” implementation of DefaultDispatchAsync again from the GWT-Dispatch package.
Below I will only give you a click handler on a button that initiates an RPC call through the specified interface and displays the result received from the server side:
  1. //. . .
  2. private DispatchAsync rpcDispatcher = new DefaultDispatchAsync ();
  3. //. . .
  4. @UiField Button singleValueTestButton;
  5. //. . .
  6. @UiHandler ( "singleValueTestButton" )
  7. public void singleValueButtonClicked (ClickEvent event ) {
  8. responseLable.setText ( "" );
  9. rpcDispatcher.execute ( new SingleRequestAction (paramTextbox.getText ()), new
  10. AsyncCallback <SingleRequestResult> () {
  11. @Override
  12. public void onFailure (Throwable caught) {
  13. responseLable.setText ( "Error occured:" +
  14. caught.getMessage ());
  15. }
  16. @Override
  17. public void onSuccess (SingleRequestResult result) {
  18. responseLable.setText (result.getResultMessage ());
  19. }
  20. });
  21. }
* This source code was highlighted with Source Code Highlighter .

The main point here is that we give the dispatcher an initialized command to send it to the server. In the callback from the received SingleRequestResponse, the result is simply retrieved: responseLable.setText(result.getResultMessage());

Everything is written, implemented, configured and even works!

Demo project


The screenshot below shows the structure of the demo project in the Project Packages panel.

image hosting

If you look at it, you can see that another RPC team, MultiRequestAction , is implemented in the project. The result of its execution is a MultiRequestResult , which in turn contains a list of DummyDTO objects, which is filled in a loop in the server handler of this command.
Project for live-viewing is available RPC Command Demo Project

Instead of conclusion


The described approach of RPC interaction does not diminish the role of simple RPC calls, which were discussed a little in the article Authorization via the User Service in GWT applications . In some cases, when you have one, maximum two references to the server side in your project, it makes little sense to make a garden out of Action, Result, some Guice, and others like them, because it only complicates the code. On the other hand, the use of “correct” practices for building an OOP code increases its structurization, readability and _ add your benefit_.
Moreover, I know several projects on GWT, which generally do not contain Java on the server side. So with such a server implementation, it is natural to use some common messaging format, for example, JSON or XML. But that's another story…

I am waiting for constructive criticism, wishes and, of course, questions!
Thank.

Source: https://habr.com/ru/post/94844/


All Articles