Hi,
as you know, we are about to introduce the concept of method level
constraints in Hibernate Validator 4.2 (see [1] for more details).
In this context we are also planning to support the definition of
method level constraints using the programmatic constraint API [2].
Hardy, Kevin and I have discussed the required changes for a while and
came to a point where we would like to get some more feedback.
As of today this API is used as follows:
ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Car.class )
.property( "licensePlate", FIELD )
.constraint( SizeDef.class )
.min( 2 )
.max( 14 )
.property( "seatCount", FIELD )
.constraint( MinDef.class )
.value ( 2 );
Note, that this is one single invocation chain. In particular,
constraint definitions are part of this chain. With the introduction
of method level constraints this leads to problems as it doesn't make
sense any more to invoke any method of the API at any point of time.
For instance the following invocation should be possible:
ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Car.class )
.method( "drive" )
.parameter(1)
.constraint( NotNullDef.class )
.parameter(2)
.constraint( SizeDef.class )
.min( 2 )
.max( 14 )
.genericConstraint (Pattern.class)
.param( "regex", "...");
But not the following one (as invoking parameter() doesn't make sense
when being on a property):
ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Car.class )
.property( "licensePlate", FIELD )
.constraint( NotNull.class )
.parameter(2) //doesn't make sense
...
So the general problem is, that depending on the current invocation
context only certain methods should be part of the API at that point.
This is no problem when looking at specific methods (such as
property(), method(), parameter() etc.) which return an appropriate
context offering the methods allowed next.
But this fails with a generic method such as constraint() which is
allowed to be invoked at several places but should return a specific
constraint definition at the same time. After several experiments the
following seems to be the best solution IMO:
ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Car.class )
.method( "drive" )
.parameter(1)
.constraint( ConstraintDef.create( NotNull.class ) )
.parameter(2)
.constraint( ConstraintDef.create( SizeDef.class )
.min( 2 )
.max( 14 ) )
.constraint( ConstraintDef.createGeneric( Pattern.class)
.param( "regex", "...") );
Here constraint definitions are created and configured separately and
then are passed to constraint(). That way the invocation context is
not propagated through constraint definition types and can be modeled
to contain exactly the allowed methods at each point of time. Some
variations of this approach are discussed in this Gist [3], e.g.
...
constraint( new SizeDef().min( 2 ).max( 14 ) )
...
On the downside the API looses a bit of its "fluency" and at least for
beginners it might not be obvious how to obtain constraint definition
instances. Personally I think this is ok for the sake of API
consistency (plus, actually I start to like this hierarchical approach
of first configuring constraints and then placing them).
WDYT, is this a step into the right direction? Or do you see any other
approaches we've missed so far (one other thing we've tried was to
parametrize constraint definitions with the current context type, but
this worked out to be pretty cumbersome [4])?
Another question is whether we should enforce an order within the
constraint definitions for one type (e.g. class-level -> properties ->
method 1 parameters -> method 1 return value -> method 2 ...) or allow
an arbitrary order here. Personally I don't see the need for such an
order (we don't require one implementation-wise, and users could
adhere to one by convention if they want to), but Hardy and Kevin feel
different about this :)
For the interested the current proposal can be found at [5].
Thanks for any feedback,
Gunnar
[1]
http://in.relation.to/18074.lace
[2]
http://docs.jboss.org/hibernate/validator/4.2/reference/en-US/html/chapte...
[3]
https://gist.github.com/940438
[4]
https://github.com/gunnarmorling/hibernate-validator/commit/6cfe540144828...
[5]
https://github.com/gunnarmorling/hibernate-validator/tree/HV-431-new