Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GWT how can I reduce the size of code serializers for RPC calls

Tags:

gwt

gwt-rpc

I found that the more than 60% of the javaScript code generated by GWT on my application is for RPC serializers. Also I found that serializers are not shared between service interfaces, I mean if I have for example AccountDTO type referenced on 2 rpc service interfaces, I will get 2 serializer classes instead of 1 for the same type. In Order to reduce the size of the compiled code I was thinking that maybe I could use Deferred Binding in order to do a replacement of all the services interfaces I have for one big interface. If that could be possible, maybe then GWTCompiler will produce only one AccountDTO serializer instead of 2.

I'm not sure this is a good idea or if there is a better solution for my problem.

What I was trying to implement was something like this:

// Define new interface that extends all service interfaces
public interface GenericService extends RemoteService,
                    AccountingService,
                    FinancialService,..., { }

public interface GenericServiceAsync extends AccountingServiceAsync,
                         FinancialServiceAsync, ..., { }

// At Application.gwt.xml do:

<module>
...
...
    <replace-with class="com.arballon.gwt.core.client.GenericService">
        <when-this-is class="com.arballon.gwt.core.client.AccountingService>
    </replace-with>
    <replace-with class="com.arballon.gwt.core.client.GenericService">
        <when-this-is class="com.arballon.gwt.core.client.FinancialService>
    </replace-with>
    ...
    ...

But at the moment I was receiving the error:

[ERROR] Errors in 'file:/C:/Users/Daniel/EclipseWorkspace/ADK/src/com/arballon/gwt/core/client/FinancialService.java' [ERROR] Line 31: Rebind result 'com.arballon.gwt.core.client.GenericService' could not be found

Any thoughts about the issue will be appreciated. Regards

Daniel

like image 232
Daniel Ardison Avatar asked Jul 21 '11 15:07

Daniel Ardison


4 Answers

GWT's RPC generation code builds several classes to do its work as you've noted: a *_FieldSerializer for each type that goes over the wire, and a *_Proxy class for the RemoteService async type. That proxy type requires a *_TypeSerializer, which is the root of your problem - for some reason, GWT wires up all of the serialization/deserialization methods in a string->js function map, probably to facilitate fast lookups - but this setup code comes at the cost of lines of code that need to be in the final build. A more optimized approach could have each FieldSerializer have a registration method where it adds its methods to the static map owned by the Proxy - this is plagued, however, but GWT's optimization of attempting to not reference instantiate(), deserialize() and serialize() methods if it doesnt appear they will be called.

Your issue stems from having many types that can be serialized, and from your having attempted to build out RemoteService types that each describe specific units of functionality, but re-use many model types. Admirable goal, especially as it will probably make your server-side code look nicer, but apparently GWT bites you for it.

The solution I attempted to offer you on freenode (as niloc132) was to build a single large RemoteService type, which you named GeneralService, and a matching GeneralServiceAsync, each extending all of the existing rpc service types. My first thought was to use a <replace-with> to tell the generator system that when you want each RemoteService type to replace it with GeneralService, but as Tahir points out, this doesn't make sense - GWT doesn't pass rebind results back into itself to keep doing lookups. Instead, I would suggest that when you want a service async type, do the following:

AccountingServiceAsync service = (AccountingServiceAsync) GWT.create(GeneralService.class)

The rebind result from GeneralService will implement GeneralServiceAsync, which is itself assignable to AccountingServiceAsync. If memory serves, you said that you have static methods/fields that provide these services - change those sites to always create a GeneralServiceAsync instance. As long as you do not invoke GWT.create on any RemoteService subtype but GeneralService, you will limit the number of TypeSerializers to one.

As a side note, the RemoteServiceProxy subtypes are stateless, so ensuring that you create only one instance might make it easier to build consistently, but saves no runtime memory or time, as they are almost certainly compiled out to static methods. The *_TypeSerializer classes do have state however, but there is only one instance of each, so combining all of your RemoteServices might save a very small amount of working memory.

like image 128
Colin Alworth Avatar answered Oct 31 '22 03:10

Colin Alworth


Well, after a pair of roundtrips we finally found a solution to our problem I want to share with in case it could help others. First I have to mention the help of Colin Alworth, without his support this solution wouldn't be possible at all. Also I have to mention that I'm not really proud of the final solution but it works for us and for the moment is the best we have.

What we finally did was, as Colin remarks on last post was replacing the GWT.create of each of our service interfaces to create instead the GenericBigService interface.

So our first patch goes like this:

1) Create GenericBigService interface which extends all Service interfaces we have (at the moment 52 interfaces), and also create its Async brother. we done this thru a phytom script.

So our GenericBigInterface looks like this:

package com.arballon.gwt.core.client;

import com.google.gwt.user.client.rpc.RemoteService;

public interface GenericBigService extends RemoteService,
                                       AccountingService,
                                       ActionClassifierService,
                                       AFIPWebService,
                                       AnalyticalService,
                                       AuthorizationService,
                                       BudgetService,
                                       BusinessUnitService,
                                       CatalogPartService,
                                       CategoryService,
                                       ClientDepositService,
                                       .....
                                       .....
{ }

2) We have an Util inner static class in each Service interface to instanciate the Async instance, in there we replace the GWT.create to create the GenericBigInterface.

One of our Service interfaces so looks like this:

public interface FinancialPeriodBalanceCategoryService extends RemoteService {
    /**
 * Utility class for simplifying access to the instance of async service.
 */
public static class Util {
    private static FinancialPeriodBalanceCategoryServiceAsync instance;
    public static FinancialPeriodBalanceCategoryServiceAsync getInstance() {
        if (instance == null) {
            instance = GWT.create(GenericBigService.class);
((ServiceDefTarget)instance).setServiceEntryPoint(GWT.getModuleBaseURL()+"FinancialPeriodBalanceCategoryService");
        }
        return instance;
    }
}

we have to do the serServiceEntyPoint call in order to maintain our web.xml unmodified.

When we first compiles this it compiles ok, but it doesn't work because at runtime the server call throws an Exception:

IncompatibleRemoteServiceException Blocked attempt to access interface GenericBigService 

, which is not implemented by FinancialPeriodBalanceCategoryService

Well that was absolutelly right we are calling the service with an interface it doesn't implement, and here is when the ugly part cames in. We couldn't found a the moment a better solution we can code, that the one we decided to implement that is:

We replace RPC.java with our own copy and we replace the code like this:

in the decodeRequest method we did:

  if (type != null) {
    /*if (!implementsInterface(type, serviceIntfName)) {
      // The service does not implement the requested interface
      throw new IncompatibleRemoteServiceException(
          "Blocked attempt to access interface '" + serviceIntfName
              + "', which is not implemented by '" + printTypeName(type)
              + "'; this is either misconfiguration or a hack attempt");
    }*/
    if (!implementsInterface(type, serviceIntfName)) {
          if(!serviceIntfName.contains("GenericBigService")){
              throw new IncompatibleRemoteServiceException(
                      "Blocked attempt to access interface '" + serviceIntfName
                          + "', which is not implemented by '" + printTypeName(type)
                          + "'; this is either misconfiguration or a hack attempt");
          }
    }

The benefit of doing this was :

1) we went to take an 1 hour and 20 minutes to compite to take only 20 minutes for 6 permutarions.

2) In devMode all starts to run more quickly. Startup remains more or less the same but execution once it starts goes really well.

3) Reduction in the size of compilation was other not minor interesting result, we reduce the left over segment from 6Mb to 1.2Mb, we reduce the whole compilation of JS size in aprox. 50% to 60%.

We are really happy with GWT-RPC and we don't want to leave it, but typeSerializers was really a problem basically because of the size of the JS that results. With this solution, I know is not very elegant but it works, and it works grate. Thanks again Colin for your help!

Regards Daniel

like image 20
Daniel Ardison Avatar answered Oct 31 '22 03:10

Daniel Ardison


For any GWT-RPC Service, GWt will generate one Proxy, one TypeSerializer. And for each object which possibly can be passed via GWT you will have one FieldSerializer class. And there can be only one FieldSerializer per class. So there is no way you can have two FieldSerializers for one AccountDTO.

Deferred binding rule which you trying to use will not work. For example you have something like this: MyServiceAsync sync = GWT.create(MyService.class);

Deferred binding rules will change it into:

MyServiceAsync sync = new MyServiceAsync_Proxy();

Your rules will actually do something like this:

MyServiceAsync sync = new MyGenericService() ;//not valid since MyGenericService is an interface

So your solution will not work.

Since you are saying that 60% of you application generated code is RPC related stuff, I suspect you have RPC type explosion problem.

Check if GWT doesn't throws any warnings during compilation, or generate stubs for RPC TypeSerializers, most likely you've some very common interface in service.

like image 40
jusio Avatar answered Oct 31 '22 04:10

jusio


If you want to have a nicer solution, why not use a command pattern. that way you only need one GWT service that accepts a Command subtype and returns a Result subtype (you can make it typesafe by using generics).

The nice thing is that you only need to declare one method in one gwt servlet and from there on you can dispatch to any other server side service.

The command pattern can give you a lot of added benefit as well since you have a central point of control to do security checks or allows you to transparently batch requests

You exposure to GWT thus becomes much smaller on the server side.

like image 1
David Nouls Avatar answered Oct 31 '22 03:10

David Nouls