NRules, reglas en tu código c#.

En esta ocasión, exploraremos una biblioteca fascinante que nos proporciona la capacidad de agregar reglas de ejecución a nuestra lógica de negocios. La herramienta que utilizaremos es NRules, y puedes acceder a su repositorio en GitHub haciendo clic aquí.

¿Qué es NRules?

Se trata de un motor de reglas diseñado para .NET, fundamentado en el algoritmo de coincidencia conocido como Rete. Más allá de su función como motor de reglas, destaca por ser un motor de inferencia, lo que implica que las reglas deben activarse o ejecutarse en respuesta a alguna situación específica que se desencadene. Posteriormente, estas reglas se ejecutan según el algoritmo de resolución establecido.La funcionalidad principal de NRules es permitir la definición y ejecución de reglas de negocio de manera declarativa en aplicaciones .NET.

Algunos puntos clave sobre NRules:

  • Motor de Reglas: Permite definir reglas de negocio utilizando un enfoque declarativo, lo que facilita la expresión de la lógica empresarial de manera clara y comprensible.
  • Inferencia: Va más allá de un simple motor de reglas al incluir capacidades de inferencia. Esto implica que las reglas se activan en función de situaciones específicas que se desencadenan, y luego se ejecutan según un algoritmo de resolución.
  • Basado en Rete: Utiliza el algoritmo Rete, un algoritmo eficiente para la coincidencia de patrones, que permite una evaluación rápida y eficaz de las reglas.
  • GitHub: El código fuente y la documentación de NRules están disponibles en su repositorio de GitHub, lo que facilita su acceso y colaboración en el desarrollo.

¿Cómo funciona?

El motor de NRules está compuesto por diversos componentes, diseñados de manera que puedan apilarse unos sobre otros. Esta estructura favorece la compatibilidad y facilita la expansión de sus capacidades.

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 es una definición.

El tercero y último 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 último utiliza Session para almacenar las reglas en memoria.

Manos a la obra

Lo primero que debemos hacer es crear un proyecto de consola y haremos referencia al 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;
        }
    }
}

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 numerosas situaciones, la implementación de un motor de reglas se presenta como una solución valiosa para abordar lógicas de negocio complejas. Lo que hemos explorado hasta ahora es solo un primer paso para adentrarnos en el uso de esta biblioteca. Si deseas profundizar más, te invito a explorar su repositorio en GitHub para obtener información adicional y descubrir todas las posibilidades que ofrece.

Espero que hayas encontrado útil este post. Si tienes algún tema específico que te gustaría que analicemos con más detalle, no dudes en compartirlo en los comentarios. Estoy aquí para ayudar.

0 0 votos
Valora la Publicación
Suscribirse
Notificación de
guest
0 Comentarios
Feedback en línea
Ver todos los Comentarios

Comentarios Recientes

0
Nos encantaría conocer tu opinión: ¡comenta!x
Ir a la barra de herramientas