Collecting Validation Results using Notification Pattern
Error codes, collections, exceptions, and, in Java/Groovy world, Bean Validation: these are all well know methods for collecting validation errors (Bean Validation also implements validations) — but I find them all very limiting.
Some are well known bad methods, such as collections or error codes. Exceptions usage is very debatable as some argue they were not intended for validation and, indeed, the messy try-catches — which are absolutely fine for the intended usage of exceptions — make validation handling difficult.
Bean Validation is a better fit for properties validation but are hard to extend to cover other use cases. Nowadays I just mix properties Bean Validations with normal if’s for more complex validations and, in the end, I just merge all the results in exceptions that hold collections of errors. This, however, is messy, inflexible and I only do that because I wasn’t able to find something better.
What I wanted was to find a mechanism (framework or API) for collecting validation results that would:
- Be flexible enough to foster cumulative validation results in different levels of granularity;
- Enable grouping, naming and association with system elements, such as a properties or operations;
- Easily output in different ways, such as in a web form, a REST response or a file;
- Be simple to use and not get in the way of normal execution.
Nevertheless I couldn’t find anything much different from the aforementioned variants and couldn’t think in anything that could do better.
Then I stumbled upon A Mentoring Course on Smalltalk which, in the “Validation Revisited” section, compares validation to automated tests and concludes that that they are alike. To tell if something is valid, e.g. objects and operations, one must check a list of premises; if all of them turn out to be true, then the validation has passed. That is exactly the same for tests: one specifies a list of premises for a given behavior and implements tests around those; if those tests succeed it means the system complies to the premises, meaning it was correctly implemented and is valid. After this realization, the author subverts the SUnit (xUnit for Smalltalk) framework to collect validations like failed tests are collected, and reports positive feedback of its usage in some projects.
I liked this idea: validation methods did the job of naming validations. I could group a set of validations in a suite, provide methods to associate the validation with a system element, such as a class attribute, and methods to deal with errors as needed. Since a test execution is essentially an event system in which tests notify when they starts, finish, fail, succeed, etc; listeners could be provided to handle those errors and even provide different views of them depending on the situation at hand. It seemed I could accomplish everything I wanted with this approach, so I tried it with JUnit in Java.
In the end it looked good, but I found out that it is much harder to change a framework to your needs in a static language than in a dynamic one. I had to use some Groovy goodies to circumvent limitations imposed by the JUnit Framework, such as extending some final or private scoped class, and make it work. As I went further in the implementation, it got harder to make JUnit become a framework for collecting validation results. Just when I was about to lose hope, I found a 2004 article from Martin Fowler describing an approach for collecting errors through notifications, as an alternative to deal with problems not handled by others approaches.
There were many similarities between the approach proposed by Fowler and what I had in mind, Since I was having too many problems I decided to abandon the JUnit idea and, instead, implement something like a Notification Framework for Validation similar to what he had proposed.
The idea is pretty simple: provide a validation results collection mechanism through the use of an Observer Pattern, so that anyone interested in listening to errors can register itself as an observer. I implemented it in Groovy using two classes: ValidationObserver and ApplicationValidationNotifier. In this case, ApplicationValidationNotifier acts as the subject.
In a usual Observer Pattern, each subject holds a reference to its observers, and when a notifiable change to the subject takes place, the subject iterates over its observers notifying them. In my implementation, instead of the subject it is the ApplicationValidationNotifier responsibility to store observers and notify them. Any object in the system can access it through its static methods and make a notification using one of the validation notification methods provided.
This is a simplification that frees observers to register with the exact subjects of interest, leaving it up to observers to do that distinction inside their implementation. Let’s have a look at the ValidationObserver and ApplicationValidationNotifier interfaces, and walk-through each one of their methods.
interface ValidationObserver {
void startValidation(String validationName)
void issueMandatoryObligation(Object subject, Map context, String error)
void issueMandatoryObligationComplied(Object subject, Map context, String mandatoryValidationName)
void issueError(Object subject, Map context, String error)
void finishValidation()
Boolean successful()
}interface ApplicationValidationNotifier {
static void addObserver(ValidationObserver validationObserver)
static void removeObserver(ValidationObserver validationObserver)
static void removeAllObservers()
static void executeNamedValidation(String validationName, Closure validation)
static void startValidation(String validationName)
static void finishValidation()
static void issueError(Object subject, Map context, String error)
static void issueError(Object subject, Map context, String instantValidationName, String error)
static void issueMandatoryObligationComplied(Object subject, Map context, String mandatoryValidationName)
static void issueMandatoryObligation(Object subject, Map context, String mandatoryValidationName, String error)
}
Note that the ValidationObserver methods are just shadows of ApplicationValidationNotifier ones. Also note that each issue notification method receives the subject by parameter so that the observer can know which object made the notification. Let’s start with the methods for observer registration and de-registration:
ApplicationValidationNotifier.addObserver(observer)
//Do Stuff 1
ApplicationValidationNotifier.removeObserver(observer) //Not interested in Stuff 2
//Do Stuff 2
The observer is concerned with what happens during “Do Stuff 1”, but won’t be notified of anything happening during “Do Stuff 2”. If more classes are created and more stuff is done inside “Do Stuff 1”, the observer does not have to change a bit, and doesn’t to register with the new classes — those new classes will simply notify their validation and the observer will start getting notified of those too.
Note how this fosters cumulative validation results in different levels of granularity: no matter what happens in “Do Stuff 1”, the observer is notified. Also note that validation results collection point of view is of some execution line. While stuff is done in the system, validation is done and its results are collected. It is not focused on objects or even methods, but rather in the execution. So when any code calls ApplicationValidationNotifier.issueError(…) it is telling anyone interested in its execution line that an error happened (by “anyone interested in its execution” I mean “registered as an observer”).
To implement that, ApplicationValidationNotifier saves its observers in a WeakHashMap inside a ThreadLocal variable. This way, every notification is handled inside a separate Thread (i.e. the execution line) in a very fluid way, where objects only need to call the static notification methods without having to pass several parameter or rely on some DI framework (e.g., providing the notification as a request scoped bean). Besides, by using WeakHashMap it is not strictly necessary for observers to de-register themselves, thus also avoiding memory leaks.
I am aware of ThreadLocal issues and cautions people need to take in order to use it properly. In a Servlet container, for example, it is imperative to implement a filter that cleans the ApplicationValidationNotifier ThreadLocal variables because of thread reuse. Nevertheless, it seems to be a better tool for what I wanted to do, i.e, having observers and notifications associated with an execution flow in way that is simple to use and doesn’t get in the way of the regular execution flow. If you know a better way of doing that, please tell me :)
When notified, the observer can ignore some errors or entire validations depending on its needs. Let’s suppose that, inside “Do Stuff 1”, issueError(this, [:], “employee.name.mandatory.error”) is called by an Employee instance. This, in turn, will spam the shadow method issueError(subject, [:], “employee.name.mandatory.error”) on the observer, which can choose to ignore it because it is not interested in validations bound to the Employee class.
Some validation context can be obtained from the subject object, but if the notifier wants to provide more information, such as the name of validated properties or the value that failed validation, it can use the context object, which I chose to implement as a Map. I am not sure if this is the best approach, but works as needed for now. The subject and context being passed onto a given notification allows validation errors to be grouped and associated with system elements, such as properties or operations.
To be able to organize and group even better, three other method notifications are provided: startValidation, finishValidation and executeNamedValidation. The first two mark the start and the end of a validation, so every error issued between them can be assumed as part of one validation. Also, the observers can have an idea of the time spent on each validation if they need to.
Let’s have a look at some code example:
startValidation("Payroll execution validation")
//Do N validations and issue N errors on based the results
finishValidation()
executeNamedValidation is just an alias to create a validation scope over a closure, this is how it was implemented:
static void executeNamedValidation(String validationName, Closure validation) {
startValidation(validationName)
validation(this)
finishValidation()
}
Likewise, the issueError overload that takes an instantValidationName parameter is just an alias for creating a validation scope with the provided instantValidationName and issuing only the error provided to it as a parameter:
static void issueError(Object subject, Map context,
String instantValidationName, String error) {
startValidation(instantValidationName)
getObserversIterator().each {it.issueError(subject, context, error)}
finishValidation()
}
There are two notification variations that require better explaining: issueMandatoryObligation and issueMandatoryObligationComplied.
Those methods were born out of my desire to ensure objects are always on a valid state. So, for example, if the Employee object only allows positive salaries, its setter would change the Employee state only upon validation success. This is pretty easy to achieve:
class Employee{
(...)
public void setSalary(newSalary){
if(newSalary <= 0) {
issueError(this, [:], "employee.salary.must.be.positive")
return
}
salary = newSalary
}
(...)
}
There is a problem, though, with mandatory rules. If, for example, a salary is a mandatory attribute of the Employee class, when an instance is created it will be instantly invalid, since salary will be null. If, afterwards, the attribute isn’t set, it will remain null and, therefore, invalid; but no notification of that fact will be issued. (One might argue that a custom constructor would solve this, but that isn’t always true because there are many cases when classes have complex construction processes and the best choice is to provide a default constructor and a builder class instead of a constructor with lots of parameters.)
Under such circumstances, how is a class supposed to let other parties know that a mandatory attribute was not filled? Other than having some client class explicitly setting this attribute to null, it can’t, because it can’t issue an error while the build is not finished. Until the build process ends, such attribute can be correctly filled, and the class can’t know for sure when this will happen, or if it will happen at all. To circumvent this problem I created the issueMandatoryObligation and issueMandatoryObligationComplied methods.
The first method is used to indicate a mandatory obligation that needs to be fulfilled until the end of object’s build process; otherwise, validation should fail. The second method, issueMandatoryObligationComplied, is used to tell that the obligation has been fulfilled. For as long as issueMandatoryObligationComplied is not invoked, successful() must return false. That way I could use builders and guarantee that objects are valid at the end of build. I am not sure this is a good way to approach this problem, but, again, it works as needed for now.
Those methods are only useful in maintaining objects in valid state (which was a experience of mine that I enjoyed). In case this is not desired, both methods can be ignored and the proposed notification framework can be used normally.
Now that all notification and observing methods are in place, let’s see how to implement a validation observer:
class SimpleValidationObserver implements ValidationObserver{
def errors = []
def mandatoryObligation = [:]
@Override
void startValidation(String validationName) { }
@Override
void issueMandatoryObligation(Object subject, Map context, String mandatoryValidationName, String error) {
mandatoryObligation.put(mandatoryValidationName, error)
}
@Override
void issueMandatoryObligationComplied(Object subject, Map context, String mandatoryValidationName) {
mandatoryObligation.remove(mandatoryValidationName)
}
@Override
void issueError(Object subject, Map context, String error) {
errors.add(error)
}
@Override
void finishValidation() { }
@Override
Boolean successful() {
return errors.isEmpty()
}
def getErrors() {
return errors + mandatoryObligation.collect {it.value}
}
}
This is a very simple implementation that just stores all found errors in a list. It doesn’t care about validation scope either, it just collects errors.
Now let’s see how to provide an implementation that will collect errors and turn them into a Web Service response:
public class WebServiceControllerValidationListener implements ValidationObserver{
private Map errorsByValidation
private currentErrors
private mandatoryObligations = [:]
private Boolean successful = true
def private generateResponseStrategy
def private issueErrorStrategy
def body
WebServiceControllerValidationListener () {
currentErrors = new ArrayList()
errorsByValidation = [null: currentErrors]
generateResponseStrategy = responseOkStrategy
issueErrorStrategy = issueFirstErrorStrategy
}
public Map getErrorsByValidation(){
return errorsByValidation.findAll {!it.value.isEmpty()}
}
@Override
void startValidation(String validationName) {
currentErrors = new ArrayList()
errorsByValidation.put(validationName, currentErrors)
}
@Override
void issueMandatoryObligation(Object subject, Map context, String mandatoryValidationName, String error) {
mandatoryObligations.put(mandatoryValidationName, {issueError(subject, context, error)})
}
@Override
void issueMandatoryObligationComplied(Object subject, Map context, String mandatoryValidationName) {
mandatoryObligations.remove(mandatoryValidationName)
}
@Override
void issueError(Object subject, Map context, String error) {
issueErrorStrategy(error)
}
def private issueFirstErrorStrategy = { error ->
issueErrorOnly(error)
generateResponseStrategy = responseFailStrategy
successful = false
issueFirstErrorStrategy = issueErrorOnly
}
def private issueErrorOnly = { error ->
currentErrors.add(error)
}
@Override
void finishValidation() {
currentErrors = errorsByValidation.get(null)
}
@Override
Boolean successful() {
return successful && mandatoryObligations.isEmpty()
}
public generateResponse(){
mandatoryObligations.each {it.value()}
mandatoryObligations.clear()
return generateResponseStrategy()
}
def private responseOkStrategy = {
return ResponseEntity.ok(body)
}
def private responseFailStrategy = {
return ResponseEntity.badRequest().body(errorsByValidation)
}
}
This is a much more elaborate implementation that uses Spring framework to generate a bad request with errors grouped by validation scope. Being ValidationObserver an interface, it is easy to provide different implementations that deal with error handling in multiple ways, such as translating it in a web form, a REST response, a file or even in DBMS tables or columns.
I have to try it out much more but I really liked the results so far and I believe this approach is much better than anything else I have tried so far. The next thing I will try is to integrate this with a MVC framework.