Overview
Splitting business rules away for the traffic flow of data has become commonplace in all development for the last few years. It has a lot of advantages as it crystallizes the rules for an object into a simple definable, readable and most of all testable item.
Testable
By concentrating the rules into a object it eliminates the need to have a data set in a certain way within a datastore, and then have to go through the procedure and resetting once your done. The developer can simply write a testcase which instantiates the object and load it with with data set in a certain way, and check the result of the rules.
FluentValidation
I think FluentValidation has been misnamed, yes you can use it a as a validation engine, and it has lots of primitive methods that enable you to do that. Its real strength is in collecting those validators together so you can perform complex rules validation.
The below snippet shows a validator I created for a simple Contact class. * The first collection of RuleFor statements (Default )will execute if you call validate on a class. * The RuleSet statements, are a collection of validators to report on certain business decisions. * In this case the ‘ContactWithNoTransaction’ ruleset checks to see if * The contact was created 2 years ago * The MaxRetentionDate is NULL * And The status is Pending
So What does all this mean ? * It Means * Separate you business decisions into a encapsulated set of rules * Create specific Unit Tests to test those rules * Simplify the code, Rules engines have a specific format, Plain C# is open to experience or specialist knowledge.
        public ContactValidator()
        {
            RuleFor(x => x.Id).NotEmpty();
            RuleFor(x => x.Title).NotEmpty();
            RuleFor(x => x.FirstName).NotEmpty();
            RuleFor(x => x.LastName).NotEmpty();
            RuleFor(x => x.CreatedDate.Value).NotEmpty();
            RuleSet("ContactWithNoTransaction", () =>
            {
                RuleFor(x => x.CreatedDate.Value).NotEmpty().DependentRules(() =>
                {
                    RuleFor(x => x.CreatedDate.Value).LessThanOrEqualTo(DateTime.Today.AddYears(-2)); 
                });
                RuleFor(x => x.MaxRetentionDate.HasValue).Equal(false);
                RuleFor(x => x.Status).Equal(ContactStatus.Pending);
            });
            RuleSet("ContactMaxRetentionDateExpired", () =>
            {
                RuleFor(x => x.MaxRetentionDate.HasValue).Equal(true).DependentRules(() =>
                {
                    RuleFor(x => x.MaxRetentionDate.Value).GreaterThanOrEqualTo(DateTime.Today);
                });
                RuleFor(x => x.Status).Equal(ContactStatus.Completed);
            });
        }Particular rulesets are called, as below.
            var validator = new ContactValidator();
            var default =  validator.Validate(_contact);
            var contactWithNoTransaction = validator.Validate(_contact, ruleSet: "ContactWithNoTransaction");
            var contactMaxRetentionDateExpired = validator.Validate(_contact, ruleSet: "ContactMaxRetentionDateExpired");Simple Rules engine
UML
Participants
The classes and objects participating in this pattern are:
- IRule– Defines the interface for all specific rules.
- IRuleResult– Defines the interface for the results of all specific rules.
- BaseRule– The base class provides basic functionality to all rules that inherit from it.
- Rule– The class represents a concrete implementation of the BaseRule class.
- RulesChain– It is a helper class that contains the main rule for the current conditional statement and the rest of the conditional chain of rules.
- RulesEvaluator– This is the main class that supports the creation of readable rules and their relation. It evaluates the rules and returns their results.
Note
Most of this text has come from the DZone article listed in the references.