Jess Information

Jess Home
Jess 7 Features
Download Now!
Online Demo

Documentation
FAQ
Manual
Mailing List
Jess Wiki

More information Related Web Sites
User Contributions
JSR94 Info
Developer's Log
About This Site

JESS ®, the Rule Engine for the JavaTM Platform

Jess Wiki: Facts Are Not Objects Anti Pattern

Assume we are representing employees in some department. We may define a simple Java object model like the following:

public class Employee {
    private int employeeId;
    public int getEmployeeId() { return employeeId; }
    public void setEmployeeId(int employeeId) {
        this.employeeId = employeeId;
    }

    private String name;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    private String address;
    public String getAddress() { return address; }
    public void setAddress(String addr) { address = addr; }

    ...
}

public class Department {
    private int departmentId;
    public int getDepartmentId() { return departmentId; }
    public void setDepartmentId(int departmentId) {
        this.departmentId = departmentId;
    }

    private String name;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    private Employee manager;
    public Employee getManager() { return manager; }
    public void setManager(Employee manager) {
        this.manager = manager;
    }

    private Map employees = new HashMap();
    public void addEmployee(Employee employee) {
        employees.add(new Integer(employee.getEmployeeId()), employee));
    }
    public Employee removeEmployee(int employeeId) {
        return employees.remove(new Integer(employeeId));
    }
    public void getEmployee(int employeeId) {
        return employees.get(new Integer(employeeId));
    }

    ...
}

Letís also define a class that represents a promotion to manager:

public class PromoteToManager {
    private Employee employee;
    public Employee getEmployee() { return employee; }
    public void setEmployee(Employee employee) {
        this.employee = employee;
    }

    private Department department;
    public Department getDepartment() {
        return department;
    }
    public void setDepartment(Department department) {
        this.department = department;
    }
}

These classes provide a fairly typical object-oriented representation of our data and would work well if we were writing a Java program. But weíre not. Weíre programming Jess and there are some differences. The first thing we need to do is get these objects represented as facts. The simplest way is to create shadow facts using "defclass":

    (defclass employee Employee)
    (defclass department Department)
    (defclass promote-to-manager PromoteToManager)
Now, we write a rule that promotes an employee from some department to a manager position, possibly relocating him from the department heís in:
    (defrule promote-employee
        (promote-to-manager
            (employee   ?EMPLOYEE)
            (department ?DEPARTMENT_EMPLOYEE_IS_MOVING_TO))
        (department
            (OBJECT     ?DEPARTMENT_EMPLOYEE_IS_MOVING_FROM
                &:(neq nil
                    (call ?DEPARTMENT_EMPLOYEE_IS_MOVING_FROM getEmployee
                        (call ?EMPLOYEE getEmployeeId)))))
        =>
        (call ?DEPARTMENT_EMPLOYEE_IS_MOVING_TO setManager ?EMPLOYEE)
        (call ?DEPARTMENT_EMPLOYEE_IS_MOVING_TO addEmployee ?EMPLOYEE)
        (call ?DEPARTMENT_EMPLOYEE_IS_MOVING_FROM removeEmployee
            (call ?EMPLOYEE getEmployeeId)))
I hope the first thing you think when you see this rule is "Wow! Thatís ugly!" This rule demonstrates several problems with our data representation. First, the only way to find out what department our new manager is coming from is to check every department there is and see if he belongs to it. The only way to do this is by using an external Java function called "getEmployee" and making sure the return value isnít null. This function, however, takes as input an employee id, which we donít have. We do have a reference to the employee object, however, and can get the employee by accessing another external Java function on it called "getEmployeeId". Also, notice that weíre not pattern-matching on the department fact directly. Instead, weíre just using it as a means to get the backing Java object and then testing the Java object directly to see if the rule should fire on it. Generally, you should never need to access the OBJECT slot to determine whether or not the rule should fire. Other slots in the fact should reflect all the data in the object. You should be able to check anything you need to just by accessing these slots.

The RHS of this rule is really Java code, not Jess. This may excite you Java programmers. It shouldnít! Itís really bad. First, if you want to write Java code, write Java code! Donít write Java code using the Jess interpreter. Itís slow by comparison and is usually much harder to understand and maintain. Second, weíre relying on the Java "Department" object to notify the Jess system that the manager has changed. This requires PropertyChangeEvents to be fired as part of the set methods, and, if you look in the Java code, youíll see that they arenít. When the "setManager" method is called on the external Department object, the corresponding fact will not be changed. It will still refer to the old manager. Finally, the "addEmployee" and "removeEmployee" external methods need to be called to actually add the employee to his new department and remove him from his old one. Calling external Java methods throughout your code is usually a sure sign that you have a bad data representation in Jess.

All these problems are minor compared to one other that may be difficult to see. Any guess what it is?

No, itís not that we didnít reassign the previous manager from the department our employee is taking over. Iíll assume that will be taken care of elsewhere in the system.

Hereís a hint: the problem is in how the pattern matcher will evaluate the department fact on the LHS of the rule.

Still donít see it? The "Department" fact on the LHS is constrained using a predicate function. Normally, slot values constrained by predicate functions are re-evaluated when the slots change. In our example, however, the slot value is "OBJECT", which refers to, and will always refer to, the shadow factís corresponding Java object. The address of this object will never change.

Assume we have another rule:

    (defrule fire-employee
        (fire-employee
            (employee  ?EMPLOYEE))
        ?EMPLOYEE_FACT <- (employee
            (OBJECT    ?EMPLOYEE))
        (department
            (OBJECT    ?DEPARTMENT_OF_EMPLOYEE
                &:(neq nil
                    (call ?DEPARTMENT_OF_EMPLOYEE getEmployee
                        (call ?EMPLOYEE getEmployeeId)))))
        =>
        (call ?DEPARTMENT_OF_EMPLOYEE removeEmployee
            (call ?EMPLOYEE getEmployeeId))
        (retract ?EMPLOYEE_FACT))
Further assume that "promotion-to-manager" and "fire-employee" facts are asserted for the same employee. Both these rules will become active and be placed on the agenda. One will fire before the other. Letís assume itís the fire-employee rule. What happens to the "promote-employee" rule activation? We might assume it would be taken off the agenda since the employee no longer exists as a fact in the system. Will it?

In this example, the answer is no. The "promote-employee" activation will remain on the agenda and will fire even after the employee has been removed from the system. The reason is simple: nothing that the "promote-employee" rule depends on has changed. If you look at the rule, you can see that it doesnít actually depend on the existence of the employee fact. Therefore, removing it wonít deactivate the rule. The "promote-employee" rule does require the employee to belong to the department in order to be activated, but this check is done using an external Java method. The RHS of the "fire-employee" rule removes the employee from the department, but this also is carried out by an external Java method. The department fact doesnít have a slot containing a list of all its employees, so it doesnít see when employees are added or removed from it. Hence, it never changes.

Here we have a genuine bug. Even worse, itís a bug thatís hard to see. One hint that thereís a problem is that the "OBJECT" slot is being matched and checked on the LHS. It is a very rare situation where youíll need to do this. So, how do we fix these problems? The best way is to fix the representation.

See the FactsAreNotObjectsPattern page to see how to fix the representation.

Submitted by:
GeorgeWilliamson
Union Pacific Railroad
gawillia@up.com



FrontPage, JavaPitfalls, FactsAreNotObjects


Front Page | Sandbox | Recent Changes | Powered by Friki | Last Edited: 18 August 2006