10
votes

Data validation should occur at the following places in a web-application:

  • Client-side: browser. To speed up user error reporting
  • Server-side: controller. To check if user input is syntactically valid (no sql injections, for example, valid format for all passed in fields, all required fields are filled in etc.)
  • Server-side: model (domain layer). To check if user input is domain-wise valid (no duplicating usernames, account balance is not negative etc.)

I am currently a DDD fan, so I have UI and Domain layers separated in my applications.

I am also trying to follow the rule, that domain model should never contain an invalid data.

So, how do you design validation mechanism in your application so that validation errors, that take place in the domain, propagate properly to the client? For example, when domain model raises an exception about duplicate username, how to correctly bind that exception to the submitted form?

Some article, that inspired this question, can be found here: http://verraes.net/2015/02/form-command-model-validation/

I've seen no such mechanisms in web frameworks known to me. What first springs into my mind is to make domain model include the name of the field, causing exception, in the exception data and then in the UI layer provide a map between form data fields and model data fields to properly show the error in it's context for a user. Is this approach valid? It looks shaky... Are there some examples of better design?

2
You should consider designing a form with a uniqueness validation button and all it does is perform a query. Then when you submit the form you can let the database throw the uniqueness exception and the service layer can catch it and wrap it in a nice http response. - danfromisrael
@danfromisrael Well, that HTTP response should rerender the form, with the field, caused duplication error exception, marked in red, right? So, I just wonder how to design that. - Vladislav Rastrusny
it depends how your application is architechtured. http response can return a json that your client app will use or it can render and return the HTML. either case the idea is to separate command/query and that the uniqueness check will be done before the user submits the form. when she does, you execute a command via application and domain layer and let the DB make sure the uniqueness is correct and throw if its not. then you catch it wherever you want to report the error to the user (in your case the html renderer but it could easily be http response) - danfromisrael
@danfromisrael Whatever architecture I can choose, there is a need to map domain logic error to the actual client UI form field. And that was exactly the question. See solution by theDmi for example. - Vladislav Rastrusny
@VladislavRastrusny might take a look at this wonderful answer from Steven, for a probably related idea. - kayess

2 Answers

5
votes

Although not exactly the same question as this one, I think the answer is the same:

Encapsulate the validation logic into a reusable class. These classes are usually called specifications, validators or rules and are part of the domain.

Now you can use these specifications in both the model and the service layer.

If your UI uses the same technology as the model, you may also be able to use the specifications there (e.g. when using NodeJS on the server, you're able to write the specs in JS and use them in the browser, too).

Edit - additional information after the chat

  • Create fine-grained specifications, so that you are able to display appropriate error messages if a spec fails.
  • Don't make business rules or specifications aware of form fields.
  • Only create specs for business rules, not for basic input validation tasks (e.g. checking for null).
2
votes

I want to share the approach used by us in one DDD project.

  • We created a BaseClass having fields ErrorId & ErrorMessage.
  • Every DomainModel derive from this BaseClass & thus have a two extra fields ErrorId & ErrorMessage available from BaseClass.

  • Whenever exception occurs we handle exception(Log in server, take appropriate steps for compensating logic & fetch User Friendly message from client location based localized Resource file for message ) then propagate data as simple flow without raising or throwing exception.

  • At client side check if ErrorMessage is not null then show error.

It's basic simple approach we followed from start of project.

If it's new project this is least complicated & efficient approach, but if you doing changes in big old project this might not help as changes are big.

For validation at each field level, use Validation Application Block from Enterprise Library.

It can be used as :

Decorate domain model properties with proper attributes like:

public class AttributeCustomer 
{
    [NotNullValidator(MessageTemplate = "Customer must have valid no")]
    [StringLengthValidator(5, RangeBoundaryType.Inclusive, 
        5, RangeBoundaryType.Inclusive, 
        MessageTemplate = "Customer no must have {3} characters.")]
    [RegexValidator("[A-Z]{2}[0-9]{3}", 
    MessageTemplate = "Customer no must be 2 capital letters and 3 numbers.")]
    public string CustomerNo { get; set; }
}

Create validator instance like:

Validator<AttributeCustomer> cusValidator = 
            valFactory.CreateValidator<AttributeCustomer>();

Use object & do validation as :

customer.CustomerNo = "AB123";
customer.FirstName = "Brown";
customer.LastName = "Green";
customer.BirthDate = "1980-01-01";
customer.CustomerType = "VIP";

ValidationResults valResults = cusValidator.Validate(customer);

Check Validation results as:

if (valResults.IsValid)
{
    MessageBox.Show("Customer information is valid");
}
else
{
    foreach (ValidationResult item in valResults)
    {
        // Put your validation detection logic
    }
}

Code example is taken from Microsoft Enterprise Library 5.0 - Introduction to Validation Block This links will help to understand Validation Application Block:

http://www.codeproject.com/Articles/256355/Microsoft-Enterprise-Library-Introduction-to-V

https://msdn.microsoft.com/en-in/library/ff650131.aspx

https://msdn.microsoft.com/library/cc467894.aspx