Hi!
During the last Dev group meeting, we’ve had a brief discussion about rules. Often the business rules are duplicated and in a layered application, are across multiple layers.
Here’s a simple implementation that will allow you to validate at either the domain layer or externalized. This will allow us to validate a set of rules against a domain object.
First off : Here’s the interface
public interface IRule<T>{
bool is_broken_by(T item);
string error_message { get; }
}
This will allow us to validate an item against a requirement, and get back a message. Simple enough.
Here’s a simlpe implementation of the IRule interface.
public class BreakableRule : IRule<T>{
protected readonly Predicate<T> predicate;
protected readonly string broken_rule_message;
public BreakableRule(Predicate<T> predicate, string broken_rule_message ){
this.predicate = predicate;
this.broken_rule_message = broken_rule_message;
}
public bool is_broken_by(T item){
error_message = string.Empty;
var is_broken = predicate.Invoke(item);
if ( is_broken ) error_message = broken_rule_message;
return is_broken;
}
public string error_message { get; protected set; }
}
Next of we create a ruleset. This will be where we collect the rules and iterate through them to validate against the domain. This will give us an emuerable list of error messages.
public interface IRuleset<T> {
IEnumerable<string> validate(T item);
bool is_valid();
}
I’ve implemented this as an abstract class that will be used as a base class for the ruleset implementation
public abstract class rule_set <T> : IRuleset <T> {
protected IList <IRule<T> > all_rules;
protected Report report;
protected bool isBroken;
protected rule_set() : this(new List <IRule<T> > ()) {}
protected rule_set( IList <IRule<T> > allRules){
all_rules = allRules;
configure_rules();
}
protected void add_a_rule(IRule<T> this_rule){
all_rules.Add(this_rule);
}
public virtual bool is_valid(){
return !isBroken;
}
public virtual IEnumerable <string> validate(T item){
var report = new Report(); // Nothing more than an object that collects the error messages
isBroken = false;
foreach (var rule in all_rules){
if (rule.is_broken_by(item)){
isBroken = true;
report.add_validation_messages(rule.error_message);
}
}
return report.final_result;
}
/// This will be be overridden by the implementation
protected abstract void configure_rules();
}
When the validate method is called it iterates through all the rules passing in the object to validate and calls the is broken method on each. This in turn evaluates the predicate and handles the error messaging.
The idea being that each ruleset will be implemented using the above base class. Its usage is simple(ish). When you
implement a rule set, override the configure_rules(), and add the new breakable rule, eg.
… protected override void configure_rules(){
add_a_rule(new BreakableRule<IPerson>(x => x.FirstName.is_null_or_empty(), "First name cannot be blank"));
}
So lets create a domain to play with all this.
public interface IPerson{
string FirstName { get; set; }
string LastName { get; set; }
int Age { get; set; }
}
public class Person : IPerson{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
The above objects are not self-validating. The rule set will validate them. So the rule set that will be used for validating the person object specifies the person must have a First and last name that is not null or empty
and the person must be greater than 21 years.
public class Person_rule_set: rule_set<IPerson>{
protected override void configure_rules(){
add_a_rule(new BreakableRule<IPerson>(x => x.FirstName.is_null_or_empty(), "First name cannot be blank"));
add_a_rule(new BreakableRule<IPerson>(x => x.LastName.is_null_or_empty(), "Last name cannot be blank"));
add_a_rule(new BreakableRule<IPerson>(x => x.Age < 21, "Person must be older than 21"));
add_a_rule(new BreakableRule<IPerson>(some_complex_rule,"No person with Q in your firstname"));
}
private bool some_complex_rule(IPerson person){
return person.FirstName.is_null_or_empty() || person.FirstName.Contains("Q");
}
}
To use this to validate an instance of a person object we do the following..
var ruleset_for_a_person = new Person_rule_set();
var error_report = ruleset_for_a_person.validate(some_person_instance);
// you can test the vaildation
if ( ! ruleset_for_a_person.is_valid() ) show_the (error_report );
Now if we extend this to domain objects that need to validate themselves create an interface IValidatable
public interface IValidatable<T> {
IEnumerable<string> validate_using(IRuleset<T> this_rule_set);
bool is_valid();
}
Implementing this on an object will allow the object to “validate” itself - well actually using the visitor pattern we can add validation to the object.
This will allow you to pass the ruleset in (stratergy pattern),
public class ValidatablePerson : IPerson,IValidatable<IPerson>{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
// STORE THE RULESET
private IRuleset<IPerson> ruleset;
public IEnumerable<string> validate_using(IRuleset<IPerson> this_rule_set){
ruleset= this_rule_set;
return ruleset.validate(this); /// ** Here’s where we ask the ruleset to validate this object **
}
public bool is_valid(){
return ruleset.is_valid(); /// ?? should we call the validate_using ???
}
}
Here’s how we validate the object
var ruleset_for_a_person = new Person_rule_set(); // Use the same rule set
var error_report = person_instance.validate_using ( ruleset_for_a_person);
// you can test the vaildation
if ( ! person_instance.is_valid() ) show_the (error_report );
So thats about it.. although we can make it a little neater to combine the rulesets to make up new rules. This ability to combine rules we tip into the fluent interface made popular by the specification pattern and an abstract class
I call “a chainer” (I’m sure there’s a better name for it ) - we’ll also wrap the fluentness into an extension class. so here goes…
public abstract class Combined_ruleset<T> : rule_set<T>
{
protected readonly IRuleset<T> first;
protected readonly IRuleset<T> second;
public Combined_ruleset(IRuleset<T> first, IRuleset<T> second) {
this.first = first;
this.second = second;
}
public override IEnumerable<string> validate(T item){
return first.validate(item).Union(second.validate(item));
}
protected override void configure_rules(){
}
}
// The AND chainer..
public class AND_ruleset<T> : Combined_ruleset<T> {
public AND_ruleset(IRuleset<T> first, IRuleset<T> second) : base(first, second) { }
public override bool is_valid() {
return first.is_valid() && second.is_valid();
}
}
// The OR chainer..
public class OR_ruleset<T> : Combined_ruleset<T> {
public OR_ruleset(IRuleset<T> first, IRuleset<T> second) : base(first, second) { }
public override bool is_valid() {
return first.is_valid() || second.is_valid();
}
}
The extension class to bring in the fluentness
public static class RulesetExtensions {
public static IRuleset<T> and<T>(this IRuleset<T> fist_specification, IRuleset<T> the_other){
return new AND_ruleset<T>(fist_specification, the_other);
}
public static IRuleset<T> or<T>(this IRuleset<T> fist_specification, IRuleset<T> the_other){
return new OR_ruleset<T>(fist_specification, the_other);
}
}
We can then use this to AND or OR together different rulesets that can be use in the validation eg.
var casino_entry_rule = new adults_only_rule().and(new must_have_name_rule());
report = some_person.validate_using (casino_entry_rule );
So now we validate an object based on a combination of rulesets. We can now vary the validated state of a object based on an externalized set of rules.
Hope that helps!
.. download the code here
Thanks
Zak