C# Design Patterns – What is the Business Rules Design Pattern?

cdesign-patterns

I'm working on an interface for implementing business rules in order to improve SOLID-ity; so I can move a lot of logic out of Web API controllers and into a business library. The Common Problem being that an action should occur if one or several conditions are met, and some of these conditions are likely to be required throughout the system with different actions as the end result. I've done some research and came up with the code below. Does this conform to an existing design pattern?
I looked in the GoF list and didn't find any matches there.

/// <summary>
/// Use for designing a business rule where conditions are evaluated and the actions are executed based on the evaluation.
/// Rules can be chained by setting the "Action" as another business rule.
/// </summary>
/// <typeparam name="TCondition">The type of the condition or conditions.</typeparam>
/// <typeparam name="TAction">The type of the action or actions to be executed.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <seealso cref="Core.Interfaces.IBusinessRule" />
internal interface IBusinessRule<TCondition, TAction, TResult> : IBusinessRule
    where TCondition : IRulePredicate where TAction : IRuleAction<TResult>
{
    ICollection<TAction> Actions { get; set; }

    ICollection<TCondition> Preconditions { get; set; }
}


internal interface IBusinessRule
{
    IEnumerable Results { get; }

    RuleState State { get; }

    Task Execute();
}

public enum RuleState
{
    None,
    Initialized,
    InProgress,
    Faulted,
    FailedConditions,
    Completed
}

public interface IRulePredicate
{
    bool Evaluate();
}

public interface IRuleAction<TResult>
{
    Task<TResult> Execute();
}


public abstract class RuleBase<TCondition, TAction, TResult> :
    IBusinessRule<TCondition, TAction, TResult> where TCondition : IRulePredicate
    where TAction : IRuleAction<TResult>
{
    public ICollection<TResult> Results { get; } = new List<TResult>();

    public ICollection<TCondition> Preconditions { get; set; } = new List<TCondition>();

    public ICollection<TAction> Actions { get; set; } = new List<TAction>();

    IEnumerable IBusinessRule.Results => Results;

    public RuleState State { get; private set; } = RuleState.Initialized;

    public async Task Execute()
    {
        State = RuleState.InProgress;
        try
        {
            var isValid = true;
            foreach (var item in Preconditions)
            {
                isValid &= item.Evaluate();
                if (!isValid)
                {
                    State = RuleState.FailedConditions;
                    return;
                }
            }

            foreach (var item in Actions)
            {
                var result = await item.Execute();
                Results.Add(result);
            }
        }
        catch (Exception)
        {
            State = RuleState.Faulted;
            throw;
        }

        State = RuleState.Completed;
    }
}

public class TestRule1 : RuleBase<FakePredicateAlwaysReturnsTrue, WriteHelloAction, string>
{
    public TestRule1()
    {
        Preconditions = new[] { new FakePredicateAlwaysReturnsTrue() };
        Actions = new[] { new WriteHelloAction() };
    }
}

public class FakePredicateAlwaysReturnsTrue : IRulePredicate
{
    public bool Evaluate()
    {
        return true;
    }
}

public class WriteHelloAction : IRuleAction<string>
{
    public async Task<string> Execute()
    {
        return await Task.Run(() => "hello world!");
    }
}


public static class Program
{
    public static async Task Main()
    {
        IBusinessRule rule = null;

        try
        {
            rule = new TestRule1();
            await rule.Execute();

            foreach (string item in rule.Results)
            {
                // Prints "hello world!"
                Console.WriteLine(item);
            }
        }
        catch (Exception ex)
        {
            if (rule != null && rule.State == RuleState.Faulted)
            {
                throw new Exception("Error in rule execution", ex);
            }

            throw;
        }
    }
}

Best Answer

You could probably have a look at Rules Design Pattern. There is a good video also at Pluralsight, see Rules Pattern (you will need to sign in).

Related Topic