Contextual Validation in Java
Join the DZone community and get the full member experience.
Join For FreeHere, we will consider the different ways to carry out validation, what is contextual validation, and why it beats all the other methods.
You may also like: Validation in Java Applications
Context-Independent Validation Bound to Data-Model
Most of the current frameworks compel us, its users, to put validation in the data model. At least, the default mode for most of us is to simply bind validation rules to specific fields in the data model. What’s wrong with this approach?
Consider an example where a guest registers a new food delivery order. The company behind this service, which actually cooks the order, is called SuperFood. That’s how the whole user-story looks like:
Vasya as a guest visits SuperFood’s site and registered an order there.
SuperFood’s backend service must ensure several constraints before putting stuff in a database. One of them is to ensure that either email or phone number is passed.
Now suppose another user registers an order in SuperFood, but this time through some aggregator service, call it AggreA. This order doesn’t differ much from the one registered on SuperFood site, though the constraints to be enforced are different. For example, passing a phone number is essential for that aggregator, while email is optional.
Now, a third user registers an order in SuperFood, and she does it through some else aggregator service, AggreB. And for that one, passing a phone number is not needed, but email is a must.
So, we have the following situation. I have a single data-model for an order, but there are at least three contexts with a different set of constraints. I can go traditional way: introduce an entity corresponding to a database row, and impose those constraints through annotations or config files or whatever way I'me got used to. Data-model validation favors the following approach:
public class User
{
private String phoneNumber;
private String email;
// ...
}
Custom annotation ValidContactInfo
eventually brings us to the custom validator, something like ContactInfoValidator
. Its clearest implementation reflects the mental model of product-manager, which goes like the following (in a pseudo-code):
xxxxxxxxxx
If order is being registered through site, then either email or phone number must be present.
If order is being registered through AggreA, phone is required.
If order is being registered through AggreB, email is required.
The primary objective is to find out somehow what exactly is the concrete scenario it operates within.
The data-model way implies that we should do that in a validator, taking fields from entity data-object. This way, I believe it is the least palatable one since we can’t use the power of the domain model and have to reside the validation logic in service classes. Simplified, it roughly looks like that:
xxxxxxxxxx
public class ContactInfoValidator
{
public boolean isValid(Order order)
{
if (order.getSource.equals(new Site())) {
return order.getPhone() != null || order.getEmail() != null;
} else if (order.getSource.equals(new AggreA())) {
return order.getPhone() != null;
} else if (order.getSource.equals(new AggreB())) {
return order.getEmail() != null;
}
throw new Exception("Unknown source given");
}
}
Now, imagine a mess your validation code turns into if a request gets more or less complex.
Arguably Better: Context-Independent Validation in Domain Objects
Often times, validation logic shown in the previous example gets out of control. In this case, it probably could be more beneficial to put it in a domain object responsible for business-logic. Besides, traditionally, it is a domain code that web developers tend to test-cover first. It could look like the following (mind the naming: I renamed Order
to OrderFromRequest
to stress the difference between it and domain order):
xxxxxxxxxx
public class DomainOrder
{
public DomainOrder(OrderFromRequest orderFromRequest, HttpTransport httpTransport, Repository repository)
{
// set private fields
}
public boolean register()
{
if (this.isRegisteredThroughSite() && this.isValidForRegistrationThroughSite()) {
// business logic 1
} else if (this.isRegisteredThroughAggreA() && this.isValidForRegistrationThroughAggreA()) {
// business logic 2
} else if (this.isRegisteredThroughAggreB() && this.isValidForRegistrationThroughAggreB()) {
// business logic 3
}
}
private boolean isRegisteredThroughSite()
{
return orderFromRequest.getSource.equals(new Site());
}
private boolean isValidForRegistrationThroughSite()
{
return orderFromRequest.getPhone() != null || orderFromRequest.getEmail() != null;
}
}
But the problem of collecting errors and mapping them to UI arises. To my knowledge, there is no clean solution for that.
Contextual Validation That Is Specific to a Concrete User Story
For me, validation serves a clear purpose: to tell clients what exactly is wrong with their requests. But what exactly should go to validation? It depends on your take on the domain model. In my opinion, objects in the domain model represent context-independent “things” that can be orchestrated by a specific scenario in any possible way. They don’t hold any context-specific constraints. They check only universal rules, the ones that simply must be true, otherwise, that thing simply can’t be that thing. This reflects an always-valid approach when you simply can’t create an object in an invalid state.
For example, there is such a thing as courier id. It can only consist of UUID value. And I’ll definitely want to make sure that this is the case. It usually looks like the following:
xxxxxxxxxx
public class CourierId
{
private String uuid;
public CourierId(String uuid)
{
if (/*not uuid*/) {
throw new Exception("uuid is invalid");
}
this.uuid = uuid;
}
}
Introducing its own UUID interface with a couple of implementations would be even better:
xxxxxxxxxx
public class FromString implements CourierId
{
private UUID uuid;
public FromString(UUID uuid)
{
this.uuid = uuid;
}
public String value()
{
return this.uuid.value();
}
}
Typically, domain model invariants are quite basic and simple. All the other, more sophisticated context-specific checks belong to a specific controller (or Application service, or user-story). That’s where Validol comes in handy. You can first check basic, format-related validations, and proceed with however complicated ones.
Contextual Validation Example
Consider the following JSON request:
xxxxxxxxxx
{
"delivery":{
"where":{
"building":1,
"street":"Red Square"
}
}
}
Validation could look like that:
xxxxxxxxxx
new FastFail<>(
new WellFormedJson(
new Unnamed<>(Either.right(new Present<>(this.jsonRequestString)))
),
requestJsonObject ->
new UnnamedBlocOfNameds<>(
List.of(
new FastFail<>(
new IsJsonObject(
new Required(
new IndexedValue("delivery", requestJsonObject)
)
),
deliveryJsonObject ->
new NamedBlocOfNameds<>(
"delivery",
List.of(
new FastFail<>(
new IndexedValue("where", deliveryJsonObject),
whereJsonElement ->
new AddressWithEligibleCourierDelivery<>(
new ExistingAddress<>(
new NamedBlocOfNameds<>(
"where",
List.of(
new AsString(
new Required(
new IndexedValue("street", whereJsonElement)
)
),
new AsInteger(
new Required(
new IndexedValue("building", whereJsonElement)
)
)
),
Where.class
),
this.httpTransport
),
this.dbConnection
)
)
),
CourierDelivery.class
)
)
),
OrderRegistrationRequestData.class
)
)
.result();
I admit it might look scary for anyone who sees the code for the first time and is totally unfamiliar with a domain. Fear not, things are not so complicated. Let’s consider what’s going on, line by line.
Lines 1-4
: check whether the input request data represent well-formed JSON. Otherwise, fail fast and return a corresponding error.
Line 5
: in case of well-formed JSON, a closure is invoked, and JSON data is passed.
Line 6
: JSON structure is validated. The higher-level structure is an unnamed block of named entities. It closely resembles a Map
.
Line 7
: A list with a single named block is implied.
Line 11
: It’s called delivery
.
Line 10
: It’s required.
Line 9
: It must represent a JSON object.
Line 14
: If all previous conditions are satisfied, closure is invoked. Otherwise, this whole thing fails fast and returns an appropriate error.
Line 15
: A block named delivery
consists of other named entities.
Line 19
: Namely, where
block. It’s not required though.
Line 20
: If it’s present, closure is invoked.
Line 23
: Block named where
consists of other named entities.
Line 28
: Namely, street
which is …
Line 27
: … required;
Line 26
: and is represented as a string.
Line 33
: and building
, which is …
Line 32
: required as well;
Line 31
: and should be represented as an integer.
Line 37
: if all previous checks are successful, an object of the class Where
is created. To be honest, it’s not a full-fledged object. It’s just a data-structure with convenient, type-hinted and IDE-autocompleted access to its fields.
Line 22
: if underlying checks are passed, an address is ensured to exist. Mind the second argument, httpTransport
. It’s for requesting some third-party service that checks an address existence.
Line 21
: Aaaand, finally, we want to ensure that courier delivery is enabled in that area. We’ll need a database access for that, hence dbConnection
argument.
Line 45
: If everything was fine, a CourierDelivery
object is created. It has a single argument, a Where
class.
Line 49
: Finally, OrderRegistrationRequestData
object is created and returned.
I’ve intentionally put all the validating code in a single class. If the data-structure is really complex, I’d recommend to create a class per block. Check an example here.
Conclusion
So that’s pretty much it. This approach might (and actually does) look like overkill with such a simple request, though it shines with more complicated ones.
If you're interested in what else Validol library can do, check the documentation. A good place to start is this quick-start guide.
Further Reading
Published at DZone with permission of Vadim Samokhin. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments