System Design| Business Rules Implementation
Tags: architecture, codequality, oop, dotnet
Published: 2022-08-10 14:39:00

---
let's assume having to support some legacy code that has a lot of conditional logic and poor quality. 

as well it's ongoing development on the system so It can be quite hard to integrate new rules as the code can be difficult to understand and to digest what is going on. 

This sort of code often has comments explaining what the different pieces of conditional logic are doing.

The problems only gets worse as you have to add more conditions over time.

> You can get a better understanding of how the Rules Pattern works by checking out the code in my [github repo](https://github.com/realkarimfahmy/SystemRules).

the example we have about e-commerce store that has discount criteria feature

```
public class DiscountCalculator
{
    public decimal CalculateDiscountPercentage(Customer customer)
    {
        decimal discount = 0;
        if (customer.DateOfBirth < DateTime.Now.AddYears(-65))
        {
            // senior discount 5%
            discount = .05m;
        }

        if (customer.DateOfBirth.Day == DateTime.Today.Day &&
            customer.DateOfBirth.Month == DateTime.Today.Month)
        {
            // birthday 10%
            discount = Math.Max(discount, .10m);
        }

        if (customer.DateOfFirstPurchase.HasValue)
        {
            if (customer.DateOfFirstPurchase.Value < DateTime.Now.AddYears(-1))
            {
                // after 1 year, loyal customers get 10%
                discount = Math.Max(discount, .10m);
                if (customer.DateOfFirstPurchase.Value < DateTime.Now.AddYears(-5))
                {
                    // after 5 years, 12%
                    discount = Math.Max(discount, .12m);
                    if (customer.DateOfFirstPurchase.Value < DateTime.Now.AddYears(-10))
                    {
                        // after 10 years, 20%
                        discount = Math.Max(discount, .2m);
                    }
                }

                if (customer.DateOfBirth.Day == DateTime.Today.Day &&
                    customer.DateOfBirth.Month == DateTime.Today.Month)
                {
                    // birthday additional 10%
                    discount += .10m;
                }
            }
        }
        else
        {
            // first time purchase discount of 15%
            discount = Math.Max(discount, .15m);
        }
        return discount;
    }
}
```
**The Rules Design Pattern**
the fact that this complexity will increase as more rules are added. 

I was looking around for a design pattern that might address those things and came across the Rules Pattern

The Rules Pattern works by separating out the rules from the rules processing logic applying the Single Responsibility Principle.
 
This makes it easy to add new rules without changing the rest of the system applying the Open/Closed Principle.

this image of new design

![code map](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7pqj9fvpfom4stvr9y0.png)

illustration of the above image:

- IRule<T> : contains all methods of rule flow such `ClearConditions()` , `Initialize()` , `IsValid()` , `Apply()` , `OnRuleViolated()` as well you can add `OnRuleCompleted()` 

- IRule<T> implemented by abstract class `BaseRule<T>` that use dependency of `ICondition` that has expression of rule requirement  

- RulesEngine.cs  has `ApplyRule()` which the execution path of rule

- the rules classes itself inherit base rule and override the method body

BirthdayDiscountRule example:
```
public class BirthdayDiscountRule : BaseRule<Customer>
    {
        public override void Initialize(Customer obj)
        {
            Conditions.Add(new ConditionExtension(CustomerExtensions.IsBirthday(obj)));
        }

        public override Customer Apply(Customer obj)
        {
            obj.Discount += .10m;
            return obj;
        }
        public override void OnRuleViolated(Customer obj)
        {
            Console.WriteLine($"{this.GetType().Name}  Violated");
        }
    }
```

this code snipped of final result

```
class Program
    {
        static void Main(string[] args)
        {
            var productA = new Product { Price = 100, Name = "Apple Watch" };
            var productB = new Product { Price = 50, Name = "Samsung Watch" };

            var customer = new Customer
            {
                Products = new List<Product> { productA, productB, productA, productA },
                DateOfFirstPurchase = DateTime.Now.AddYears(-2),
                DateOfBirth = DateTime.Now.AddYears(-5),
            };
            customer.TotalValue = customer.Products.Sum(p => p.Price);

            var ruleManager = new CustomerRuleManager(customer);
            var result = ruleManager.Run();
        }
    }
    public class CustomerRuleManager
    {
        private readonly Customer _customer;

        public CustomerRuleManager(Customer customer)
        {
            _customer = customer;
        }

        public Customer Run()
        {
            _customer
                .ApplyRule(new BirthdayDiscountRule())
                .ApplyRule(new BuyThreeGetThirdFree("Apple Watch"))
                .ApplyRule(new FreeShipping())
                .ApplyRule(new LoyalCustomerRule(5, 0.12m))
                .ApplyRule(new NewCustomerRule())
                .ApplyRule(new RetiredDiscountRule());

            return _customer;
        }
    }
```