Created
April 4, 2012 15:13
-
-
Save PaulUpson/2302490 to your computer and use it in GitHub Desktop.
Validation within Aggregate Roots - Part 2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IValidationHandler<in T> where T : Command { | |
ValidationResult Validate(T cmd); | |
} | |
public interface ICommandValidator { | |
ValidationResult Validate<T>(T command) where T : Command; | |
} | |
public class CommandValidator : ICommandValidator { | |
private readonly IDictionary<Type, Func<object, ValidationResult>> _validationHandlers | |
= new Dictionary<Type, Func<object, ValidationResult>>(); | |
public void RegisterValidationHandler<T>(Func<T,ValidationResult> handler) where T : Command { | |
_validationHandlers.Add(typeof(T), o => handler((T) o)); | |
} | |
public ValidationResult Validate<T>(T command) where T : Command { | |
Func<object, ValidationResult> handler; | |
if (!_validationHandlers.TryGetValue(command.GetType(), out handler)) | |
return new ValidationResult(); | |
try { | |
return handler(command); | |
} | |
catch(TargetInvocationException ex) { | |
throw ex.InnerException; | |
} | |
} | |
} | |
public class ValidationResult { | |
private readonly List<ValidationFailure> errors = new List<ValidationFailure>(); | |
public bool IsValid { | |
get { return Errors.Count == 0; } | |
} | |
public IList<ValidationFailure> Errors { | |
get { return errors; } | |
} | |
public ValidationResult() { } | |
public ValidationResult(IEnumerable<ValidationFailure> failures) { | |
errors.AddRange(failures.Where(failure => failure != null)); | |
} | |
} | |
public class ValidationFailure { | |
public ValidationFailure(string propertyName, string error) | |
: this(propertyName, error, null) {} | |
public ValidationFailure(string propertyName, string error, object attemptedValue) { | |
PropertyName = propertyName; | |
ErrorMessage = error; | |
AttemptedValue = attemptedValue; | |
} | |
public string PropertyName { get; private set; } | |
public string ErrorMessage { get; private set; } | |
public object AttemptedValue { get; private set; } | |
public override string ToString() { | |
return ErrorMessage; | |
} | |
} | |
// Class to encapsulate all command validation rules for an Aggregate Root | |
public class PatientValidationHandler : IValidationHandler<AddReferredPatient>, IValidationHandler<ChangePatientAddress> { | |
public bool Validate(AddReferredPatient command) { | |
var validationErrors = new List<ValidationFailure>(); | |
if (string.IsNullOrWhitespace(command.Firstname)) | |
validationErrors.Add("firstname", "First Name is required"); | |
return new ValidationResult(validationErrors); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Register your command validators as you might your command handlers (N.B. this could be done with an IoC or reflection to auto-wireup) | |
var patientValidator = new PatientValidationHandler(); | |
validator.RegisterValidator<AddReferredPatient>(patientValidator.Validate); | |
validator.RegisterValidator<ChangePatientAddress>(patientValidator.Validate); | |
// Then on all command sending | |
protected void Send<T>(T cmd) where T : Command { | |
// Validate the command | |
var result = Validator.Validate(cmd); | |
// if invalid set Model Error warnings and be done | |
if (!result.IsValid) { | |
SetModelErrors(result.Errors); | |
} | |
else { // if valid try a send | |
try { | |
Bus.Send(cmd); | |
} | |
catch (DomainValidationException ex) { //only catch business rule failures that can only be validated from within the AR | |
SetModelErrors(ex.Errors); | |
} | |
} | |
} | |
// since both command and domain validation return the same error collection use the same process to strip them out | |
private void SetModelErrors(IEnumerable<ValidationFailure> errors) { | |
foreach (var error in errors) { | |
ModelState.AddModelError(error.PropertyName, error.ErrorMessage); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks Craig, that was what I was getting at. Chris, I thought the original focus was on validation strategies so I'd extended my examples in that direction. I agree that getting a stable set of stories agreed is necessary for taking the discussion regarding the specifics of the aggregate roots forward.
Can I just caveat my responses with the fact that I'm currently working in a completely separate and unrelated domain and am transposing my examples over. Therefore loosing the true business context and fitting the above solutions to contrived examples in the healthcare domain. I appreciate that aggregate roots (once defined) in our shared healthcare context may not behave the same as mine in my current context, but I felt that the discussions regarding validation transcended these domain specifics.
Can I suggest that we start a new gist focused on clarifying the ARs?