Thursday, August 18, 2011

Ein Beispiel-Szenario für Drools-Regeln

In dem Post "Drools für meine Rules" habe ich auf ein später folgendes Hello-World-Beispiel verwiesen. Nun ist es soweit.

Das Beispiel-Szenario bezieht sich auf das Projekt des Human Task Service (http://www.humantaskservice.blogspot.com/). Eine Task-Instanz (TI) ist im Kontext dieses Systems das technische Äquivalent zu dem abstrakteren Begriff des Human Tasks. Die TI gehört also zum Domänen-Modell des Human Task Service (HTS). Für dieses Beipiel ist ein vereinfachtes Modell die Grundlage.

Jede TI wird zum Erzeugungszeitpunkt mit einem Zeitstempel versehen. Weiterhin ergibt sich aus der zugewiesenen Task-Beschreibung die maximale Dauer für die Bearbeitung einer Task-Instanz bzw. den Human Task. Diese muss für in jedem Fall zugesichert werden können, damit der HTS als wirtschaftliche Dienstleistung angeboten werden kann. Neben der Erfüllungsdauer gibt es noch weitere Gütemerkmale von Diensten. Diese werden in sog. Service Level Agreements definiert. Es muss für die Verwendung der Rule-Engine Drools also eine Regel formuliert werden, die den erklärten Sachverhalt erfüllt.

Hier kommt die Regel dazu:
<?xml version="1.0" encoding="UTF-8"?>
<package
    name="de.saxsys.hti.monitoring.controller"
    xmlns="http://drools.org/drools-5.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
    xs:schemaLocation="http://drools.org/drools-5.0 drools-4.0.xsd">
    <import name="de.saxsys.hti.monitoring.domain.*"/>
    <import name="de.saxsys.hti.monitoring.util.mail.*"/>
    <import name="de.saxsys.hti.monitoring.util.deadline.*"/>
    <import name="de.saxsys.hti.monitoring.handler.*"/>
    <import name="javax.xml.datatype.*"/>
    <import name="java.math.BigDecimal"/>
    <import name="java.util.*"/>

    <function
        return-type="String"
        name="prepareMessageText">
        <parameter
            identifier="$ti"
            type="TaskInstance"/>
        <body>             
            StringBuffer sb = new StringBuffer();
           
            sb.append("An task instance with state '").append($ti.getState().value());
            sb.append("' has passed the half of the deadline!\n");
            sb.append("--------------------------------------------\n");
            sb.append("Detailed data:\n\n");
            sb.append("id: ").append($ti.getId()).append("\n"); 
            sb.append("name: ").append($ti.getName()).append("\n");
            sb.append("creation: ").append($ti.getCreationTime()).append("\n");
           
            Date creationDate =    $ti.getCreationTime();
            String duration = $ti.getTaskDescription().getServiceLevelAgreement().getDeadline().getTimeSpan();
            sb.append("deadline: ").append(DeadlineChecker.getEndDate(creationDate, duration));
           
            return sb.toString();
        </body>
    </function> 

    <rule name="deadline is half passed">
        <lhs>
            <pattern object-type="TaskInstance" identifier="$ti">
                <and-constraint-connective>
                    <field-constraint field-name="state">
                        <qualified-identifier-restriction evaluator="!=">State.COMPLETED</qualified-identifier-restriction>
                    </field-constraint>
                    <field-constraint field-name="state">
                        <qualified-identifier-restriction evaluator="!=">State.FAILED</qualified-identifier-restriction>
                    </field-constraint>
                </and-constraint-connective>
            </pattern>
            <eval>
                DeadlineChecker.halfOfDeadlineHasPassed($ti.getCreationTime(), $ti.getTaskDescription().getServiceLevelAgreement().getDeadline().getTimeSpan())
            </eval>
        </lhs>
        <rhs>
            $ti.setPriority(Prioritizer.increase($ti.getPriority()));
            System.out.println("... increase priority to: " + $ti.getPriority().value());
            System.out.println("... inform responsible person of half passed deadline");
            String message = prepareMessageText($ti);
            HTIReactionHandler htiRH = (HTIReactionHandler) ReactionHandlerFactory.getReactionHandler(ReactionHandlerType.HTI_HANDLER);
            htiRH.sendMail("department_chief@anycompany.com", "Warning", message, $ti.getAttachment());
        </rhs>
    </rule>
</package>
Nun die Erläuterung des Dokuments:

  1. Zuerst das Wurzelelement: <package></package> Mit dem Attribut name wird eine Bezeichnung angegeben, welche die in diesem Dokument enthaltenen Regeln im Speicher der Rule-Engine eindeutig von anderen Regeln unterscheidet.
  2. In den Regeln kann Java-Codeenthalten sein. Die genutzten Klassen werden durch den  import-Tag geladen.
  3. Regeln werden durch das Element <rule/> ausgedrückt. Ein Regel besteht aus einem Bedingungsteil (left hand side) und dem Konsequenzteil (right hand side). Die Tags <lhs/> und <rhs/> besitzen diese Semantik und werden als Kindelemente des rule-Tags eingefügt.
Der Bedingungsteil:
  1. Eine Bedingung wird in Drools durch sog. Patterns, also Muster, ausgedrückt. Man beschreibt mit Patterns wie entsprechende Datenobjekte ausehen sollen, welche die Bedingung erfüllen sollen. Mit dem Attribut object-type wird der Typ bzw. die Klasse des Objekts festgelegt. Über einen Bezeichner, der mit dem Attribut identifier angegeben wird, kann man in dem Dokument auf das aktuelle Objekt zugreifen.
  2. Bisher würde die Bedingung lauten: "Objekte müssen vom Typ TaskInstance sein, damit der Konsequenzteil (rhs) aktiviert wird.". Darum werden mit Feld-Beschränkungen (Restriktionen) genauere Angaben zu dem Objekt gemacht. Mit dem field-constraint-Tag wird beschrieben, welche Felder das Objekt haben muss.
  3. Als Kindelemente des field-constraint-Tags können zum Beispiel folgende Elemente verwendet werden, um die Werte der Felder zu beschränken:
    • Um Enumerationen anzugeben, wird eine qualified-identifier-restriction verwendet.
    • Für alle anderen (primitiven bzw. standard) Typen kann der folgende XML-Tag benutzt werden: <literal-restriction evaluator=">" value="0"/> Mit solchen Restriktionen können Felder mit Zahlen oder Zeichenketten ausgezeichnet werden. Mit dem evaluator-Attribut wird angegeben, wie der jeweilige Wert des Feldes auzuwerten oder zu interpretieren ist.
  4. Um solche Feld-Restriktionen können nun noch logische Verknüpfungen (<and-constraint-connective/>, <or-restriction-connective/>) geschachtelt werden.
  5. Um die Patterns können weiterhin Negationen (<not/>), der Existenz- (<exists/>) und der All-Quantor (<forall/>) geschachtelt werden.
  6. Neben den Patterns zur Beschreibung der Beschaffenheit eines Objekts gibt es noch die Möglichkeit, diese durch eigenen Java-Code zu prüfen. Dafür wird der Tag <eval></eval> benutzt. In ihm kann beliebige Logik formuliert sein, die weitere Objektmerkmale prüfen kann. Die einzige Einschränkung ist, dass der Ausdruck zu einem Boolschen Wert evaluiert.
Der Konsequenzteil:
  1. Im Konsequenzteil der Regel kann nun jedweder Java-Code stehen. Entsprechende Importe sind dafür nicht zu vergessen. 
  2. Um auf das aktuelle Objekt zuzugreifen und die Konsequenz für ein solches auszuführen, benötigt man eine Referenz darauf. Diese konnte durch die angabe eines Bezeichners (identifier) im Pattern angelegt werden. Damit haben wir nun Zugriff auf die Daten des Objekts. Für ein besseres Verständnis sorgt es, wenn in den Regelbedingungen angelegte Bezeichner mit beispielsweise dem Dollar-Zeichen den Unterschied zu im Java-Code definierten Bezeichnern singnalisieren.
  3. Typisch für XML ist der Overhead, der durch die Dokumentstruktur entsteht. Aus diesem Grund, und für die Wiederverwendbarkeit ist es sinnvoll, Logik zu kapseln. Einfacherweise macht man dies in Java-Klassen, die nur eingebunden und aufgerufen werden brauchen. Eine zweite Möglichkeit ist, die Verwendung von Dokument-Internen Funktionen. Ein Exemplar davon ist genau unter den Import-Beschreibungen des Beispieldokuments zu finden. Weiterer Erklärung bedarf es nach all den Erläuterungen nicht. Wie zu sehen, kann im Funktionsrumpf wieder Java-Code stehen. Funktionen sind vor allem dafür geeignet, regelspezifische Logik im Regeldokument zu kapseln. Logik, die in unterschiedlichen Regeldokumenten gebraucht wird, sollte besser in Javaklassen verpackt werden.
Nachdem nun die Syntax des Regeldokuments recht ausgiebig erklärt wurde, folgt nun eine kurze Beschreibung der Semantik. Was soll die Regel tun?

Für jede Task-Instanz, die noch nicht im Zustand beendet (COMPLETED) oder fehlgeschlagen (FAILED) ist, und deren Bearbeitungsfrist bereits zur Hälfte überschritten wurde, soll eine Konsequenz ausgeführt werden.
Sinn der Regel ist das Scheitern des Human Tasks zu verhindern. Darum lautet die Konsequenzhandlung: Es muss eine zuständige Person, z.B. ein Abteilungsleiter, per E-Mail über den dringenden Zustand informiert werden.

Alle Sprachelemente und Funktionen konnten natürlich nicht gezeigt werden, jedoch wurde in diesem Post eine kleine Einführung in Drools-XML-Regeln gegeben.