Understanding the Concept of Rule Engine through Shopping Cart Discount feature

Hi Everyone, Hope you are doing well, In this article, we will delve into the concept of Rule Engine and and its implementation through NRule (.NET Package). For better understanding, we will create a new desktop app in .NET 8 illustrating the shopping cart discount feature for users based on their membership status (Normal/Silver/Gold/VIP).

Let's get started with Rule Engine and NRule.

A rule engine is a software system that executes one or more business rules in a runtime production environment. The rules might come from legal regulation, company policy, or other sources. A rule engine may be viewed as a sophisticated if/then statement interpreter. The primary benefit of a rule engine is that the business rules can be written in a way that non-programmers can understand, and those rules can be modified without changing the underlying application code.

NRules - An Introduction

NRules is an open-source .NET rule engine that supports defining business rules separate from the system logic. It allows for rules to be written in C#, using internal DSL to express rule conditions and actions. NRules is highly extensible, can integrate with any .NET application, and supports dynamic rule compilation.

Shopping Cart Discount Logic Using NRules

In this example, we will create a simple console application in C# that uses NRules to apply different discount percentages based on the user's membership type (Normal, Silver, Gold, VIP).

Step 1. Set Up the Project.

First, create a new Console Application in Visual Studio or using the .NET CLI. Then, add the NRules package and NRules.Runtime package via NuGet.

dotnet add package NRules
dotnet add package NRules.Runtime

Step 2. Define the Domain Model.

Create classes for Customers and Orders.

public class Customer
{
    public string MembershipType { get; set; }
}

public class Order
{
    public Customer Customer { get; set; }
    public decimal TotalAmount { get; set; }
    public decimal DiscountedAmount { get; set; }
}

Step 3. Define the Rules.

Create a class MembershipDiscountRule for discount rules. We'll consolidate the rules into a single class for simplicity.

using RuleEngine_ShoppingCartDiscount.Models;
using NRules.Fluent.Dsl;
using System;

namespace RuleEngine_ShoppingCartDiscount
{
    public class MembershipDiscountRule : Rule
    {
        public override void Define()
        {
            Order order = null;

            When()
                .Match<Order>(() => order,
                              o => o.Customer != null,
                              o => o.DiscountedAmount == 0); // Ensure discount is not already applied

            Then()
                .Do(ctx => ApplyDiscount(order))
                .Do(ctx => ctx.Update(order));
        }

        private void ApplyDiscount(Order order)
        {
            var discount = order.Customer.MembershipType switch
            {
                "Normal" => 0.90m, // 10% discount
                "Silver" => 0.80m, // 20% discount
                "Gold" => 0.75m, // 25% discount
                "VIP" => 0.70m, // 30% discount
                _ => 1.00m // No discount
            };

            order.DiscountedAmount = order.TotalAmount * discount;
            Console.WriteLine($"Applied {((1 - discount) * 100)}% discount for {order.Customer.MembershipType} member. Total now: {order.DiscountedAmount}");
        }
    }
}


Step 4. Configure and Run the Rule Engine.

In your Main method, set up the rule repository, compile rules, create a session, and create the list of Customers (Normal,Silver,Gold,MVP).

// See https://aka.ms/new-console-template for more information
// Load rules
using NRules;
using NRules.Fluent;
using RuleEngine_ShoppingCartDiscount;
using RuleEngine_ShoppingCartDiscount.Models;

var repository = new RuleRepository();
repository.Load(x => x.From(typeof(MembershipDiscountRule).Assembly));

// Compile rules
var factory = repository.Compile();

// Create a session
var session = factory.CreateSession();

var customers = new List<Customer>
{
    new Customer { MembershipType = "Normal" },
    new Customer { MembershipType = "Silver" },
    new Customer { MembershipType = "Gold" },
    new Customer { MembershipType = "VIP" }
};

// Create customer and order
foreach (var customer in customers)
{
    var order = new Order { Customer = customer, TotalAmount = 100 };

    // Insert facts into rules engine's memory
    session.Insert(order);

    // Start match/resolve/act cycle
    session.Fire();

    Console.WriteLine($"Final amount to pay: {order.DiscountedAmount}\n");
}

Console.ReadLine();

Step 5. Test the Application.

Run the console application and it will print the All membership-based discount price.

Test Application

Code Explanation

  • Rules Definition: A single rule handles all membership types by using a switch expression to determine the discount rate based on the membership type.
  • Rule Engine Setup: Rules are loaded, and compiled, and a session is created where the order is inserted as a fact.
  • Execution: The session.Fire() method triggers the rule engine, which evaluates the inserted facts against the compiled rules and applies the appropriate discount.

Conclusion

Utilizing a rule engine like NRules in a .NET application offers a powerful approach to managing business logic separately from application code. This separation enhances maintainability, flexibility, and scalability, making it easier to adapt to changing business requirements without extensive modifications to the core application. For more detail regarding NRule documentation and architectural flow check out this official page NRule Documentation.


Similar Articles