We want to use Evil (Entity Validation Library) do DTO validation on UI layer. I then changed Jimmy’s pattern to use the same style as Evil. One big benefit is removing the out keyword in method, Igloo coder said using out is a procedural code smell.
Some principals for validation:
- Domain entity should always be valid, even right after new(). ctor has to take all required fields to make a fresh created entity valid.
- Never try to valid the entity retrieve database, instead, a data clean up should be done before app released. Or create a cc.net routine to constantly check data on those shared database?
- Never try to positionally make a entity invalid, always try valid before making changes, not after then rollback.
// in Organization
public void AddEmail(Email emailToAdd)
{
// Before add, check duplication
IEnumerable<string> brokenRules;
var emailValidator = new EmailValidator(emailToAdd);
if (!emailValidator.IsValid()) throw new ValidationException(emailValidator.ValidationErrors);
var dupValidator = new NoDuplicateEmailAddressValidator(_emails, emailToAdd);
if (!dupValidator.IsValid()) throw new ValidationException(dupValidator.ValidationErrors);
_emails.Add(emailToAdd);
}
public class EmailValidator : BaseValidator
{
public EmailValidator(Email email)
{
if (new SomethingWentWrongSpec.IsSatidsiedBy(entity)
AddValidationError("Email address has something went wrong. ");
}
public class BaseValidator
{
private IList<string> _validationErrors = new List<string>();
public IEnumerable<string> ValidationErrors
{
get { return _validationErrors; }
}
public bool IsValid()
{
return ValidationErrors.Count() == 0;
}
protected void AddValidationError(string error)
{
_validationErrors.Add(error);
}
protected void AddValidationErrors(IEnumerable<string> errors)
{
_validationErrors.Union(errors);
}
}
}
I tried Specification pattern in Domain entities, it’s neat for its nice re-wrote if condition, but the native Specification can’t process error messages, so I just use the validator directly in Domain entity class.