Motor de Reglas SimpleRules (3) Agregando Hechos

Published on Author lopez

Anterior Post

Para mi proyecto de motor de reglas en JavaScript:

https://github.com/ajlopez/SimpleRules

usaba un modelo. ¿Qué es un modelo? Un simple objeto JavaScript que se entrega al motor de reglas, y éste lo usa como base para emitir acciones, generalmente modificando el mismo modelo. Un modelo, por ejemplo, podría representar a un paciente médico, con su temperatura, sus datos de edad, género, etc. Un ejemplo de test:

exports['add and run rule on model'] = function (test) {
    var model = { temperature: 37 };
    
    var eng = engine({});
    
    eng.rule({ name: 'rule1', title: 'Rule 1' })
        .when("model.temperature == 37")
        .then("model.hasFever = true");
        
    eng.run(model);
    
    test.equal(model.temperature, 37);
    test.equal(model.hasFever, true);
}

En este caso, el modelo se ve enriquecido por la acción de una regla, detectando que tiene fiebre. Pero he visto que en otros motoros de reglas también se maneja otro modelo, lo voy a llamar “facts” (hechos) que son los que el motor de reglas agrega, separado del modelo de entrada. Entonces, decidí escribir un test en el que se le entrega al motor un modelo, y objeto de hechos vacío, obteniendo un resultado:

exports['add and run rule on model and facts'] = function (test) {
    var model = { temperature: 37 };
    var facts = { };
    
    var eng = engine({});
    
    eng.rule({ name: 'rule1', title: 'Rule 1' })
        .when("model.temperature == 37")
        .then("facts.hasFever = true");
        
    eng.run(model, facts);
    
    test.equal(model.temperature, 37);
    test.strictEqual(model.hasFever, undefined);
    test.equal(facts.hasFever, true);
}

También escribí un test donde los hechos se usan como entrada, como datos para las condiciones que se deben cumplir en alguna regla:

exports['add and run rule using facts in condition'] = function (test) {
    var model = { age: 80 };
    var facts = { hasFever: true };
    
    var eng = engine({});
    
    eng.rule({ name: 'rule1', title: 'Rule 1' })
        .when("model.age > 70")
        .when("facts.hasFever")
        .then("facts.critical = true");
        
    eng.run(model, facts);
    
    test.equal(facts.critical, true);
}

Una vez escrito esto, modifiqué la implementación para que cada condición y cada acción (internamente son funciones) admita como argmuentos modelo Y hechos, en vez de solamente manipular el modelo. Una vez modificada la implementación interna (apenas modificar unas líneas en un lenguaje dinámico como JavaScript), los tests de arriba pasaron, y ya tengo un motor de reglas que maneja un modelo y hechos, como objetos separados. Realmente la modificación fue simple, gracias a seguir el flujo de trabajo de TDD (Test-Driven Development) desde el principio. Y además de pasar los tests nuevos de arriba, quedó asegurado que los anteriores tests siguen pasando.

Esto es uno de los beneficios de esta forma de trabajo: la implementación de nuevos casos de uso, las refactorizaciones internas, y los rediseños de la API, se pueden encarar sin mayor “dolor”, gracias al proceso ágil.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez