Skip to content

Instantly share code, notes, and snippets.

@PaulUpson
Created April 4, 2012 15:13
Show Gist options
  • Save PaulUpson/2302490 to your computer and use it in GitHub Desktop.
Save PaulUpson/2302490 to your computer and use it in GitHub Desktop.
Validation within Aggregate Roots - Part 2
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);
}
// 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);
}
}
@PaulUpson
Copy link
Author

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment