Our first thought is to put validation in the entity. After all, entities are in charge of maintaining their invariants. They are the heart of our business logic, and validation can also be pretty fundamental to our business logic.
One disadvantage of validating in the entity is that the entity can grow too large. Another disadvantage is that validations often require access to services, so putting those in the entity is not Clean Architecture. The third disadvantage is that entity methods do not naturally provide a powerful enough API. Throwing exceptions does not reasonably handle results for more than a single validation. It's too easy for calling code to neglect to properly call pairs of methods for validation or to check method return values, and these calling conventions detract from the proper focus of an entity.
E.g.
class Creature {
public void eat(Snack v) {...} //invariant maintained using types
private void setBloodSugarLevel(int i) {...} //invariant maintained privately
public void eatIfValid1(Object o) throws RuntimeException() {...} //no
public void eatIfValid2(Object o) throws EatingException() {...} //no
public ValidationResults eatIfValid3(Object o) {...} //no
public ValidationResults validateEat(Object o) {...} //no
}
The rest of this post describes a different approach to validation, which solves these problems.
We code each validation in a class by itself, thereby satisfying the Single Reponsibility Principle. All the validations of an object should implement a common interface. Two interfaces are better than one here; use one interface for indicating that the object is invalid, and another for providing information regarding how or why it's invalid (SRP again). Not only does this help share code for generating validation results, it also causes your code to be cleaner for the cases in which the result is specific to the validation.
E.g.
@Singleton
class ComfySnackValidation implements Predicate, Function {
@Inject
WeatherService weather;
public boolean test(Snack snack) {
int temperature = weather.getCurrentTemperatureInFarenheit();
if (temperature < 68 || 78 < temperature)
return true;
}
public ValidationResult apply(Snack snack) {
return new ValidationResult(getClass().getSimpleName());
}
}
There are two important aspects to this approach:
1) we validate whole objects and not individual method calls, and
2) we allow creating invalid objects.
Validating anything other than whole objects requires one of inelegant APIs mentioned above. Validating only whole objects enables us to leverage the type checker, as we'll see in the next post. The objects that we validate may be entities or value objects. They may be "command objects", that exist solely to serve as arguments to a single method. Often, the object needs a reference to another object which is already valid and persisted. This is fine, so long as nothing in the persistent object graph yet refers back to the new object, the object which is not yet known to be valid.
Creating invalid objects is especially compelling in Java, which doesn't yet support named parameters, and for which entity builders can be challenging. Even in languages which do support named parameters, we often want to use the actual object before we know it's valid, consulting it in defaulting and validation logic. We may even want to publish invalid objects, and it’s better to not have two different code paths for publishing the same fields.
We can achieve “correctness by construction”; there should be no reasonable way to call the domain incorrectly. We can achieve this without the entities having to know about each validation. The essence of the design is that a factory injects a collection of validating services into the object to be validated.
e.g.
@Singleton
public class SnackFactory {
private Validator validator = new Validator();
@Inject setComfyValidation(ComfySnackValidation v) {
validator.add(v);
}
...other validations to inject...
public Snack create() {
return new Snack(validator);
}
}
With a small generic ValidatorImpl, the boilerplate that we need in the validated object is small:
e.g.
class SnackImpl implements Snack {
private Validator validator;
public Snack(Validator validator) {
this.validator = validator;
}
public Snack validate(ValidationResults results) {
return validator.validate(this, results);
}
}
Here is an example of a generic validator to support.
Next post will discuss how the type checking works.