microstream icon indicating copy to clipboard operation
microstream copied to clipboard

Communication value validation

Open tm-ms opened this issue 6 years ago • 0 comments

When using MicroStream as a database solution, the use case is virtually completely that if internally managed data. Meaning all parts of the process can be trusted. Hence, no encyption or consistency/sanity validation of loaded data is required. At least not by default.

For "Serialization" (actually more like "communication"), this is another story. Encryption (via a modular logic hook) is needed. So is data validation.

Simple example: A field whose value can never be null (an instance is created directly in the constructor / at field definition) can be easily transferred as a null value from an external source.

So there must be a way to validate data if required/desired.

It is already possible to implement a custom TypeHandler that transforms instances to and from a persistent form with an arbitrary concept and logic, including any and all validation logic one might think of. However, implementing such a TypeHandler is rather verbose and required perfectly careful explicit binary offset handling. microstream-one/microstream-private#88 would ease the explicit binary offset handling part, but the verbosity of implementing a whole TypeHandler just to enable validation is still to high.

The ideal (and technically possible) solution would be to have an annotation like the following:

@Validate(Validations::isCapitalizedString)
private String someStringField;

Method references to static methods are a compile time constant and hence would be a possible annotation value. However, annotations do not allow that by design. Maybe they realize and fix that in Java 37 or so, but until they do, that ideal solution is not an option.

Enum instance implementing a Predicate would be possible, but that makes it rather verbose for developers to create validation logic and a compatible Annotation.

Defining a simplified checking logic as a plain string (like min/max value etc) would be possible, but putting logic into plain Strings is always one of the worst ideas (refactorings will sooner or later break it and the compiler can't help) and the "simple" logic will be pretty quickly exploded into a validation language. Sounds good at first, but is pretty dumb in the long run.

The best solution currently conceivable is the following:

A separate class (maybe a static nested class in the entity itself), called "Validations" or "Checks" or such (the exact name is arbitrary and up to the using developer) that contains validation methods with an equal name as the field whose values shall be validated.

Example:

public class Person
{
	@Validated(Check.class) // optional
	String firstname;

	@Validated // optional
	String lastname;
	
	
	
	public void setFirstname(final String firstname)
	{
		// setter uses centralized checking logic for validation
		this.firstname = X.validate(firstname, Check::firstname);
	}
	
	public void setLastname(final String lastname)
	{
		// setter uses centralized checking logic for validation
		this.lastname = X.validate(lastname, Check::lastname);
	}
	
	
	// centralizing validation logic for both class itself and external reconstruction solutions
	@Validations // optional
	static class Check
	{
		static boolean firstname(final String value)
		{
			// arbitrary logic here. Trivial null-check for simplicity's sake of the example
			return value != null;
		}
		
		static boolean lastname(final String value)
		{
			// arbitrary logic here. Trivial null-check for simplicity's sake of the example
			return value != null;
		}
	}
	
}

The validation logic gets centralized for both explicit internal use in the actual class and implicit use by external state reconstruction logic (aka Serialization / Persistence). The annotations can be optional and handled by a modular analyzer. Other possibilities could be to search for a nested static class with a centrally configured name (here "Check") and in there for a suitable method (parameter count and type, return type, name). Annotations could also specifically define the name of the method to be used, however that's programming in plain strings, again.

Of course, the usual (naive) annotations like @NotNull, @UseGetter, @UseSetter (or maybe better @UseAccessControl) and the like could also be supported. And the Enum function approach, too. Adding even simpler strategies as a variant is never a problem, but there has to be one solution that can cover "everything" (sanely conceivable in the specific topic's context). That would be the "Validations" class.

Side note: This is also a nice example of OOP-designwise separations of concerns: The entity class itself should only be the data carrier, without validation logic being hardcoded intertwined. When the validation logic is modularized, it can be reused generically and implicitely by other logic. In the end, this might be an even cleaner and better solution than the "ideal" solution at the beginning ...

tm-ms avatar Aug 19 '19 09:08 tm-ms