Friday, January 28, 2011

Implementing Simple Custom Rules By Code

In my previous two posts Microsoft Workflow Rules Engine was used to execute the business rules. That maybe a bit overkill if there're only a small set of rules to run and the use cases of them are relatively simple. In that case we could implement the rules and control their execution directly. The simple console app below demos the concept of custom rules implementation:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        #region demo domain objects
        public class Product
        {
            public string Name { get; set; }
            public decimal Price { get; set; }
        }

        public class ShoppingCart
        {
            public List<Product> SelectedProducts { get; set; }
            public List<string> CouponCodes { get; set; }
            public decimal TotalAmount { get { return SelectedProducts.Sum(prod => prod.Price); } }
            internal decimal Discount { get; set; }

            public ShoppingCart()
            {
                SelectedProducts = new List<Product>();
                CouponCodes = new List<string>();
                Discount = 0;
            }
        }
        #endregion demo domain objects

        #region promotion objects 
        class CouponPromotion  
        {
            public string Name { get; set; }
            public string PromotionCode { get; set; }
            public decimal Discount { get; set; }
            public DateTime ExpiredOn { get; set; }
        }

        class TotalAmountPromotion // discount if total amount is greater than a number
        {
            public string Name { get; set; }
            public decimal TotalAmount { get; set; }
            public decimal Discount { get; set; }
            public DateTime ExpiredOn { get; set; }
        }
        #endregion promotion object

        #region custom rules interface and implementation
        interface ICustomRule
        {
            void Execute(ShoppingCart cart);
        }

        class CouponPromotionRule : ICustomRule
        {
            private CouponPromotion mCouponPromotion;
            public CouponPromotionRule(CouponPromotion promotion)
            {
                this.mCouponPromotion = promotion;
            }

            public void Execute(ShoppingCart cart)
            {
                if (mCouponPromotion.ExpiredOn >= DateTime.Today && cart.CouponCodes.Contains(mCouponPromotion.PromotionCode))
                {
                    Console.WriteLine(mCouponPromotion.Name + " applied");
                    cart.Discount += mCouponPromotion.Discount;
                }
            }
        }

        class TotalAmountGreaterRule : ICustomRule
        {
            private TotalAmountPromotion mTotalAmountPromotion;
            public TotalAmountGreaterRule(TotalAmountPromotion promotion)
            {
                this.mTotalAmountPromotion = promotion;
            }

            public void Execute(ShoppingCart cart)
            {
                if (mTotalAmountPromotion.ExpiredOn >= DateTime.Today && cart.TotalAmount >= mTotalAmountPromotion.TotalAmount)
                {
                    Console.WriteLine(mTotalAmountPromotion.Name + " applied");
                    cart.Discount += mTotalAmountPromotion.Discount;
                }
            }
        }
        #endregion custom rules interface and implementation

        static void RuleTest(ShoppingCart cart)
        {
            // Mock CouponPromotionRule and TotalAmountRules
            CouponPromotionRule rule1 = new CouponPromotionRule(new CouponPromotion()
            {
                Name = "Coupon Promotion Rule 1",
                PromotionCode = "123456789",
                Discount = 10,
                ExpiredOn = new DateTime(2100, 1, 1)
            });

            CouponPromotionRule rule2 = new CouponPromotionRule(new CouponPromotion()
            {
                Name = "Coupon Promotion Rule 2",
                PromotionCode = "111111111",
                Discount = 20,
                ExpiredOn = new DateTime(2000, 1, 1)
            });
            TotalAmountGreaterRule rule3 = new TotalAmountGreaterRule(new TotalAmountPromotion()
            {
                Name = "Total Amount Rule 1",
                TotalAmount = 1000,
                Discount = 100,
                ExpiredOn = new DateTime(2100, 1, 1)
            });

            ICustomRule[] rules = new ICustomRule[] { rule1, rule2, rule3 };
            foreach (var rule in rules)
            {
                rule.Execute(cart);
            }
        }

        static void Main(string[] args)
        {
            ShoppingCart shoppingCart = new ShoppingCart();
            Enumerable.Range(1, 5).ToList().ForEach(i => shoppingCart.SelectedProducts.Add(
                   new Product() { Name = "Product" + i.ToString(), Price = 100 * i }));
            shoppingCart.CouponCodes.Add("123456789");
            shoppingCart.CouponCodes.Add("111111111");
            Console.Write("\nShopping Cart before rules execution -- ");
            Console.WriteLine("Total : ${0}\tDiscount : ${1}", shoppingCart.TotalAmount, shoppingCart.Discount);

            RuleTest(shoppingCart);

            Console.Write("Shopping Cart after rules execution -- ");
            Console.WriteLine("Total : ${0}\tDiscount : ${1}", shoppingCart.TotalAmount, shoppingCart.Discount);

            Console.Read();
        }      
    }
}

The result of the test app: