Community
Participate
Working Groups
Warning: This bug is a general rant about the current state of the bindings framework design. It's not clear where the team would prefer this discussion to live, so I thought I'd start here on Bugzilla. IMHO, there are several features that are currently being confounded in an awkward way: 1. IConverters do not always work on the complete source type domain, so there needs to be a way to validate input before conversion is attempted, or at least to report the problem in a meaningful way. This kind of validation is coupled to the converter at a basic level, and should only be performed when changes are actually propagated across the binding via the converter. E.g. string conversion to int or Integer would use this to block values out of range for a 32-bit Java int. Currently, validation is artificially separated from conversion, so it is difficult to properly account for converter state in validation. For example, how would String2IntegerValidator account for Locale-specific NumberFormats? How would it even know if the current IConverter is sensitive to such things? (Right now, this is a bug.) 2. After successful conversion, the domain model might still reject the input for its own reasons. Again, we want to report this problem in a meaningful way. This check should only occur after successful conversion. E.g. a domain model might only accept a specific range of integer values. This seems to be handled correctly, although it is difficult to tell if the framework is properly mapping the domain description or incorrectly mapping the domain value type to IDomainValidator. (More type documentation would disambiguate this, which is part of why I requested it.) 3. Some input can and should be rejected immediately, before the GUI even displays a response to the user's action. This kind of pre-validation/veto power is also typically coupled to the converter, but is sometimes driven from the domain model. This veto check must be performed before the GUI responds to an action, which means in practice that it must be performed during event processing of almost all user actions. E.g. a string2int converter can veto alphabetic characters if the radix is < 10, or a domain model might veto a Combo selection change if the new value is invalid based on other model state. The only way to provide this functionality now is to implement IValidator.isPartiallyValid(). This works okay for the default case, when the constraints are coupled with the IConverter, but it suffers in the same way as the full isValid(). This is really awkward when additional domain-specific constraints are necessary. The domain constraints and the conversion constraints must be merged together into one method, coupling implementation details from two architectural components in a combinatorical fashion. Fortunately, domain constraints rarely veto changes in this way, so the design problem doesn't cause much pain in practice. (Fixing this would make the framework significantly more complex, so I recently argued to ignore the problem. I mention it here only for completeness.) 4. The events that should trigger a veto check are different than the events that should trigger change propagation through the network. Currently, the TIME_X and POLICY_X constants are the only way to configure the triggers. I don't think that this is sufficient. There needs to be some kind of overridable mapping between the description object and the trigger event(s). Client code should be able to control this in a straightforward way on individual bindings. 5. Sometimes specific validation rules must be relaxed on a temporary basis while state is being updated, or else change events must be processed in a constrained order so that validation constraints are never violated. A similar problem exists in relational database design with foreign key constraints. In theory, the binding framework should be aware of all such constraints, so it can perform a constraint-satisfaction analysis and reorder event processing so that everything works. (This is what Hibernate does to avoid foreign key constraint violations.) In practice, it is very easy for client code to introduce additional constraints or additional edges in the event propagation network, so it is impossible for the binding framework to detect all possible conflicts. Therefore, there must be a way to suspend and resume change propagation explicitly from client code. There is currently no easy way to do this. There is no way to turn off change propagation for specific edges nor network segments. The only way is to dispose and recreate an entire DataBindingContext whenever validation must be suspended. This seems ridiculously heavyweight, and it reduces encapsulation of the binding network configuration logic. 6. Last but not least, change propagation networks often do have cycles, and it is very useful for the framework to act intelligently when it has enough information to do so. For example, if two views have two-way bindings to the same model, and the propagation trigger occurs on one view, it should act like so: view1 -> model -> view2 -> |stop| It should not try to push the same change back around the cycle infinitely: view1 -> model -> view2 -> model -> view1 -> etc. The framework gets extra bonus points if it is smart enough to walk the graph until the value stabilizes. This is sometimes necessary if converters do not define bijective functions. For example, imagine a string-to-int converter that accepts both english number words and numerals (a many-to-one mapping): view1("one") -("one"->1)-> model(1) -> view1("1") -("1"->1)-> |stop| The current design for Binding makes it ambiguous whether an instance will behave as a one-way or two-way link in the change propagation network. It doesn't attempt to detect even trivial cycles. Validation is only applied along one direction, but changes can be propagated (and conversions performed) in both directions. This is all a bit silly. Bindings must either be symmetrical, with validation in both directions, or consistently one-way. I would be happy to put these ideas into concrete terms (i.e. actual Java interfaces), but I'm not sure how to proceed. These changes seem necessary to me, but obviously it's your perogative to do what you like with the design. Please let me know if you'd like me to assist in any specific way.
Thanks for your detailed thoughts. I'm very sympathetic to the general thrust of your message. Unfortunately I don't have time to respond in detail to this report at the moment, but I wanted to stop and thank you for taking the time to annotate the bugs like you've been doing.
I've been thinking that we need 3 basic changes: 1) Like you say, we have symmetric conversion, validation should be symmetric as well. 2) Some things like update ordering or priorities cannot be implemented properly without having a centralized manager for firing target2model and model2target copies. Scott Delap's unit-of-work bug is crying for a centralized event manager. 3) Bug 147128 may imply that we need a higher level construct for differentiating selection changes from actual model or target changes. One way to do this is described in that bug.
In #147128, you describe data bindings as an "assembly language". This is the central concept that defines the whole exercise. At its core, a data binding framework is a way to define a change propagation network in a declarative style. This is similar to the goal of Spring's declarative configuration of an object dependency network. In both cases, distributed, piecemeal wiring logic is replaced by a centralized configuration mechanism. This allows the cross-cutting concern to be encapsulated. DataBindingContext <=> ApplicationContext Unfortunately, data binding is even more complex than dependency injection, because the framework must actively participate even after the configuration phase is done. I hope that helps to put things in perspective. :-)
Peter, Thanks for your input on this and other bugs. I think your observations are valuable and we should try to address your concerns. They are however very general in nature and I don't see how we can solve all those problems in a reasonable time frame. > I would be happy to put these ideas into concrete terms Great! I would suggest you start by describing use cases, not API, that help us understand in the concrete what the current framework cannot do (or can only do in an awkward way). By doing this, you can prioritize new features or API changes based on the importance of a particular use case. For example, currently our IObservable implementations break cycles by setting a flag "updating" to true in setters, and only reacting to change events if "updating" is false. This is a naive approach, but so far it has worked sufficiently well. Your example in (6.) where ideally, you want a smart mechanism that allows values to stabilize, this would be a use case that the framework does not support. After all, I think we will have done a good enough job if the framework helps building 80% of the boilerplate code connecting models and the UI, and doesn't stand in the way when you need to write the remaining 20% in the classsic way by hooking listeners on objects explicitly. I don't think we have to aim for 100% - experience shows that in some way or another, all abstractions are leaky anyways.
What should we do with this discussion? Do we need to keep this bug open?
It looks to me like a lot of this is out of date. Peter, would you care to elaborate on any remaining concerns?
No further discussion for nearly two years. Closing as obsolete.