Contextual Validation in Java
Join the DZone community and get the full member experience.Join For Free
Here, 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:
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):
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:
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
OrderFromRequest to stress the difference between it and domain order):
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:
Introducing its own UUID interface with a couple of implementations would be even better:
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:
Validation could look like that:
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
Line 7: A list with a single named block is implied.
Line 11: It’s called
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,
streetwhich 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
Line 45: If everything was fine, a
CourierDelivery object is created. It has a single argument, a
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.
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.
Published at DZone with permission of Vadim Samokhin. See the original article here.
Opinions expressed by DZone contributors are their own.