NCommons Rules Engine

On June 28, 2010, in Uncategorized, by derekgreer

I recently decided to invest some time to learn how my team might leverage the MvcContrib rules engine for our projects at work. I discovered this feature after browsing the CodeCampServer source which seems to be the only publicly available example of the rules engine in use. I was impressed at how clean the controller actions were as a result of leveraging this feature in conjunction with some additional infrastructure sugar the CodeCampServer adds.

While I liked the capabilities of the MvcContrib rules engine overall, there were a few aspects I wanted to change.  One of those things was the coupling to the validation strategy provided by the rules engine and another was the need for a bit of additional infrastructure code to help parse the results when mapping validation errors back to their corresponding UI elements.

I also recently became aware of the Fluent Validation library by Jeremy Skinner and thought to myself: “I wonder how long it would take to just create my own rules engine leveraging the Fluent Validation framework“, so I sat down last weekend to find out. Well, after getting something up and going, I thought I might as well share my results. I should note that given my effort was inspired by the MvcContrib rules engine, some of the same concepts are reflected in my effort (though perhaps with a more naive implementation).

Overview

The basic rules engine works as follows:

  1. A user invokes some Controller action.
  2. The Controller takes the action’s parameter and invokes the RulesEngine.Process() method.
  3. The RulesEngine invokes an injected implementation of IRuleValidator.Validate() on the object.
  4. The IRulesValidator returns a RuleValidationResult denoting the status of the validation as well as containing any validation error messages.
  5. The RulesEngine uses the Common Service Locator to find implementations of ICommand<T> where T matches the type of the object.
  6. The implementations of ICommand<T> are executed and any results accumulated.
  7. The RulesEngine returns a ProcessResult which contains the process status and any validation messages and return items.
  8. The Controller uses the status to determine which action to display. In the event of a validation failure, the error messages are added to the ModelState with their associated property names.

Here is an example usage:

[HttpPost]
public ActionResult Create(ProductInput productInput)
{
    ProcessResult results = _rulesEngine.Process(productInput);

    if (!results.Successful)
    {
        CopyValidationErrors(results);
        return View(productInput);
    }

    return RedirectToAction("Index");
}

void CopyValidationErrors(ProcessResult results)
{
    foreach (RuleValidationFailure failure in results.ValidationFailures)
    {
        ModelState.AddModelError(failure.PropertyName, failure.Message);
    }
}

In an example application I’ve included with the source, I created an implementation of the IRulesValidator which adapts to the Fluent Validation library:

public class FluentValidationRulesValidator : IRulesValidator
{
    public RuleValidationResult Validate(object message)
    {
        var result = new RuleValidationResult();
        Type validatorType = typeof (AbstractValidator<>).MakeGenericType(message.GetType());
        var validator = (IValidator) ServiceLocator.Current.GetInstance(validatorType);
        ValidationResult validationResult = validator.Validate(message);

        if (!validationResult.IsValid)
        {
            foreach (ValidationFailure error in validationResult.Errors)
            {
                var failure = new RuleValidationFailure(error.ErrorMessage, error.PropertyName);
                result.AddValidationFailure(failure);
            }
        }

        return result;
    }
}

This uses the Common Service Locator to find types closing the Fluent Validation AbstractValidator generic type, validates the message with the validator, and adds any validation error messages to the RuleValidationResult.

Mapping

I also included the ability to map UI types to domain types using a library such as AutoMapper. To express this as an optional feature, I created a MappingRulesEngine which has a dependency on an IMessageMapper. At first I went back and forth between expressing an IMessageMapper as an optional dependency to the RulesEngine, but I don’t really like property injection and the notion of using constructor injection for optional dependencies was a bit distasteful, so using inheritance felt like the cleanest and most expressive option.

The MappingRulesEngine uses the Common Service Locator to find a type closing AssociationConfiguration<T> which provides the type to be converted to as well as configuration used by the MappingRulesEngine to associate validation error messages on the domain type back to the origin properties on the UI type. The following is an example usage:

public class ProductInputAssociationConfiguration : AssociationConfiguration<ProductInput>
{
    public ProductInputAssociationConfiguration()
    {
        ConfigureAssociationsFor<Product>(x =>
            {
                x.For(output => output.Id).Use(input => input.Id);
                x.For(output => output.Description).Use(input => input.Description);
                x.For(output => output.Price).Use(input => input.Price);
            });
    }
}

That’s about it. You can get the source at http://github.com/derekgreer/ncommons.

Tagged with:  
  • Dm

    Good article. But one thing that seems strange to me is why to perform validation in boundary of UI ? Conceptually it is more related to the domain. The only input validation required on the UI is to check the proper types of values so they can be used for domain entities. Doing validation in UI will lead to necessity to duplicate it for all kinds of UIs. What do you think ?

    • Derek

      For Web applications, input validation should generally be performed on both the client and server side. In Domain-Driven Design terms, the approach shown here is facilitating validation through components living in the Infrastructure Layer which are configured and initiated from the Application Layer, not the User Interface Layer.

      That said, it sounds like you’re questioning the practice of not encapsulating the validation within the domain model objects themselves. This article isn’t really concerned with what type of validation is being performed, but validation can be classified in different ways. One of the most important distinctions is contextual verses invariant validation. Domain objects should always encapsulate validation for invariant data, but contextual validation is best treated as a separate concern. For example, a class named FibonacciNumber shouldn’t ever be instantiated with a non-fibonacci sequence, but n Contact class consisting of home addresses, business addresses, phone numbers, emails, etc. might be considered valid and invalid in different contexts. Such contextual validation shouldn’t be encapsulated within the business objects as that would violate the Open/Closed principle.

      I hope this helps.