So we have a RESTful webservice that implements CRUD operations for a model, like this one:
class MyModel {
private long id;
private String name;
// other fields
private boolean completed;
private boolean active;
// getter and setters
}
In this webservice there is also a path like /service/{id}/complete
that updates just the field completed
to true. This indicates a final step for the creation.
The save and the complete operation are pretty simple and similar (they both end up calling a saveOrUpdate
). Where they differ is the validation. The complete validation is stricter.
The one before me declared an interface:
interface Validator<T> {
void validate(T t);
}
And then to check that all mandatory fields where there before setting the complete
attribute to true
we have something like (writing in pseudocode):
MandatoryFieldsValidator implements Validator<MyModel> {
list = {'id', 'active'}
public void validate(MyModel model) {
foreach(element in list) {
if(myModel.get(element) is null) throw ValidationException(element + " is mandatory");
}
}
}
So then I needed another type of validator and started doing something like this because I needed specific controls over the name
field:
NameValidator implements Validator<MyModel> {
public void validate(MyModel model) {
if(model.name is null or empty)
throw ValidationException(element + " is mandatory");
if(model.name.startswith("something"))
throw ValidationException("something not valid prefix");
if(model.name.length > 10)
throw ValidationException("too long");
}
}
The controller had an attribute of type Validator that instantiated a MandatoryFieldsValidator
so I added another attribute of the same type instantiating a NameValidator
.
Now what I don't like about this:
- The fact that I need to add a new attribute to the class as soon as I have a new validator to use;
- The fact that I'm repeating checks in different validators because even if
name
is not included in theMandatoryFieldValidator
I'm applying the same logic insideNameValidator
(is null or empty); - I will also add a new type of model and a
MandatoryFieldValidator<NewModel>
with the same type of control logic is a non-sense.
Can you give me some advice? I'm looking into a general pattern to organize this kind of validation but I'm using Java so if you have some Java specific solution is ok.
1 Answer 1
You could use the composite pattern to create a tree of Validator
instances. Each node would validate its with its own Validator
before delegating to children (if any). This way children don't need to check again what was already validated in parent nodes
public class ValidatorTree<T> implements Validator<T> {
private Validator<? super T> node;
private List<Validator<? super T>> children;
// constructor etc. snipped for brevity
@Override
public void validate(T t) {
node.validate(t);
for (Validator<? super T> child : children) {
child.validate(t);
}
}
}
This way you can bundle several validators into one (so only one root validator needs to be referenced). And validation order is guaranteed, so no need to perform duplicate checks.
Edit (in response to comment)
A Validator for Model1 may very well be different from a Validator for Model2. If Validators are to be reused for different models you can define interfaces that define the common parts to be validated.
e.g.
public interface Named {
String getName();
}
and a matching NameNotNullValidator :
public class NameNotNullValidator implements Validator<Named> {
@Override
public void validate(Named named) {
if (named.getName() == null) {
throw new ValidationException(/* appropriate message here */);
}
}
}
And this Validator can be reused in validator trees for all models that implement Named
. Note that I adapted the generics in ValidatorTree
for this.
-
\$\begingroup\$ Ok but that doesn't resolve my problem when it comes to <T>. With this solution I still need, for example, two mandatoryFieldValidators one for each <T> I introduce. I'm probably missing something in the reasoning. \$\endgroup\$dierre– dierre2013年08月13日 16:26:31 +00:00Commented Aug 13, 2013 at 16:26
-
\$\begingroup\$ Edited response \$\endgroup\$bowmore– bowmore2013年08月13日 18:43:53 +00:00Commented Aug 13, 2013 at 18:43
-
\$\begingroup\$ Ok. Question: if in a model I have two field that needs a "notNullValidator" what should I do? Right now I see two solution: add a method getOtherField in the Named interface or add a new interface OtherField with method getOtherField and then I have to add a second Validator<OtherField> with the same logic of NameNotNullValidator. Is my reasoning wrong? Because in the first case I'll have an Interface with the same field of the model, in the second case I'll have logic replicated. \$\endgroup\$dierre– dierre2013年08月14日 07:21:12 +00:00Commented Aug 14, 2013 at 7:21
-
\$\begingroup\$ I thought the Validators worked on the model, not on the fields. i.e. T is a model class. Of course these can use reusable field validating components, I'm just not sure whether they have to implement the
Validator
interface. \$\endgroup\$bowmore– bowmore2013年08月14日 09:29:18 +00:00Commented Aug 14, 2013 at 9:29