Introduction
I've just posted a wave with a sketch of how I think we should deal with two related issues: data binding (ValueStore), and manipulating JPA-style server side entities from a gwt app with a minimum of DRY (RequestFactory).
Since Wave access isn't yet universal, I've used ExportyBot to publish a (kind of ugly) static version of it. I'm hoping that people who can't edit in wave will add their two cents on the comments on this wiki.
Slice of life with ValueStore and RequestFactory (static)
Apologies in advance: this isn't yet a proper design document, it's more like a core dump.
rjrjr
Moving Parts
Here's an outline of some of the classes and services presumed by the sketch.
JPA-aware script runs across services interfaces and finder methods. It creates
- Shared
- Id and Property classes for every entity (everything with an @Id)
- Builder style Request classes for every service interface (including static finder methods on entity classes)
- In addition to rpc-friendly default constructors, request classes have constructors for client side use which accept valuebox
- Client
- A GIN style RequestFactory interface with getters for every shared Request class
RequestFactoryGenerator
- Generates an implementation for the RequestFactory interface the script made
- Defines and generates a ValueStore as a side effect, and provides access to the canonical instance.
- RequestFactory depends upon ValueStore, but ValueStore can be used without RequestFactory
ValueStoreGenerator,
- GWT.create(ValueBox.class) simply presumes control of every subtype of Id.
- GWT.create(CustomValueBox.class) looks for @Types() annotation which itemizes classes that can be managed, can include both Ids and beans. @AllIds() to claim all implementors of that interface
- Adds validation rules to generated ValueBox based on JSR 303 annotations
ValueStore
- allows subscriptions to
- whole classes of Id,
- specific properties on specific values of Id (for HasValue subscribers)
- subranges of list properties on specific ids (for HasValueList subscribers)
- Allows validation contraints to be declared per property, per id/bean
- spawns DeltaValueStore for uncommitted edits
- sends subscribers errors related to these (for HasErrors subscribers)
- Does not require use of RequestFactory
DataBinder generator
- generated object performs ValueBox subscribe / unsubscribe operations
- UIObjects implement HasValue and HasValueList, and these are the ValueBox subscriber interfaces
ReflectiveDataBinder and MockUiBinder
- for use in JRE tests
- provide GWT.create() bridge s.t. tests are not required to pass in DB and UiB instances
RequestFactoryService (and servlet)
- GWT RPC
- to start with for ease of implementation
- No reason it couldn't be JSON / PB based for restricted implementations (no serializing custom value objects, anything else?)
- Sends Request objects and DeltaValueStore across the wire
- Besides the generated Request objects per service and per entity, there is a standard CRUD service.
Ray,
I tried to reply inline to the wave but it hangs when I click "edit" (It always asks me to reopen the wave)… anyway I quote my feedback here:
I used to work a lot with the “traditional” DataBinding?? frameworks (Swing JSR 295, JGoodies Binding, .NET framework Binding, Flash/Flex/JavaFx??...). If you take a look at all those frameworks, they all have some common scheme and terminology. An object is represented by a set of “Property”, each property provides a “BindingPath??” (object.property.otherProperty) when it is bound. A binding is something that links a source Property with a target Property. A binding can be “Unidirectionnal” (when you modify the source, the target is modified) or “bidirectional”, when you modify the target property, the source is synchronized. During the sync operation, “validators” and “convertors” are applied. Validators are in general something that follows JSR 303 specification (or any fluent interface scheme). Convertors are applied when source property type is not strictly equal to target property type (String -> Date,…).
After reading many times your design doc, I have to admit that I’m a bit lost when I try to extract the corresponding concepts. Maybe I don’t understand very well what you are trying to achieve but things looks very intrusive to me. If one talks about a strictly client-side Databinding that involves two properties, I have some concerns :
- Why are we talking about JPA Entity object, Databinding is about making two properties in sync. The target property could be a JPA entity, but also another widget property. From a Databinding framework perspective, a JPA entity object of any RPC interface should not be tied to the framework. What is the role of the asynchronous call request.service.fire() ?
- Why are we talking about Id instead of properties ? Is the Id a kind of key to access the target bound object ?
- why do we have to deal with kind of server-side classes ? I understand that ValueStore?? is a place when we find the bound properties (a kind of Hashmap), but is it client side, server side, shared ?
- You seem to say that there is a relationship between the RPC service interfaces and the Data bound properties, do you have a concrete example ?
In brief, after reading this design doc, I find it very complicated, too much related to MVP style (let's not forget that a lot of projects won’t use MVP, nor controller/presenter, etc…), and very verbose. For one class to bind, I need to code 3 or 4 other technical classes (EntityEditor??, Editor<Troop>, TroopId??, StringId??<TroopId>, etc…). It could be nice to highlight generated classes and “have-to-be-coded” classes.
There are three nice existing gwt frameworks, pectin, gwt-databinding and gwittir, they are not tied to any server-side framework and provide awesome features compliant with the common databinding schemes. They don’t require much coding except something like :
Gwittir :
Gwt-beans-binding :
Binding<Counter, Integer, TextBox, String> binding = Bindings.createAutoBinding(UpdateStrategy.READ_WRITE, counter, BeanProperty.<Counter, Integer> create("value"), textBox, BeanProperty.<TextBox, String> create("firstName"));why not getting the best of their worlds instead of reinvent a kind of (even nice) wheel ?
My two cents …
Sami
Is there a reason the @ServerType? annotation needs to have a String rather than an instance of Class<T>? the only reason is that you would want to refer to the annotation on the client-side... which doesn't make sense to me. I would have commented on the Wave, but experienced "turbulence"
I second everything Sami Jaber said!
So I managed to write too much and explain too little.
The intent is that ValueBox? would also be useful for data binding of plain old client side JavaBeans?, without any need for the Id and Property classes. I can define a ValueBox? interface tied to a set of bean classes and have it move their fields to and from HasValue? instances, enforcing validations in the process. I can set up this binding myself via calls like valueBox.setSubcription(bean, fieldNameString). Or I can GWT.create an EditorSupport? object to make those calls for me (which is why they didn't appear in the sketch).
RequestFactory? is intended as another, optional layer on top of this, to aid in dealing with server side ORM. Shared Id instances refer to server side Entities. They and Property instances are used as arguments to command objects (Requests) to make asynchronous RPC calls for the values of fields on these objects, and to edit them. (The @ServerType? annotation is to simplify the use of the ids server side.) ValueBox? and EditorSupport? can also be used to bind these objects and the UI that displays them. I figure we'll provide a script and a servlet that can grovel through JPA service interfaces and generate / maintain the Id and Property definitions, and that others can easily be spun for other persistence frameworks.
Re: why re-invent a wheel, we want our new data backed widgets to play very nice in an asynchronous world — I'll tell you what values I want, you push them into me when they show up, and as they get updated. My impression of the existing frameworks is that they don't play naturally in that world.
I'm supposed to be on vacation now and since I want to stay married, I probably won't be too responsive this week. Thanks for your quick feedback.
That is, "via calls like valueBox.setSubcription(bean, propertyNameString, hasValueInstance), or valueBox.setSubscription(bean, listPropertyNameString, hasValueListInstance)"
I think I've fixed the turbulence on the wave if anyone wants to respond there. The issue is a bug when your in a Group that has read-write access but Public is set to read-only.
Just a quick note before I've had a chance to dig deeper - pectin abstracts "where the data comes from" using a value model so isn't really concerned with how it comes and goes as long as it's can be adapted to a value model. Everything is event driven so as data becomes available the view updates auto-magically and vice-versa.
Some info that would helpful trying to understand your design more fully would be definitions of the various type you mentioned. E.g. Property, ValueStore, DeltaValueStore, ValueBox and the interesting GWT.create(Binder.class, Me.class) which I haven't seen before. It's a bit hard to figure out what's going on without them.
Ultimately I'd love to be able to use your bean binding/async rpc/validation hooks in pectin if I can. I'd also love to be able to hook into UiBinder but that's another story...
Enjoy your holiday (c: Cheers
The wave is working now. Thanks!
@Ray Ok I understand your "async" constraints. Let's have look at it when you get back.
Sami
I've posted this to both the wave and wiki.
In looking at using ValueStore with pectin I think it looks pretty promising (which is nice for me since I have no desire to write my own backend binding/CRUD layer). From what I can see it should be relatively easy to build an adapter that works with a ValueStore and that also supports the validation mechanisms. Just a couple of questions, mostly about the "whys".
Thanks & cheers Andrew
This looks promising. Mirrors some of my ideas at http://groups.google.com/group/google-web-toolkit-contributors/browse_thread/thread/ebeb2a9ddc3cfd52/7534d2c15d49e40d I like the emphasis on DRY
A couple of points:
1. One big DRY problem - repeating all the fields in the view class as @UiField? despite them never being used in the class. You should be able to declare fields in the UiBinder without having to repeat in the view if all they do is bind to a model property.
2. This leads to needing a way of expressing a data bind in UiBinder, which means UiBinder needs to know about the data binder. There are two approaches I can think of - ui:bind='expression' or value='{expression}' . The second might be the best as I think you should be able to bind other properties beside value - eg visible or styleName are the prime use cases.
3. Need a proper expression language in UiBinder to express binds. The current {method.method} binding in UiBinder could be extended, but needs a lot of work as its very simplistic currently
4. Please support weakly typed models. Particularly the use cases of the model being a Map, a JSONObject or an XML Node. Suggest building this into the expression language similar to JSF EL notation eg foo.bar['x'] and foo.bar.x both translate to foo.bar.get("x") if bar implements Map