A reference architecture (part 10)

…continued from part 9

Validation

In the previous part we made it possible to throw business exceptions when something goes wrong in the business logic. However, next to that we also need to do validation, which basically means checking whether properties have valid values.

In the case of validation in request handlers, we can use first check whether the request itself is correct before executing the actual logic. If there is something wrong with the request, like a required property not set, then it makes no sense to continue.

In the case of a domain entity, we can put validation at creation phase, to avoid that we can never create invalid domain entities. As such the best place is in its constructor.

The validation logic itself could be placed in the domain entities and request handlers themselves, but I find it cleaner to separate them out and move that responsibility to dedicated validators. The main concern of entities should be keeping track of business state. Another advantage is that you could create validators that are shared and so reusable inside other validators, like for example an EmailAddressValidator.

My validation framework of choice is FluentValidation. It offers everything we need, so download it, and put the FluentValidation.dll assembly in \Trunk\Libraries\FluentValidation.

Add domain entity validation

In the ArchitectureExample.Domain.Entities project, add the following validators:

public class BlogEntryValidator : AbstractValidator<BlogEntry>
{
public BlogEntryValidator()
{
RuleFor(blogEntry => blogEntry.Title).NotNull().NotEmpty().Length(1, 200);
RuleFor(blogEntry => blogEntry.Body).NotNull().NotEmpty().Length(1, 2000);
RuleFor(blogEntry => blogEntry.LastModifiedDate).NotEqual(DateTime.MinValue);
}
}

public class CommentValidator : AbstractValidator<Comment>
{
public CommentValidator()
{
RuleFor(comment => comment.Name).NotNull().NotEmpty().Length(1, 50);
RuleFor(comment => comment.EmailAddress).NotNull().NotEmpty().Length(1, 200).EmailAddress();
RuleFor(comment => comment.CommentText).NotNull().NotEmpty().Length(1, 1000);
RuleFor(comment => comment.DateWritten).NotEqual(DateTime.MinValue);
}
}

And in the entities, call validation in the constructor:

public class BlogEntry
{
private BlogEntry() {}
public BlogEntry(string title, string body)
{
LastModifiedDate = DateTime.Now;
Title = title;
Body = body;

var blogEntryValidator = new BlogEntryValidator();
blogEntryValidator.ValidateAndThrow(this);
}
}

public class Comment
{
private Comment() {}
public Comment(BlogEntry blogEntry, string name, string emailAddress, string commentText)
{
BlogEntry = blogEntry;
Name = name;
EmailAddress = emailAddress;
CommentText = commentText;
DateWritten = DateTime.Now;

var commentValidator = new CommentValidator();
commentValidator.ValidateAndThrow(this);
}
}

Now, because the ValidateAndThrow operation throws a ValidationException, we have to extend the GlobalExceptionHandler so that it can handle it too and convert it to a business fault:

public class GlobalExceptionHandler : IErrorHandler
{
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error is BusinessException)
{
var businessError = error as BusinessException;
var businessFault = new BusinessFault(businessError.ErrorCode, businessError.Message);
var faultException = new FaultException<BusinessFault>(businessFault, "Oops", new FaultCode("BusinessFault"));
var msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, error.TargetSite.Name);
}
else if (error is ValidationException)
{
var validationError = error as ValidationException;
var businessFault = new BusinessFault("VALIDATIONERROR", validationError.Message);
var faultException = new FaultException<BusinessFault>(businessFault, "Oops", new FaultCode("ValidationFault"));
var msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, error.TargetSite.Name);
}
else
{
var faultException = new FaultException(string.Format("{0}", error.TargetSite.Name));
var msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, faultException.Action);
}
}

public bool HandleError(Exception error)
{
return true;
}
}

Of course, this is just an example; you could create a separate ValidationFault that contains a list of errors, and throw that one in the case of a validation error; but you get the idea on how to catch exceptions in the global exception handler and throw faults based on them.

To test this:

[TestMethod]
public void AddBlogEntryWithoutTitleTest()
{
string errorCode = null, faultMessage = null;
using (var architectureExampleServiceAgent = new ArchitectureExampleServiceAgent("BasicHttpBinding_IArchitectureExampleService"))
{
try
{
var response = architectureExampleServiceAgent.AddBlogEntry(new AddBlogEntryRequest()
{
Title = null,
Body = "Blog entry body"
});
}
catch (FaultException<BusinessFault> fex)
{
errorCode = fex.Detail.ErrorCode;
faultMessage = fex.Detail.Message;
}
}
Assert.IsNotNull(errorCode);
Assert.IsNotNull(faultMessage);
}

Add request validation

Adding validation to the request handlers is about the same: you just add request validators and call them in the beginning of the execute operation of the request handlers.

Next time I’ll add logging!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s