Managing Software Complexity

As a developer you deal with a lot of complexities. Every organization has a different way of dealing with them. Yet, some fall into the trap of big ball of mud, hit or miss patterns & practices, which I've seen and regretfully contributed.

We developers spend most of our time reading other people's code (This is when you say the most WTF's per minute. At times, even at your own code).

One of the hardest challanges working with code is to understand the "whys" (other than deciphering some outdated comment which makes no sense however hard you try). Unorganized code is hard to (fill in the blank), even removing it from your codebase is hard. Let's not even talk about testing it.

How can we do better? There are many approaches. All with its cons and pros. You may benefit from a library that I use. Fair warning, this is an opinionated approach. You may agree or disagree and I'd respect that.

I want to introduce you to DotNetRuleEngine. It is a library that allows you to write your code as series of rules.

There are a few reasons you may consider using DotNetRuleEngine:

  • S.O.L.I.D compliant.
  • Separation of Concern.
  • Easy to maintain.
  • Testable code.
  • Encapsulates varying behavior. Such as business rules.

I'll demonstrate a simple scenario to give you an idea on how you can use DotNetRuleEngine. At the end of this article, I'll point you to the wiki where you can learn more about it.

Let's think of a scenario. You are working on a feature for shipping rules. You need to implement the following requirements:

  • Apply free shipping if the order costs $50 or more
  • And $5 shipping if the order costs less than $50

Let's see how can we apply this requirement using DotNetRuleEngine

In your existing/new .NET project, install the DotNetRuleEngine nuget package with the following command:

PM> Install-Package DotNetRuleEngine

First, let's create an Order model to represent the domain we're working on:

public class Order  
{
    public int Id { get; set; }
    public decimal Total { get; set; }
    public decimal ShippingTotal { get; set; }
    public bool FreeShipping { get; set; }
    ...
}

Here, we're defining the rules to handle the shipping requirements. Notice that we inherited from Rule abstract class which is part of theDotNetRuleEngine library. When you inherit from Rule, your class can be invoked by the rule engine.

public class QualifiesForFreeShipping: Rule<Order>  
{   
    public override IRuleResult Invoke()
    {
        if (Model.Total >= 50m)
        {
            Model.FreeShipping = true;
            Model.ShippingTotal = decimal.Zero;
        }
        return null;
    }
}
public class QualifiesForFiveDollarShipping: Rule<Order>  
{   
    public override IRuleResult Invoke()
    {
        if (Model.Price < 50m)
        {
            Model.FreeShipping = false;
            Model.ShippingPrice = 5m;
        }
        return null;
    }
}

Looks good but I think we can do little bit better...

public class QualifiesForFreeShipping: Rule<Order>  
{  
    public override void Initialize() =>
        Configuration.Constraint = m => m.Total >= 50m; 

    public override IRuleResult Invoke()
    {
        Model.FreeShipping = true;
        Model.ShippingTotal = decimal.Zero;

        return null;
    }
}
public class QualifiesForFiveDollarShipping: Rule<Order>  
{   
    public override void Initialize() =>
        Configuration.Constraint = m => m.Total < 5m; 

    public override IRuleResult Invoke()
    {
        Model.FreeShipping = false;
        Model.ShippingPrice = 5m;
        return null;
    }
}

Notice we moved out the conditional statements from our core logic into the Initialize method as a guard clause. Rule Engine won't invoke a rule, unless its Constraint evaluated to true.

Finally, we just need to execute the rule engine:

var ruleResults = RuleEngine<Order>.GetInstance(order)  
    .ApplyRules(new QualifiesForFreeShipping(),
                new QualifiesForFiveDollarShipping())
    .Execute()

RuleEngine evaluates each rule based on the metadata provided in the rule (see the wiki for more information). The idea is to encapsulate the requirements as rules. Preferably, each rule would address a specific requirement. Also note that by the name of the rule, we should have a pretty good idea what the rule does without the need of detailed examination.

You can certainly accomplish what I've demonstrated without using DotNetRuleEngine but you do get a few good benefits from it. Check out the https://github.com/ayayalar/DotNetRuleEngine/wiki to learn more about it.

Hope you enjoyed it.

Arif Yayalar

Arif Yayalar

Software Engineer

Seattle, WA

Subscribe to {:arif, :yayalar}

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!