Thursday, November 3, 2011

Validation Shouldn't Prevent Saving

I know, I've covered this, right?  But Martin Fowler just tweeted a link to something he wrote a while back and I think it adds some important context to the overall idea.  Specifically, the last paragraph of what he wrote:
"In About Face Alan Cooper advocated that we shouldn't let our ideas of valid states prevent a user from entering (and saving) incomplete information. I was reminded by this a few days ago when reading a draft of a book that Jimmy Nilsson is working on. He stated a principle that you should always be able to save an object, even if it has errors in it. While I'm not convinced that this should be an absolute rule, I do think people tend to prevent saving more than they ought. Thinking about the context for validation may help prevent that."
I'm reminded of a concept they had at a previous job of mine.  Their overall implementation wasn't good at all, but the business goal to which they were striving was certainly a good one.  Data should always be saved.  The idea was that validity of an object depended heavily on state.  The system they had was mainly data entry.  There was some workflow to move records from group to group, department to department, all depending on state.

But in any given department there were very few input restrictions.  Users could always save the data, even if it didn't make a whole lot of sense.  Validation occurred in the workflow when the data was to be presented to another group/department.  At that point, it needed to be valid for the target users.  That is, the person who ships it off to another group needs to have completed everything for which they are responsible.  But when the data was within a single point on the entire workflow, it was very free-form and transient.  It was a scratch pad, able to be saved and returned to at a later time.

FogBugs takes a very similar approach to user input as well.  They don't put a lot of restrictions on user input.  If a user has noticed a bug, it needs to be entered into the system.  Maybe they can't accurately describe the steps to reproduce right now, maybe they don't entirely know what "module" they're in.  Those are details, and they can be added later.  But there are no barriers to simply starting a ticket.  Whatever information the user has right now is good enough.  Let them save.  It may not be "valid" enough for a developer to receive it as a work item, but it's always "valid" enough to be recorded in the system.

Users don't like barriers.  They end up either working around them or giving up.  In the former case, you have unknown things happening in your system.  In the latter case, you're losing data.  Neither of which are very appealing outcomes.  Reducing those barriers is important.  It makes things a little more complex in the sense that pure rigidity of validation isn't quite so black and white, but state-driven validation isn't difficult to implement either.

So yes, my previous model was overly-rigid.  It served to demonstrate a point, that's all.  But adding state-driven validation within the object is just as easy, really.  Objects can have statuses, methods to move them from one status to another, etc.  Consider this model:

public class Band
{
  private string _name;
  public string Name
  {
    get
    {
      return _name;
    }
    set
    {
      // Perform state-driven validation
      _name = value;
    }
  }

  private string _genre;
  public string Genre
  {
    get
    {
      return _genre;
    }
    set
    {
      // Perform state-driven validation
      _genre = value;
    }
  }

  private Status _currentStatus;
  public Status CurrentStatus
  {
    get
    {
      return _currentStatus;
    }
  }

  private Band()
  {
    // Set the default status
  }

  public Band(string name, string genre) : this()
  {
    Name = name;
    Genre = genre;
  }

  public void MoveToNextStatus()
  {
    // Based on various states and the current status,
    // validate the object with custom logic and update
    // the _currentStatus to the next business state.
  }
}

As with any domain model, it's just business logic.  There is a business concept of a scratch pad and the validation of the model implements that concept.  More rigorous validation is then performed when an action is performed on the model, such as moving it to the next business status.  There could be various private methods to handle this internal validation, and the property setters just do some basic validation.  For example, maybe one department can't set certain fields.  Then the setters would check to see if the current status is in that department and throw an exception accordingly.

It sounds like it's a little more complicated, but honestly it makes things a lot easier from a business perspective.  Remember, writing the software is the easy part.  Getting the business to actually define what it wants to validate the software against that definition... That's the hard part.  This kind of approach, in my experience, makes it a hell of a lot easier for the business.  Many times I've sat down with a business trying to define and model their concepts and objects, and when I ask deceptively simple questions such as "What fields are required?" we invariably end up in multi-hour discussions with several other business users and subject matter experts to try to find a balance between everyone's interpretation of that business concept.  And ultimately that one definition of what fields are required ends up causing a headache for another user somewhere.

Driving the validation by the workflow is just a more natural approach for many businesses.  And supporting that in the software makes for a more natural implementation of the business.

No comments:

Post a Comment