views:

16

answers:

1

Imagine a hypothetical object with a number of attributes:

pseudocode:

class Student
{
   Name: String;
   Birthdate: DateTime;
   Height: int; //inches
   GPA: float; //"Grade Point Average"
   Citizenship: string;
}

Now the user enters in values, and the object gets them:

Name: Shelby Lake
Birthdate: 6/19/1991
Height: 63
GPA: 5.6
Citizenship: United States

And the layer with all the business logical can validate it:

BusinessLayer.ValidateStudent(student);

In this example it could, for example, throw an exception:

ClientException
GPA cannot exceed 4.00 (5.60)

Okay, fine. But not everything the user types in can "fit" inside the object:

Name: Shelby Lake
Birthdate: 19 years ago
Height: 5'3
GPA: n/a
Citizenship: n/a

The fact that our users are allowed to enter in more friendly values in a business decision. At the same time there are global business rules that decide when some input is valid, e.g.

GPA: 5.6 (invalid)
GPA: n/a (valid)
Citizenship: n/a (valid)
Citizenship: (invalid)
Height: tall (invalid)
Height: 5'3 (valid)

My problem is, where do i store these string values, since i cannot store them purely in the object. They need to get to the business layer, which knows how to parse entered text into values.

My first thought is to change the class:

class Student
{
   Name: String;
   Birthdate: DateTime;
   BirthdateFreeForm: string;
   Height: int; //inches
   HeightFreeform: string;
   GPA: float; //"Grade Point Average"
   GPAFreeform: string;
   Citizenship: string;
}

This allows the more arbitrary values to be sent to the business layer:

Name: Shelby Lake
BirthdateFreeform: 19 years ago
HeightFreeform: 5'3
GPA: 4.6 Citizenship: n/a

BusinessLayer.ValidateStudent(student);

And the business layer can convert free-form values into canonical values, reporting any errors:

ClientException
Country of citizenship must be entered ("n/a")

But that seems like such an ugly answer i don't even want to consider it.

What's the enterprisey way to parse user input with business rules?

+2  A: 

One approach I've taken when data entered by the user differs significantly from the way the model represents data is to use a specific class for a view model, and provide helper methods within this class to translate that view model to a proper domain object:

 class StudentViewModel {
    StudentViewModel(Student s) {
       // map the properties of Student to the appropriate view model 
       // properties.
    }

    StudentViewModel() {
       // use this for creating a new student.
    }

    Name: string
    Height: string
    GPA: string
    // etc.

    Student GetUpdatedStudent() {
        // map your view model properties to the Student class 
        // and return an updated Student.
    }
 }

This approach is extremely flexible, although there is some additional work involved. You can validate directly against the ViewModel rather than the domain object, and be guaranteed that your models do not need to accomodate against invalid data, which allows them to focus on their true purpose of representing your domain, rather than constantly guarding against invalid data.

In addition, this approach becomes incredibly useful when you have a "flat" user experience that translates into a deep object graph.

Ryan Brunner
+1 Slick.......
Ian Boyd