Errai Best Practices

Version 11

    Errai Best Practices

    This article discusses some best practices/gotchas.

    Consider adopting Errai Workspaces rather than building your own GWT console from scratch

    I know building from scratch is awesome, will get you in the trenches so that you can write articles, make presentations on how cool you are.

     

    Honestly, it is not worth it all the time.  The Errai Workspaces is really cool that it gives you some of the infrastructure for layout, widgets etc that you can just reuse and get off the ground faster. Since Errai Workspaces is an open source project, you should be able to see the source code, feed features/bugs to the project and help it grow.

    It's simple to add new tools to the workspace:

    @LoadTool(name = "Users", group = "Administration")                          (1)
    
    public class UserManagement implements WidgetProvider
    {
      public void provideWidget(final ProvisioningCallback callback) {           (2)
        callback.onSuccess(new UserManagementWidget());
      }
    
      class UserManagementWidget extends LayoutPanel                             (3)
      {
          [...]
      }
    
    }
    
    

    Always declare subscriber in client (widget) before sending a message to the server

    Assume that we are sending a message to the server which will send a response back.

     

    Let us take some widget on the client side:

     

    bus.subscribe("TokenProviderList", new MessageCallback()
    { 
       public void callback(Message message)
       {
           //We got response from server
       }
    });
           
           
    //Send a message to the server       
    MessageBuilder.createMessage()
    .toSubject("TokenProviderService")
    .command( TokenProviderConfigurationCommands.TOKEN_LIST )
    .noErrorHandling().sendNowWith(bus);
    

     

    In this example, we are first subscribing to the bus with subject called as "TokenProviderList".  After subscribing, we are sending a message to the server (service is TokenProviderService).

     

    This order is important because you will start seeing there is no subscribers for "TokenProviderList" from the errai bus.

     

    To be complete, let me show the snippets from TokenProviderService on the server side.

     

    @Service
    @RequireRoles({"admin"})
    public class TokenProviderService implements MessageCallback
    {
    private MessageBus bus; 
       
       @Inject
       public TokenProviderService( MessageBus bus )
       {
          this.bus = bus;
       }
       
    
       public void callback(Message message)
       { 
          String commandType = message.getCommandType();
          
          if( commandType == null )
          {
             return;
          }
          String resultStr = "SUCCESS";
    
          TokenProviderConfigurationHandler handler = new TokenProviderConfigurationHandler();
          
          switch( TokenProviderConfigurationCommands.valueOf( commandType ) )
          {
             case TOKEN_ADD:
                TokenProvider tokenProvider = message.get( TokenProvider.class , TokenProviderConfigurationParts.TokenProviderObj );
                trace( tokenProvider ); 
                try
                {
                   handler.persist( tokenProvider );
                   message.set("RESULT",  "SUCCESS" );
                }
                catch (Exception e)
                {
                   log.error( "Unable to persist TokenProvider" ); 
                   resultStr = "FAILURE";
                }
                updateClient( message, resultStr );
                break;
                
             case TOKEN_DELETE:
                String providerClass = message.get( String.class, TokenProviderConfigurationParts.ProviderClass ); 
                Set<TokenProvider> tokenProviderSet;
                try
                {
                   tokenProviderSet = handler.getTokenProviders();
                }
                catch ( Exception e1)
                {
                   log.error( "Unable to get list of token providers", e1 );
                   return;
                }
    
                Iterator<TokenProvider> iter = tokenProviderSet.iterator();
                
                TokenProvider removeThisTokenProvider = null;
                
                while( iter.hasNext() )
                {
                   TokenProvider tp = iter.next();
                   if( tp.getProviderClassName().equals( providerClass ))
                   {
                      removeThisTokenProvider = tp;
                      break;
                   } 
                }
                
                if( removeThisTokenProvider != null )
                {
                   try
                   {
                      handler.remove(removeThisTokenProvider);
                   }
                   catch ( Exception e )
                   {
                      log.error( "Unable to remove TokenProvider", e); 
                   } 
                }
                break;
                
             case TOKEN_UPDATE:
                tokenProvider = message.get( TokenProvider.class , TokenProviderConfigurationParts.TokenProviderObj );
                trace( tokenProvider );  
                try
                {
                   handler.persist( tokenProvider ); 
                }
                catch (Exception e)
                {
                   log.error( "Unable to persist TokenProvider", e); 
                   resultStr = "FAILURE";
                }
                updateClient( message, resultStr );
                break; 
                
             case TOKEN_LIST: 
                tokenProviderSet = new HashSet<TokenProvider>();
                try
                {
                   tokenProviderSet = handler.getTokenProviders();
    
                   System.out.println( "TOKEN PROVIDER LIST RECEIVED:" +  tokenProviderSet.size());
                }
                catch ( Exception e)
                {
                   log.error( "Unable to persist TokenProvider", e );  
                } 
                finally
                {
                   MessageBuilder.createConversation(message)
                   .toSubject("TokenProviderList")
                   .signalling()
                   .with( TokenProviderConfigurationParts.TokenProviderList,  tokenProviderSet )
                   .noErrorHandling().sendNowWith( bus );
                }
                break;
          }
       }
    
    }
    

    If you do not really follow the above example, I suggest that you read Errai Documentation (or suggest changes to their team to update parts of the documentation such that you can understand).

     

     

    Choose appropriate messaging facility - with or without parameters

     

    The following are some of the possible ways:

     

    //SEND ONE PARAMETER  ("with" usage)
    MessageBuilder.createMessage().toSubject("ClientBus")
    .command(BusCommands.RemoteSubscribe)
    .with(MessageParts.Subject, service)
    .noErrorHandling().sendNowWith(bus);
    
    // ZERO PARAMETERS
    MessageBuilder.createMessage()
    .toSubject("ClientBus")
    .command(BusCommands.FinishStateSync)
    .noErrorHandling().sendNowWith( bus);
    
    //TWO PARAMETERS
    MessageBuilder.createMessage()
    .toSubject(remoteMonitorSubject)
    .command(MonitorCommands.SubscribeEvent)
    .with(MonitorParts.Time, System.currentTimeMillis())
    .with(MonitorParts.MessageContent, subject)
    .noErrorHandling().sendNowWith( bus );

     

    Choose Serialized Objects Wisely

    "Just because you can does not mean you should"

     

    Assume you have a UserManagement feature where you send User objects from client to server. You can do that provided the User object is serializable.

     

    There can be few operations associated with User.  1) Create 2) Update 3) Delete 4) Read.

     

    In the Create and Read operations, you will need to transmit the entire User object.  But for Update and Delete, you can send in the user identifier and just the fields that have changed.  The server clearly can identify the user based on the user id and its knows the fields.

    Your serialized objects should have equals and hashcode implementation

     

    When you add serialized objects to Java Collections, there can be duplication of objects if these methods are not properly implemented. Why not just use the IDE feature to implement these methods?

     

    Use the Development Mode Window to see the Generated Marshaller/Unmarshaller code for serialized objects

     

    Please see the following image for an example:

    ErraiGeneratedCode.png

    Do not try to develop the client and the server code simultaneously

     

    GWT build/debug etc takes a long time.  It is wise to first develop the server code first. Unit test the server side code thoroughly. Once the server components are ready, build the client components as necessary. This will make you very productive. Iterations on this will be necessary but remember the mantra : "First server side, unit test it and then the client components".

    Choose UI Widgets from GWT Mosaic Project

    They are very user friendly and provide a rich experience.  But sometime they can be buggy in which case you may have to fallback on the GWT core components.  One bug that bothered me was the inability to display a multi selection list box (http://code.google.com/p/gwt-mosaic/issues/detail?id=68).

     

    So always try to scan the bugs list for the appropriate project to see if you are affected.

    Use the ActivityMonitor from Errai to visualize what messages (and their formats/contents) are from the client to the server

    Indispensable tool (thanks to Mike Brock) for debugging subtle issues.  He talks about this tool in his blog post (http://errai-blog.blogspot.com/2010/03/visual-bus-monitor.html ).

     

    Two things you have to do:

    1) Bring in org.jboss.errai/errai-tools as a dependency in your maven project.

    2) Set the JVM parameter "-Derrai.tools.bus_monitor_attach=true"

     

    For me in my pom, I have:

     

    <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>gwt-maven-plugin</artifactId>
                    <version>1.2.0-11137</version>
                    <configuration>
                        <inplace>true</inplace>
                        <logLevel>DEBUG</logLevel>
                        <runTarget>PicketLinkService.html</runTarget>
                        <warSourceDirectory>war</warSourceDirectory>
                        <extraJvmArgs>-Xmx512m -Derrai.tools.bus_monitor_attach=true</extraJvmArgs>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>compile</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>