Rules Engine
Rules engines are extremely valuable for validating business rules. Recently I needed a framework and I looked for some implementations. Some examples concentrated on translating rules in a database to rule object using expressions. I was not interested in that. Then I looked at (https://rulesengine.codeplex.com/) which is built around rule classes and relying on fluent interface. While studying this great project, I asked myself, do you really need all that stuff and can't you do it far more simple so everybody can extend more easily. In fact you can. The core of the engine exists of a few methods, a few classes and one interface. It is easily extendable and fluent and can be modified towards your validation needs.
Separation of Definition and Execution and a Fluent Interface
It is nice to separate the definitions of rules from the applying of the rules on a concrete object. Let's first study separation with a simple example:
private Func<string, string> ConcatSeparation(string Add)
{
return (x => x + " " + Add);
}
public void Test()
{
var defineConcatone = ConcatSeparation("says something");
var defineConcatonanother = ConcatSeparation("answers him");
Console.WriteLine(defineConcatone("Bart"));
Console.WriteLine(defineConcatonanother("Eric"));
}
A Func
delegate as a return type is a natural way to construct the separation of definition and execution.
A Fluent interface is a kind of method chaining, making code more readable. For a rules engine, you chain the validation methods to apply.
How can we code a Fluent interface? I have seen a lot of approaches but the simplest one, in my opinion, is to follow the following construct. Method chaining is easy when the input argument and the return argument are of the same type or generic type. Just look at the next example:
public class TestObject
{
public string name { get; set; }
}
public static class TestObjectExt
{
public static TestObject Bind(this TestObject source, Func<TestObject, string> changeName)
{
return new TestObject() { name = changeName(source) };
}
}
var fluent = new TestObject().Bind(x => "ChangeName").Bind(x => "new changed name");
Console.WriteLine(fluent.name);
You don't need to make it more difficult than this. Although you must take into account complicating factors on covariance and contravariance, which issues I leave aside here.
Rules Engine and State
The final ingredient is State. During the execution of all the rules, we have to keep track of the validation results and collect them. State can be incorporated when we use the following delegate:
public delegate IValidationPair<V, ValidationResults>
ToValidationPaired<in I, out V, out ValidationResults>(I i);
This delegate is a definition of the following: giving an input of type I, provide an output of V and ValidationResults
. This ValidationResults
is our state. The output is a pair of value and state. In our case, the type V is the type of the instance to apply the rule on. An interesting part of this definition is that given a input of type I, we can return a value of another type (type V). We can start the chain with type I, and end the chain with a validation on another type.
Putting It Together
The above delegate returns a value/state interface. In our example, this interface is implemented in the Pair
class.
public interface IPair<out V, out S>
{
V Val { get; }
S State { get; }
}
public class Pair<V, S> : IPair V, S
{
public S State { get; private set; }
public V Val { get; private set; }
public Pair(V val, S state)
{
Val = val;
State = state;
}
V IPair<V, S>.Val
{
get { return Val; }
}
S IPair<V, S>.State
{
get { return State; }
}
}
The only thing left is to implement a similar TestObject Bind
method for our Pair
. Here it is:
public static ToValidationPaired<I, I,
ValidationResults> Init<I>(Func ValidationResults createState)
{
return i => new Pair<I, ValidationResults> (i, createState());
}
public static ToValidationPaired<I, V, ValidationResults> Bind<I,
V>(this ToValidationPaired<I, V, ValidationResults> pair, Func<V, ValidationResult> func)
{
return (i =>
{
var statepair = pair(i);
if (statepair.Val == null) {
return statepair;
}
var validating = func(statepair.Val);
if (validating != null && !validating.Valid)
{
statepair.State.Errors.Add(validating.Mgs);
}
return statepair;
});
}
public static Func<I, IEnumerable string>>
Return<I, V>(this ToValidationPaired<I, V, ValidationResults> pair)
{
return (i => pair(i).State.TheErrors);
}
These three functions make up the core of our Rule engine, nothing more nothing less! The init
function creates the initial object that will be chained. In this Init
function we create, when applied on an instance of an object, a new ValidationResults
objects that holds our validation results. The init
object is of the delegate type. The Bind
method is of the same type, and has an input argument that is the same as the return type and therefore can be used in fluent interface chaining. The Return
method unwraps the pair and returns the errors stored on our state object. It returns exactly what a rules engine is about: validation errors.
We can already write:
private static Func<Basket, IEnumerable string> somerules;
somerules = ValidationPair.InitBasket(() => new Rules.Engine.ValidationResults())
.Bind(x => x.CanAdd())
.Bind(x => x.HasBasketOwner())
.Return();
Executing the rules looks like:
var errs = somerules(mybasket);
Extensions
Init
, Bind
and Return
make up the core construct of our Rules engine. You can add your own extensions. Say you want to apply a rule to a collection, then you add the following method:
public static ToValidationPaired<I, V, ValidationResults> Bind<I,
V, W>(this ToValidationPaired<I, V, ValidationResults> pair, Func<I,
IEnumerable<W>> list, Func<W, ValidationResult> func)
{
return (i =>
{
var statepair = pair(i);
if (statepair.Val == null)
{
return statepair;
}
foreach (W item in list(i))
{
var validating = func(item);
if (validating != null && !validating.Valid)
{
statepair.State.Errors.Add(validating.Mgs);
}
}
return statepair;
});
}
And make you call:
var colrules = ValidationPair.Init<Basket>(() => new Rules.Engine.ValidationResults())
.Bind(x => x.Mylist, y => y.NotNullName(y))
.Return();
If you need to proceed with another object instead of the basket, you define another extension like:
public static ToValidationPaired<I, V, ValidationResults>
Bind<I, V>(this ToValidationPaired<I, I, ValidationResults> pair, Func<I, V> func)
{
return (i =>
{
var statepair = pair(i);
if (statepair.Val == null)
{
return new Pair V, ValidationResults (default(V), statepair.State);
}
return new Pair V, ValidationResults (func(statepair.Val), statepair.State);
});
}
And the caller code will be:
var complextyperules = ValidationPair.Init<Basket>(() => new Rules.Engine.ValidationResults())
.Bind(x => x.HasPolicyOwner()) .Bind(x => x.CanAdd())
.Bind(x => x._basketholder) .Bind(o => o.NotNullNamc(x));
And if you want, you can add conditional branching as well. I leave that to the user, because it is more of the same.
Modify the Implementation
Because the code is so concise, you can easily modify this implementation to your needs. You can add properties on the ValidationResults
object like StopValidationAtFirstFault
or whatever.
Just pass the correct parameters to the init
method and add some extra checks in the other extension methods (return immediately, if state object already has a failure, which is highly effective).
You can use your own ValidationResult
or ValidationResults
class. This will also not involve too much work. An alternative is raising validation failure events. Change your validation methods to methods with no return value, change the Func
delegate to an Action
delegate and use the RX-framework to subscribe and query the fired events. This approach is very valuable if you have to filter all kind of validations in combination with various strategies. Whatever your needs, I don't think it takes too much to change the current implementation. The reason for this is that this implementation is a kind of state monad and monads are extremely powerful.
Encapsulation
In the example that comes with this article, I have to pinpoint another very important aspect. A Rules Engine is most useful when it applies to business logic that is properly encapsulated.
The rules apply to a (shopping) Basket
, which is normally stored in a WEB-Session object. In a lot of implementations, an object is stored in a web Session variable and is accessible and mutable from everywhere in a site. In most implementations, the member variables, fields and properties are public
.
In this case, however, the Basket
is put in a separate assembly with no direct coupling to any UI or persistence storage (Web session). The Basket
and the items in the Basket
cannot be modified directly. The Basket
is initialized using a repository interface. The only way to change something in the Basket
is through simple commands like (Commands.AddPhoneItemCommand
). A command can only be processed by one handler and has no return value a caller can work on. Therefore the command handler that processes a command is the place where you can apply the rules of your engine. You define separate set of rules for separate commands. This makes the code more readable and easier to modify. Just like a fluent interface, this approach is attributing to the readability and maintainability of your system.
The Basket
reports state changes back through messages. The code includes a separate assembly where this command/messageprocessor is implemented. Therefore the Basket
is decoupled from the concrete implementation of the database- and UI layer. It can be easily tested in a test environment.
The other advantage of this approach is that the programmer of those two layers doesn’t need to study or know the internal interface or implementation of the Basket itself. The only things at his disposal are very simple commands and messages, that speak for themselves. This technique is central in a CQRS-approach, but that is an issue to be dealt with in another place and time.
Stripped Version
If you are only interested in the engine as such, and not validation and encapsulation, just download the stripped example that I have included and play with that.
Core Classes
To study this code, concentrate on the following assemblies and classes:
BMCommonInterfaces
: IPair
and Pair
: encapsulate the 'state' delegate
BMInterestedItemsBusiness
: /Rules/Engine: Validation* : Define Rule engine state/fluent interface
BMInterestedItemsBusiness
: Handler
/ItemHandler
: Rule engine rules and invoking of the rules
BMMessenger
: Command/Messenger implementation