Monday, May 20, 2013

Validating composite models with knockout validation

When you use knockout validation to extend observables with validation rules, it will add a few functions to these observables - the most important ones being; error and isValid. You can use these functions to verify if any of the validation rules were violated, and to extract an error message.

To extract all of the error messages out of a composite model, you can use a grouping function.
function BookingModel() {      
    var self = this;
   
    self.contact = new ContactModel();
    self.departure = new DepartureModel();
   
    self.isValid = function() {
        return self.contact.isValid() && self.departure.isValid();
    };
   
    self.validate = function() {                           
        if (!self.isValid()) {
            var errors = ko.validation.group(self);                           
            errors.showAllMessages();
        
            return false;          
        }

        return true;
    };                    
}

function DepartureModel() {
    this.street = ko.observable('').extend({ required: true });
    this.houseNumber = ko.observable('').extend({ required: true });
    this.city = ko.observable('').extend({ required: true });
    this.time = ko.observable('').extend({ required: true });            
    
    this.isValid = function() {
        return 
            this.street.isValid() &&
            this.houseNumber.isValid() &&
            this.city.isValid() &&
            this.time.isValid();                
    };
}

function ContactModel() {
    this.firstName = ko.observable('').extend({ required: true });
    this.lastName = ko.observable('').extend({ required: true });
    this.phoneNumber = this.firstName = ko.observable('').extend({ required: true });
    this.email = ko.observable('').extend({ required: true });   

    this.isValid = function() {
        return 
            this.firstName.isValid() &&
            this.lastName.isValid() &&
            this.phoneNumber.isValid() &&
            this.email.isValid();                
    };    
}

This is what my first attempt looked like. A little later I discovered that you can get rid of these boilerplate functions on the composite model by applying the validatedObservable function instead.
ko.applyBindings(ko.validatedObservable(new BookingModel()));

ko.validatedObservable = function (initialValue) {
    if (!exports.utils.isObject(initialValue)) { 
        return ko.observable(initialValue).extend({ validatable: true }); }

    var obsv = ko.observable(initialValue);
    obsv.errors = exports.group(initialValue);
    obsv.isValid = ko.computed(function () {
        return obsv.errors().length === 0;
    });

    return obsv;
};
The validatedObservable function will add an errors function to the composite model which traverses the object graph and validates each eligible property. The errors function also has a showAllMessages function that will display an error message next to each invalid element. The isValid function only asserts if there are any errors.
function BookingModel() {      
    var self = this;
    
    self.contact = new ContactModel();
    self.departure = new DepartureModel();

    self.validate = function() {                           
        if (!self.isValid()) {                                         
            self.errors.showAllMessages();
        
            return false;          
        }

        return true;
    };             
}   

function DepartureModel() {
    this.street = ko.observable('').extend({ required: true });
    this.houseNumber = ko.observable('').extend({ required: true });
    this.city = ko.observable('').extend({ required: true });
    this.time = ko.observable('').extend({ required: true });            
}

function ContactModel() {
    this.firstName = ko.observable('').extend({ required: true });
    this.lastName = ko.observable('').extend({ required: true });
    this.phoneNumber = this.firstName = ko.observable('').extend({ required: true });
    this.email = ko.observable('').extend({ required: true });        
}

Removing that cruft results in less bulky, cheaper models.

If you try this example, you will notice that the model appears to be valid even though the validation rules are clearly violated. It took me a few minutes of browsing the source to figure out why this was happening. When you use the group functions to validate your model, they will by default only look at first level properties. So if you have a composite model, you need to modify the grouping validation configuration, and set the deep property to true.
ko.validation.init({ grouping : { deep: true, observable: true } });

Sunday, May 12, 2013

IDDD Tour notes (2/2)

This is the second and last part of my notes I scribbled down attending the IDDD Tour. The first part was published last week.

A better model
Even if you come up with a better model, the fact that it has been the ubiquitous language of the domain for decades proves that it works for them.
This quote bothers me a bit. There definitely is truth to this, but modeling an existing process often presents such a great opportunity to revise and improve it. Naked models don't conceal deficiencies, inefficiencies and aberrations. Exploring alternative models free of habituation, politics and legacy is dirt cheap, while the outcome could considerably benefit all. It seems such a shame not to take advantage of this. As with most things, know when to pick your fights.

Elegance
Elegance is for dressing, not for delivering software.
This is one to remember; I'll be using this one next time someone uses elegance as an argument for gold plating.

Cultivating models
Models grow; you will never have the best model from the start. Improve them every time you pass by.
Your first attempt at it is hardly ever right. Don't beat yourself up over it. The best models are the result of multiple iterations.
In general I consider dwelling on one problem too long to be a waste of time. Settle for good enough, and allow the better solution to emerge by itself over time.
When I feel a design is mighty important, I might accelerate this process by iterating over it multiple times in just a few days; asking for constant feedback, carrying the problem with me everywhere I go, always challenging it, and molding it in different shapes until one sticks.

Reuse
Lots of people use a shared kernel just for reuse. It's often not worth it.
People are so obsessed with the DRY principle, and the dogma of avoiding duplication, it often does more harm than good. Nonexistent concepts are introduced just to spare a few duplicate lines of code, while they will hinder and complicate autonomous evolution.

REST and DDD
Don't expose your domain model over REST: expose use cases. 
You want to enable relentlessly evolving your domain model without breaking clients. In practice you would post intent-revealing command resources, while hypermedia guides you in navigating to subsequent commands.

Published language
How is the language agreed upon; over lunch or by a committee?
 A great question which reveals if a language should be really considered as published.

And that's the last of it. If you thought some of these were interesting, you should probably get the book. I finished Growing Object-Oriented Software Guided by Tests last week, so I finally get to read it myself after having it collect dust for two months.

Sunday, May 5, 2013

IDDD Tour notes (1/2)

Two weeks ago I got to spend four days attending the IDDD Tour by Vaughn Vernon. Although my book queue has only allowed me to shallowly browse the book, I had high hopes for this course. I anticipated a week of getting lectured on DDD with a few practical exercises, but was blown away by the openness and interaction promoted by Vaughn and his associate Alberto Brandolini. A passionate group, engaging workshops, long days and lots of sharing made these few days exceptionally satisfying and inspirational. I'm grateful to those who got this show on the road; it was more than worth your trouble.

Over these few days I scribbled down some things I thought were worth remembering or worth some more thought. Most of these are aimed at the strategic side of DDD and the softer side of software since that's where I acquired most new insights.

Complexity
If we really understood the complexity of a system, we would very often make different decisions from when designing new systems. 
Understanding the complexity of a new system is hard. Modeling can serve as the perfect tool to help you expose the hidden complexities as early as possible.
Often a system appears to be not much more than CRUD, to quickly evolve into something with a lot more behaviour than anticipated. Make sure you don't sabotage growing your design by picking an architecture or framework that doesn't allow you to.
I often start with just exposing commands and queries to my application, which encapsulate all domain logic. Behind these command- and queryhandlers is very little abstraction, just some basic building blocks; aggregates, entities and value objects. Later on I can relentlessly refactor everything behind these commands and queries without breaking application code: introduce domain services, events, separate the write- from the read model etc...

Change
How do you bring in DDD? Gather a small cluster of motivated silent people and work on a project that has lots of potential value.
This is my recipe for change as well. Find a few like-minded people that are confident drawing outside the lines set out by the status quo, work in the shades, working your way towards the light.

Brown
There is always brown, even when it's under the green. 
Every application has parts which are less pretty, even green fields; use proper encapsulation to contain them, and avoid them corrupting other parts of the system.

 Another brick in the wall
Break down that paper wall between domain experts and developers. Learn the favorite coffee of the domain expert.
Lots of organizations still build this paper wall between domain experts and developers. But paper doesn't answer to questions, nor does it carry nuances very well. Communicating on a more personal level is key to tearing down that wall. Breaking the ice can be as easy as treating your domain expert to coffee.

One language
Which language do you use when naming things in code? Yours. 
I don't think native English speakers understand just how awkward it is to write code in something other than English. Keywords, API's, documentation... it's all in English. I feel that putting these together with your own language results in a cacophony of words.
In my current project the business is bilingual; they use Dutch and French names for the same concept interchangeably. We tried to find middle ground by introducing English names, and have a map between these languages. Ideally there would only be one language though.
Someone mentioned that they use English for all globally unambiguous concepts such as account number, name... but use their mother tongue in scenarios where nuances would be lost in translation. This seems to be an attractive compromise.

Defining a bounded context
A bounded context is where one ubiquitous language is consistent.
This is by far the simplest definition of a bounded context.

Bounded context relations
You say please, so I am upstream.
The relationship between bounded contexts is expressed by saying one is upstream or downstream. I thought of the quote above to be a creative expression that can aid in explaining these two concepts.

Rewrites
When you rewrite an application, you have to look at the existing forces in an organization; chances are the previous team wasn't terrible either.
Tons of factors influence the outcome of a project; it's not just about teams and their skillset. Some forces in an organization can make it close to impossible to ship good software; fear of change, lack of vision, bone-dry information streams... So don't assume your rewrite will fix everything.

Surprise
Adding surprise value is often not that valuable.
This seems to be related to the mantra of 'good enough'. Sometimes we - with the best intentions - invest more time and money in solving a problem in the best possible way, than it will ever return.

These were some notes I wrote down the first two days. I'll try to go through the other half over these next few days. I hope you found some of them valuable too.