Motor de Reglas SimpleRules (1) Simplificando con TDD

Published on Author lopez

Siguiente Post

Hace ya uno años, escribí un motor de reglas en JavaScript/NodeJS, usando como es habitual el flujo de trabajo de TDD (Test-Driven Development). A las reglas se les entrega un modelo (un simple objeto JavaScript) y se van disparando según se cumplan condiciones que se defina en cada regla. Y si una regla se dispara, se ejecutan las acciones correspondientes.

En este diciembre, volví a vistar el proyecto, porque tengo unos casos de uso nuevos donde aplicarlo. Y me doy cuenta que no necesito todo lo que había antes definido. Por ejemplo, antes definía una condición y la controlaba con:

exports['define and apply condition'] = function (test) {
    var r = rule({ name: 'rule1' });
    r.condition({ name: 'temperature', value: 37 });
    var model = { temperature: 37 };
    
    test.strictEqual(r.checkConditions(model), true);
}

O sea, la condición estaba definida en un objeto JavaScript, especificando que campo del modelo había que controlar y por cual valor.

También tenía la posibilidad de entregar un operador a aplicar:

exports['define and apply condition with operator'] = function (test) {    
    var r = rule({ name: 'rule1' });
    r.condition({ name: 'temperature', value: 37, operator: '>=' });
    var model = { temperature: 40 };
    
    test.strictEqual(r.checkConditions(model), true);    
}   

Internamente, todo esto lo compilaba a JavaScript. Lo mismo para las acciones: las definía con objetos JavaScript, y por cada tipo nuevo de acción (por ejemplo, cambiar un valor en el modelo), tenía que inventar una forma de represantarla, como representé arriba las condiciones.

Pero ahora, con nuevos casos de uso que estoy pensando, veo que no necesito la granularidad de arriba. Directamente me basta poner un string con la condición o la acción a ejecutar. Por ejemplo, ahora el primer test de arriba es:

exports['define and apply condition'] = function (test) {
    var r = rule({ name: 'rule1' });
    r.when("model.temperature == 37");
    var model = { temperature: 37 };
    
    test.strictEqual(r.checkConditions(model), true);
}

y el segundo test es ahora:

exports['define and apply condition with operator'] = function (test) {    
    var r = rule({ name: 'rule1' });
    r.when("model.temperature >= 37");
    var model = { temperature: 40 };
    
    test.strictEqual(r.checkConditions(model), true);    
}   

El gran cambio es que ahora a la regla se le entrega una expresión en texto para representar una condición. Esto permite enviar cualquier expresión que necesite para esta regla, sin tener que definir y procesar objetos “ad-hoc”.

Pierdo el control de los textos: antes, cuando entregaba un objeto, el código a ejecutar lo armaba yo. Pero ahora veo que lo de arriba es más simple para lo que tengo que hacer. Y lo pude implementar en minutos, porque tenía todos los tests armados, y fue cuestión de refactorizar la implementación y los tests. Si alguna vez me encuentro con un caso de uso que amerite la creación de condiciones de forma más controlada, puedo implementar de nuevo la descripción por objetos, o quizás apele a una API fluent de definición de condiciones. Por ahora, no necesité ese poder. Me guío por los casos de uso que tengo entre manos, y avanzo con firmeza. TDD me da la fortaleza de aceptar el cambio, ahora o en el futuro.

Esto es algo que da el poder de TDD: el coraje de cambiar una implementación, hacer hasta refactor quirúrgico, sin morir en el intento. Es algo que aprecio mucho, y en proyectos profesionales me ha servido para ir adaptándome ágilmente a nuevos casos de uso, sin grandes complicaciones.

En un próximo post, explicaré cómo, siguiendo el principio de simplicidad, pude refactorizar otros aspectos de la implementación. Y hasta pude implementar un DSL (Domain Specific Language) textual de definición de reglas, en apenas una hora. De nuevo siguiendo el flujo de trabajo de TDD. Pueden ver el historial de commits del proyecto.

Nos leemos!

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