As has been discussed previously in various venues, I have a mostly-working
port of GWT-RPC that drops the use of legacy GWT APIs like Generators and
JSNI, future-proofing both beyond GWT 2.x, and making it functional in
other runtimes (JVM, Android, TeaVM, etc). It isn't perfect, and isn't
quite complete, but before I work on softening those rough edges, I wanted
to have a conversation here about whether it should remain a part of GWT
going forward, and be migrated to the org.gwtproject package/groupId, and
hosted at github.com/gwtproject.
There are two main reasons I see that would lead us to avoid doing this -
RPC doesn't enjoy the popularity it once did, and the use of my current
draft implementation could be considered to be far enough a way from the
old Generator implementation that it might seem to be a different tool
entirely.
* RPC-based mechanisms in general seem to be out of favor (though with
gRPC gaining popularity, the idea might be coming back), with REST (whether
JSON, Protobuf, or whatever else you like) leading in one direction and
query mechanisms like GraphQL in another. I don't want to debate the pros
and cons of these, or even to break down how "GWT-RPC" can be treated like
a RPC or query mechanism, only to note that if RPC is considered by itself
to be "too old-school", that might be are a reason to do nothing more than
provide upgrade paths *within GWT itself*.
* In order to support being run by annotation processors in a performant
way, my particular implementation all but mandates that the RPC interfaces
and beans exist in their own "shared" project, apart from either client or
server. Arguably this is a best practice to begin with, but it hasn't
actually been required. Additionally, to support external dependencies
(either other projects within your own product, or outside artifacts like
the JRE or Guava that might have serializable types you'd be interested in)
a manifest of some kind is required to point at the classes that should be
considered while compiling. Presently this is supported through annotation
processor arguments, but other mechanisms could be supported as well, but
nothing as simple and obvious as "just assume that all possible subclasses
will work" that GWT2's RPC supports. Reading private members isn't
supported either - it is assumed that either those members will be
non-private, or that getters and setters will be provided.
These two points probably need to be weighed relative to each other as
well: we could decide that as long as the migration path for 90% of
projects out there is simple enough that despite being out of favor, at
least part of this tool should end up in org.gwtproject for now. Or,
perhaps despite its continued popularity, the project is different enough
that it shouldn't be included in org.gwtproject merely due to having its
roots in "classic" GWT-RPC.
--
A quick technical overview - I don't want to get bogged down in this
discussion just yet, just providing a summary to inform the topic at hand
(though I'll be happy to provide more details as it helps to make this
decision).
The project is composed of two annotation processors, and is intended to
support a variety of use cases out of the box:
* old pipe-delimited string format, or TypedArray/ByteBuffer based
encoding (including not writing Strings to the typedarray at all but
leaving them in a plain JS array for cheaper copying between the window and
workers)
* transport tools for page/worker, websockets, and fetch/xhr calls
* interface-based approach to handling streams allowing other
implementations to be added or further specialized, and transport
mechanisms to be built per-platform so interfaces, beans, and generated
serializers can be reused between platforms, easily enabling adding new
platforms, or devising your own stream formats or transport mechanisms.
At runtime, the serialization stream interface is nearly identical to the
old one, with the addition of one additional method when reading from the
stream, to generalize mitigation on a class of attack that GWT-RPC
historically only handled in the servlet. No reflection is used in the
server implementation for de/serializing fields in objects, but it is
instead assumed that either a field serializer has been created by the code
generation mechanism, or a custom field serializer has been provided.
Instead of a .gwt.rpc file on the server to enumerate types that are
serializable and a JSNI map on the client, a single class is generated to
describe this for each platform - server gets one, and client gets another
for example, and these are almost certainly compliments of each other.
Avoiding the JSNI map may pose a small risk at increasing generated code
size, but I've tried to mitigate this by generating specific serializer
types which describe exactly which operations are supported, so that any
pruning step when compiling code can delete as much code as possible (and
support weird cases like only reading a type sent from the server, but both
reading and writing that same type when communicate with a web worker).
The SerializableTypeOracleBuilder remains at the heart of the code
generation mechanism, updated to use TypeMirrors and Elements instead of
GWT's JType hierarchy. Instead of being invoked by the RemoteService code
generator, this is instead triggered by its own annotation, expected to be
found on an interface with helper methods to add various types to an
existing stream, or decode a stream and read expected data. This interface
in turn is expected to be generated by another, higher-level annotation
processor, based on the specific use case (XMLHttpRequest, WebSocket, etc).
The built-in "high level" processor implementation takes an interface full
of asynchronous interface methods and generates boilerplate to serialize
each method's arguments as necessary. Presently, there is one such high
level processor, which relies on each interface it sees to either offer
specifics on a base class to extend, or extend another interface which in
turn provides those specifics - I've planned to add a second ("high-er"
level?) processor, just for the purpose of allowing a synchronous interface
to be provided and implemented on the server, and the async instance
generated automatically.
Potential future work:
* "Opaque serialization" for server-originating objects to sign them and
return them to the server unchanged, for use in eventually implementing the
JPA features (which otherwise have been left out so far due to the easy
attack vector it presents).
* More options for automatic de/serialization, possibly including some
kind of "violator pattern" tool, support for builder/factories,
@ConstructorProperties for final fields, etc.
* Allow disabling polymorphism in whole or in part to save bytes over the
wire (and more easily support non-Java remote endpoints).
* "Allocation-free" (or at least "reduced allocation")
serialization/deserialization, for memory/allocation constrained
situations, allowing bean-like data to be read or written, but handled on
at least one endpoint on a field-by-field basis.
--
So, as I continue to polish this and prepare to push builds for others to
begin trying, I would like to start making packages and groupIds
consistent. If the direction seems to be that it doesn't belong in
gwtproject, that's fine by me, plenty of other popular GWT libraries live
outside the official project, but if we think it might belong here, then to
avoid later churn in downstream projects, I'd like to start adopting the
org.gwtproject namespace right away.
--
You received this message because you are subscribed to the Google Groups "GWT
Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/google-web-toolkit-contributors/ba0b000c-2228-4897-94a0-cc9faa42e60f%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.