I've been exploring the Model Driven pattern/interceptor in
Struts 2 a lot recently and I've come to the conclusion that its fundamentally flawed for the vast majority of its normal use cases. I'm starting to dig into the source to see if its possible to fix this problem at the framework level, rather than with very hackish workarounds. Here's the specific problem:
When using Model Driven Action with visitor validation directly on the model, coupled with a JPA provided, when the validations fail, if there is any lazy-load to provide the redraw of the form, the invalid data is committed to the database as part of the flush before query synchronization.
Model Driven + Visitor Validation is probably near 100% of the ModelDrive use cases. Struts 2 + JPA Provider (or raw Hibernate) is also probably at least a majority of applications, so I don't think this is a rare/odd niche.
The error happens because:
1. Prepare Interceptor: loads an object from the database (ignoring the irrelevant first Params interceptor supporting this); thus the model object is bound to a persistence session.
2. (Second) Params Interceptor: injects (invalid) values into backing model -- model is now dirty
3. Validator interceptor: recognizes that the object is invalid (can also occur due to failed type conversion)
4. Control is passed to the INPUT flow
5. During re-render of the form, a request for a lazy-load on a collection on the object, OR a request for a getAll/etc style call on the action to support a select list triggers a flush of the persistence session to ensure that the read doesn't contain stale data -- flushes the dirty and invalid data to the DB
(Here's a link to a
thread from last year describing the same problem with workarounds:
http://www.nabble.com/ModelDriven-CRUD-validation-failure-still-causes-JPA-update-td12987242.html)
None of the workaround appear to offer both correct behavior and ease of use; and in all cases its adding application logic to handle what's really a framework problem.
However I do feel that
a) validations on a model should be viewed as invariants
b) data injection from the framework to the model should be atomic
c) we need access to the original request values for form redisplay (of course with ability to cleans values for XSS reasons)
If Model Driven were to satisfy these criteria, then it wouldn't cause JPA to do the wrong thing. And would still be correct in other persistence (or lack of persistence) environments.
In part the errors comes from violating the SRP by combining the requirement of form redisplay of entered values on top of the regular business logic (including definition of valid objects) on the model object.
The solution I'm looking into:
changing Model driven to create a copy of the model object (detached from the persistence session), inject the parameters, validate, on success inject into the original, on fail leave the original alone. (Its a little annoying that all the changes have to happen to _other_ interceptors and use checks to see if the action is an instance of modeldriven, but I don't see any other approach to tweaking it)
stick some proxy/mock containing the sanitized, but nearly raw request parameters onto the value stack, onto of the model -- allowing redisplay of the entered form values (this is all very hand-wavy at the moment)
Has anyone else played with this?
[ April 30, 2008: Message edited by: Eric Nielsen ]