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

5

8

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

Theogony answered 21/7, 2011 at 15:32 Comment(0)
K
4

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.

Kirkwall answered 24/7, 2011 at 21:14 Comment(0)
T
3

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

Theogony answered 5/9, 2011 at 15:2 Comment(1)
If you are interested, I just made a patch on the GWT-RPC generator so that only one big type map is built for all the application services. For my customer, this decreased the size of the generated js from 42Mo to 19 Mo (they use a lot of little RPC service interfaces, with generic Serializable parameters). I am putting a flag so that you can engage the patch or not depending on your application. If you are interested let me know, I can produce a patch on the official gwt git repoInfinitive
A
1

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.

Abortion answered 21/7, 2011 at 17:4 Comment(3)
unfortunatelly I don't have RPC type explosion problem, what I have is a huge app, more than 250000 lines on the client side and growing, and most of the generated code, that now are 13 Mb of JS came from Service TypeSerializers, according to compile report. But I understand your point, deferred binding will not do the trick, so I need something else, maybe leave RPC and start to use JSON could resolve the issue. I need to do more research on this. Thanks for your answer! DanielTheogony
I think jusio have a point. Java LOC on the client side can have an impact on javascript size but that is a different issue. As for the TypeSerializers, can you show us some of the duplication you are seeing in compile report?Hale
@Daniel Adrison 13 mb is really big number. Try to run compiler with -gen option and check exactly what TypeSerializer contains. How big is your DTO layer ? E.g. how many classes you are sending via GWT-RPC? Also it is possible that you are using some DTO class, which is used as super-class for another 1000 objects. Basically it would be the same RPC type explosion, but without warning=) Anyway I'm strongly recommend to see the source of generated TypeSerializers.Abortion
L
1

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.

Laskowski answered 14/2, 2013 at 13:46 Comment(0)
H
0

As far as I understand, the GWT code generation is supposed to supply concrete implementations of an interface. This implementation is then transformed into javascript for specific permutations.

Your sample, on the other hand, is replacing one interface with the other. If you see it from GWT compiler's eyes, perhaps you will see the problem with this configuration.

Suppose, you are the GWT compiler, and you see following line in client side code that you are converting into JavaScript

AccountingServiceAsync accountingServiceAsync = (AccountingServiceAsync) GWT.create(AccountingService.class);
accountingServiceAsync.recordTransaction(transaction,callback);

So you need to find out what should happen, at line 2. Specifically, you need to know where to find implementation of accountingServiceAsync.recordTransaction(). So you go looking into all your configuration to find if there is a rule specifying which implementation class should be used for AccountingService (not Async). But sadly you don't find any. But then you notice that AccountingService is also a RemoteService. So you dive into your configuration again. And, aha, there it is, a rule specifying that you can generate RemoteService implementations with ServiceInterfaceProxyGenerator. You happily hand over the task of providing an implementation of AccountingService to ServiceInterfaceProxyGenerator.

But suppose instead of this happy ending, your configuration tells you that AccountingService can be replaced with GenericService, and you say, "hey cool, bring it on". But just then you find out that GenericService is also an interface. Clearly, you'll be turned off, saying "now, what am I going to with another interface, all I needed was an implementation of AccountingService". At this point you'd want to get even with the programmer by throwing a cryptic error at him.

So, far all this explains why your solution (theoretically) won't work . As far as your actual concern of bloated javascript, I am amazed that this problem even exists given the amount of effort that GWT folks put in optimizing the compiled JavaScript. How did you tested your compiled output for duplication?

Hale answered 21/7, 2011 at 16:58 Comment(1)
Yes Tahir I miss understand the use of deferred binding, now I know, but I still have the issue with this huge TypeSerializers code generated. I need to do more research on this. Thanks for your answerTheogony

© 2022 - 2024 — McMap. All rights reserved.