0

Reglas en tu código c# (NRules)

En esta ocasión estaremos viendo una librería muy interesante que nos permite agregar reglas de ejecución en nuestra lógica de negocio. La librería que utilizaremos es Nrules puede visitar su github desde este link clic aquí.

¿Qué es NRules?

Es un motor de reglas para .Net. Está basado en el algoritmo de coincidencia llamado Rete. Además de ser un motor de reglas, también es un motor de inferencia lo cual implica que las reglas deben activar o ejecutarse en función de alguna situación que se dispare y luego ejecutar según el algoritmo de resolución.

¿Cómo funciona?

El motor de NRules está conformado por varios componentes y está diseñado para que los componentes puedan apilarse uno sobre otros aumentando la compatibilidad y la posibilidad de expandir su uso.

El corazón de todo motor de este tipo son las reglas. Podemos tener una gran cantidad de ellas y estas existen dependiendo de como las creemos o escribamos. Para esto NRules usa internamente DSL para describir y escribir estas reglas mediante FluentAPI. Por el momento es el lenguaje permitido para el uso de NRules, pero en futuras implementaciones se planea dar soporte a otros lenguajes de reglas.

Veamos sus componentes principales que trabajaran en tiempo de ejecución:

El primer namespaces importante es NRules.Fluent. Este contiene las clases que permiten la creación declarativa de las reglas mediante DSL. RuleRespository es la que buscará en los ensamblados de nuestra aplicación las reglas que declaramos y con Rule builder interacturectaremos con ellas para ser interpretadas

El segundo es NRules.RuleModel que contiene las clases que representan reglas como objetos. Debemos tener presente que las reglas son una colección de reglas en un conjunto y que cada una definición.

El tercero y ultimo NRules donde tendremos las clases que se implementan en tiempo de ejecución del motor de reglas.  RuleComplier traduce las reglas que serán contenidas en SessionFactory. Este ultimos utilizar Session para almacenar las reglas en memoria.

Manos a la obra

Lo primero que debermos hacer es crear un proyecto de consola y haremos refencia a paquete NRules. 

Desde visual studio, abriremos la consola de powershell y ejecutaremos:

PM> Install-Package NRules

Si estás usando VSCode, para en nuestra aplicación ejecutaremos:

dotnet add package NRules

Ahora creamos un modelo que será un modelo de nuestro domicilio, en este caso una clase Customer y Order, es una implementación simple, pero nos servirá como punto de partida.

public class Customer
{
    public string Name { get; }
    public bool IsPreferred { get; set; }

    public Customer(string name)
    {
        Name = name;
    }

    public void NotifyAboutDiscount()
    {
        Console.WriteLine($"Customer {Name} was notified about a discount");
    }
}

public class Order
{
    public int Id { get; }
    public Customer Customer { get; }
    public int Quantity { get; }
    public double UnitPrice { get; }
    public double PercentDiscount { get; set; }
    public bool IsOpen { get; set; } = true;

    public Order(int id, Customer customer, int quantity, double unitPrice)
    {
        Id = id;
        Customer = customer;
        Quantity = quantity;
        UnitPrice = unitPrice;
    }
}

Ahora crearemos nuestra regla. Para esto debemos heredar de NRules.Fluent.DSL.Rule. Recordemos que una regla está compuesta por un conjunto de condiciones y conjunto de acciones a ser ejecutadas por el motor cuando la regla se activa. En la siguiente regla aplicaremos a un cliente preferencial un descuento de un 10% a su pedido. Utilizando When() nos permitirá agregar una expresión de condición que al dispararse invocará él Then().

public class PreferredCustomerDiscountRule : Rule
{
    public override void Define()
    {
        Customer customer = default;
        IEnumerable<Order> orders = default;

        When()
            .Match<Customer>(() => customer, c => c.IsPreferred)
            .Query(() => orders, x => x
                .Match<Order>(
                    o => o.Customer == customer,
                    o => o.IsOpen,
                    o => o.PercentDiscount == 0.0)
                .Collect()
                .Where(c => c.Any()));

        Then()
            .Do(ctx => ApplyDiscount(orders, 10.0))
            .Do(ctx => ctx.UpdateAll(orders));
    }

    private static void ApplyDiscount(IEnumerable<Order> orders, double discount)
    {
        foreach (var order in orders)
        {
            order.PercentDiscount = discount;
        }
    }
}

Ahora veamos otra regla. Todos los clientes que tienen pedidos con descuento, serán notificados del descuento. Esta está relacionada con la primera regla que disparamos, una vez activa, el motor de reglas actualizará el estado y dispara la segunda regla.

public class DiscountNotificationRule : Rule
{
    public override void Define()
    {
        Customer customer = default;

        When()
            .Match<Customer>(() => customer)
            .Exists<Order>(o => o.Customer == customer, o => o.PercentDiscount > 0.0);

        Then()
            .Do(_ => customer.NotifyAboutDiscount());
    }
}

Ahora vamos a ejecutar nuestras reglas. Veamos cómo:

//Load rules
var repository = new RuleRepository();
repository.Load(x => x.From(typeof(PreferredCustomerDiscountRule).Assembly));

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

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

//Load domain model
var customer = new Customer("John Doe") {IsPreferred = true};
var order1 = new Order(123456, customer, 2, 25.0);
var order2 = new Order(123457, customer, 1, 100.0);

//Insert facts into rules engine's memory
session.Insert(customer);
session.Insert(order1);
session.Insert(order2);

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

Conclusiones

En muchos casos, implementar un motor de reglas puede ayudarnos a implementar lógicas de negocio complejas. Este fue un acercamiento para iniciarnos en el uso de la libre. Puede investigar un poco más desde su GitHub. Espero que te haya gustado el post. Si quieres que veamos algo más en detalle dejámelo en los comentarios.

Fernando Sonego

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *