10

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?

Vladislav Rastrusny
  • 29,378
  • 23
  • 95
  • 156
  • 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 Nov 12 '15 at 06:40
  • @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 Nov 12 '15 at 07:28
  • 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 Nov 12 '15 at 10:07
  • @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 Nov 12 '15 at 11:10
  • @VladislavRastrusny might take a look at this wonderful [answer](http://stackoverflow.com/questions/4776396/validation-how-to-inject-a-model-state-wrapper-with-ninject/4851953#4851953) from _Steven_, for a probably related idea. – kayess Nov 16 '15 at 09:38
  • @kayess Yes, that's actually what *theDmi* suggested. I am just wondering how they tie form fields to domain error codes. – Vladislav Rastrusny Nov 16 '15 at 10:04
  • @VladislavRastrusny by looking at answer i have mentioned, it simply yields a `new ValidationResult()` where the `Key` parameter is the fieldname from the model. – kayess Nov 16 '15 at 10:19
  • @kayess Yes. So, this implies client form should have fields with exact names or know about domain validation field names. – Vladislav Rastrusny Nov 16 '15 at 10:20
  • @VladislavRastrusny yes, however i would rather assume the client only has the field names of viewmodel mapped from the domain model. – kayess Nov 16 '15 at 10:22
  • @kayess Yep, but... validation is done on the model, not ViewModel, right? Even if this validation was done on the ViewModel, domain model also needs to be validated because it's the only place all business rules are known. – Vladislav Rastrusny Nov 16 '15 at 10:24
  • @VladislavRastrusny I actually map my viewmodel to domain model, send that to service layer where the validation occurs, and yield the new validation exception which the UI layer can catch and apply to modelstate as in the given example. – kayess Nov 16 '15 at 10:26
  • @kayess I see. Thanks. – Vladislav Rastrusny Nov 16 '15 at 10:41

2 Answers2

5

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).
Community
  • 1
  • 1
theDmi
  • 17,546
  • 6
  • 71
  • 138
  • The problem is that data set in the UI and in the domain layer can have different structure. For example, User model has `username`, `email`, `password`, `created_at` fields. Everything is required. `password` is a hash, `created_at` is a date. UI registration form has only `email`, `password`, `repeat_password` fields. Both passwords are strings, that must be equal. How can I design validation object to validate both data sets uniformly? – Vladislav Rastrusny Nov 11 '15 at 15:26
  • The latter case is just basic input validation. This has nothing to do with domain logic. So don't use a specification for this. As soon as you deemed the input well-formed, you probably want to validate it against a business rule - this is where a reusable spec can help. – theDmi Nov 11 '15 at 15:29
  • 1
    But you have to validate email against the database to prevent duplicates. And that's part of service / business model layer. How to propagate `DuplicateEmailException` back to UI layer and associate it with form's `email` field? I agree, that I considered a very simple case, but I hope you get the idea. I can saturate my example form with other fields as well, partially connected with the model, of course. – Vladislav Rastrusny Nov 11 '15 at 15:34
  • This is a bit a different case, because you need to make requests to the server to validate a form field. Nonetheless, you can encapsulate the validation logic in a specification to make it reusable. The only difference is probably that this spec has a dependency on a repository - but I don't see why this would be a problem. – theDmi Nov 11 '15 at 15:49
  • I meant server-validation only, not ajax validation. Do you mean I should create browser form specific `Specification` for domain objects? – Vladislav Rastrusny Nov 12 '15 at 07:30
  • No, specifications should not be form-specific. They should validate a domain rule. During input validation, this is usually used in the following order: 1. basic input validation (not null, valid date, etc) 2. construct preliminary VO 3. validate VO against spec – theDmi Nov 12 '15 at 07:59
  • So, that brings us back to how to map domain rule errors to input fields in the form to show error to client? – Vladislav Rastrusny Nov 12 '15 at 09:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/94900/discussion-between-thedmi-and-vladislav-rastrusny). – theDmi Nov 12 '15 at 09:29
2

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

Pranav Singh
  • 17,079
  • 30
  • 77
  • 104