52. Avoiding bad data in immutable objects

Bad data is any data that has a negative impact on the immutable object (for example, corrupted data). Most probably, this data comes from user inputs or from external data sources that are not under our direct control. In such cases, bad data can hit the immutable object, and the worst part is that there is no fix for it. An immutable object cannot be changed after creation; therefore, bad data will live happily as long as the object lives.

The solution to this problem is to validate all data that enters in an immutable object against a comprehensive set of constraints.

There are different ways of performing validation, from custom validation to built-in solutions. Validation can be performed outside or inside the immutable object class, depending on the application design. For example, if the immutable object is built via the Builder pattern, then the validation can be performed in the builder class.

JSR 380 is a specification of the Java API for bean validation (Java SE/EE) that can be used for validation via annotations. Hibernate Validator is the reference implementation of the validation API, and it can be easily provided as a Maven dependency in the pom.xml file (check the source code bundled to this book).

Furthermore, we rely on dedicated annotations to provide the needed constraints (for example, @NotNull, @Min, @Max, @Size, and @Email). In the following example, the constraints are added to the builder class as follows:

...
public static final class UserBuilder {

@NotNull(message = "cannot be null")
@Size(min = 3, max = 20, message = "must be between 3 and 20
characters")
private final String nickname;

@NotNull(message = "cannot be null")
@Size(min = 6, max = 50, message = "must be between 6 and 50
characters")
private final String password;

@Size(min = 3, max = 20, message = "must be between 3 and 20
characters")
private String firstname;

@Size(min = 3, max = 20, message = "must be between 3 and 20
characters")
private String lastname;

@Email(message = "must be valid")
private String email;

private final Date created;

public UserBuilder(String nickname, String password) {
this.nickname = nickname;
this.password = password;
this.created = new Date();
}
...

Finally, the validation process is triggered from code via the Validator API (this is needed in Java SE only). If the data that enters the builder class is invalid, then the immutable object is not created (don't call the build() method):

User user;
Validator validator
= Validation.buildDefaultValidatorFactory().getValidator();

User.UserBuilder userBuilder
= new User.UserBuilder("monika", "klooi0988")
.email("[email protected]")
.firstName("Monika").lastName("Gunther");

final Set<ConstraintViolation<User.UserBuilder>> violations
= validator.validate(userBuilder);
if (violations.isEmpty()) {
user = userBuilder.build();
System.out.println("User successfully created on: "
+ user.getCreated());
} else {
printConstraintViolations("UserBuilder Violations: ", violations);
}

This way, the bad data cannot touch an immutable object. If there is no builder class, then the constraints can be added directly at the field level in the immutable object. The preceding solution simply displays the potential violations on the console, but, depending on the situation, the solution may perform different actions (for example, throw specific exceptions).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset