API First

Recently I was writing a small package for data validation (specifically for a web-form).  My first attempt was pretty bad, so I want to take a minute to reflect on why that was my first instinct and how not to make those same mistakes again.

My first approach was to build a generic Validator object. It could be instantiated once and then extended with various rules (ex: for a string, it should be less than 140 chars).  I added a method for wrapping a Validator as a rule for another Validator. I wrote out useful methods for a StringValidator, ArrayValidator, etc.,  looked at the code I wrote, where every method built out a function and added it to the Validator’s list of rules, and felt proud.  I got all my unit tests passing. And then I went to actually use it…

function validator(gallery:Gallery): Validator<Gallery> {
    var v = new Validator<Gallery>("Gallery");
    var ruleTitle = new StringValidator("Title").minLength(3).maxLength(128).asRule(function(gallery:Gallery) {
        return gallery.title;
    });
    var ruleDescription = new StringValidator("Description").minLength(3).maxLength(128).asRule(function(gallery:Gallery) {
        return gallery.description;
    });
    v.addRules([ruleTitle, ruleDescription]);
    return v;
}

What is this hot mess?

function handleCreateGalleryCommand(command:CreateGalleryRequest): Promise<Noise> {
    var gallery = command.gallery;
    var errors = Gallery.validator().validate(gallery);
    if (errors.length != 0){
        ...
    }
...

The main issue is all the mapping functions needed to grab the properties of the Gallery and pass them into their respective Validators.  The validator().validate() seems redundant as well.

Of course I fell into this trap by being in the habit of making everything an object.  But also I started with the implementation instead of the API.  The FIRST thing I should have done was to mock out this example validate Gallery function, using a realistically complex data-object, and refine it until I had an API that was comfortable.

Once I did, I came up with this:

public static function validation(gallery:Gallery, name:String = "Gallery"): Validation<Gallery>{
    var v = new Validation<Gallery>(gallery, name);
    v.assertThat(gallery.id, "id");
    v.assertThat(gallery.title, "title").minLength(3).maxLength(128);
    v.assertThat(gallery.description, "description").maxLength(512);
    v.assertThat(gallery.owners, "owners").minLength(1).maxLength(128);
    return v;
}

...

function handleCreateGalleryCommand(command:CreateGalleryCommand): Promise<Noise> {
    var gallery = command.gallery;
    Gallery.validation(gallery).asPromise().next( ...
}

The validation function is just a function! It returns a “Validation”, which has an easy API to pull out the errors, the original value, or a pre-resolved Promise.

I had to do some interesting Haxe-foo to make this API work, but the end result is a clean API, and is even (negligibly) more performant than my first attempt.

The main lesson here is to start with the outward-facing components.  In this case the API.  The whole point, the telos, of this package is to make developers’ lives easier.  If the API is so clunky that no one uses it, it doesn’t matter how clever the implementation is.

So the usefulness of the package is capped by the ease (clarity and concision) of its API.  That being the case, it makes sense to build that first. I’ll try to keep that in mind.

What do you think of the API for this Validation library? Any way I could improve it?

ScottPlusPlus

Working to upgrade our democracy by making voting more awesome (ex: STAR Voting). Reach out if you want to chat about saving the world.

Leave a Reply

Your email address will not be published. Required fields are marked *