Zrozumieć JBoss Drools


Zbiór bibliotek napisanych w języku Java zebranych w jeden pakiet JBoss Drools to kompleksowe rozwiązanie szeroko stosowane w biznesie, a dokładniej rzecz ujmując w procesie tworzenia rozwiązań informatycznych dla biznesu. Powstało z potrzeby ułatwienia i przyspieszenia tworzenia biznesowych działań i pozwoliło na zwiększenie elasyczności poprzez wyniesienie logiki biznesowej do konfiguracji danego oprogramowania. Dzięki takiemu podejściu reguły może modyfikować analityk systemowy lub biznesowy, a zmiany wprowadzać bezpośrednio w arkuszach MS Excel lub z użyciem pseudonaturalnego języka DSL (Domain Specific Language). Jednak zamim to zrobi to od nas – ekspertów Drools – zależy jakie wyrażenia DSL będzie miał do dyspozycji lub w jaki sposób będzie zmieniał reguły biznesowe.
Wiedza jest wszystkim
Najnowsza wersja pakietu to Drools 6.3.0.Final. Nie jest moim celem opisywać tu wszystkiego, a jedynie zwrócić uwagę na najważniejsze rzeczy, które pozwolą zrozumieć jak działają reguły. Z tego względu dociekliwych odsyłam do dokumentacji.
Osoby niezaznajomione z Drools’ami często podchodzą do problemu implementowania logiki biznesowej tak jak w kodzie Java, rozwiązując ją za pomocą for’ów, if’ów oraz kondensują całą logikę w ramach kilku reguł, co sprawia, że reguła ma ponad 1000 linii i jest mało czytelna. Reguły powinny być rozsądnie krótkie, logiczne, łatwe do zrozumienia. Jeśli już widzisz taki ciągnący się makaron – rozbij na mniejsze części.
Używaj reguł tam, gdzie:
- logika zmienia się często,
- problem jest złożony i tradycyjne podejście jest trudne w implementacji,
- problem może nie jest złożony, ale tradycyjne podejście się nie sprawdzi,
- opiekun” logiki biznesowej nie jest techniczny,
Najważniejszą sprawą jest jednak to, aby zrozumieć jak działają reguły. Najlepiej na przykładzie.
Niekończąca się opowieść
Ważna persona z biznesu chce by system przywitał nowego klienta, jeśli jeszcze tego nie zrobił. W tym celu definiujemy klasę Messages, odpowiedni proces oraz plik reguł jak poniżej:
public class Messages {
public static final Integer UNDEFINED = -1;
public static final Integer HELLO = 0;
public static final Integer GOODBYE = 1;
private String message;
private Integer status;
public Messages() {
this.message = "";
this.status = UNDEFINED;
}
// getter and setters
}
rule "Process first message" ruleflow-group "ProcessMessage"
when
m : Messages(status == Messages.UNDEFINED, myMessage : message)
then
m.setMessage("Hello");
m.setStatus(Messages.HELLO);
update(m);
end
rule "Process second message" ruleflow-group "ProcessMessage"
when
m : Messages(status == Messages.HELLO, myMessage : message)
then
LOG.log(myMessage);
m.setMessage("Goodbye");
m.setStatus(Messages.GOODBYE);
update(m);
end
Proste. Pierwsza reguła sprawdzam czy przywitanie miało miejsce, jeśli nie, to ustawia odpowiedni stan obiektu Messages. Druga reguła wita gościa:
[main] INFO pl.speednet.drools.DroolsTests - ------------------------------
[main] INFO pl.speednet.drools.DroolsTests - Rule : Process first message
[main] INFO pl.speednet.drools.DroolsTests - Salience : 0
[main] INFO pl.speednet.drools.DroolsTests - Ruleflow group : ProcessMessage
[main] INFO pl.speednet.drools.DroolsTests - Execution time : 4 ms
[main] INFO pl.speednet.drools.DroolsTests - ------------------------------
[main] INFO pl.speednet.drools.DroolsTests - Rule : Process second message
[main] INFO pl.speednet.drools.DroolsTests - Salience : 0
[main] INFO pl.speednet.drools.DroolsTests - Ruleflow group : ProcessMessage
[main] INFO pl.speednet.drools.DroolsTests - Hello
[main] INFO pl.speednet.drools.DroolsTests - Execution time : 1 ms
Ważna persona z biznesu jednak chce by po przywitaniu pożegnać gościa. Modyfikujemy więc drugą i dodajemy trzecią regułę:
rule "Process second message" ruleflow-group "ProcessMessage"
when
m : Messages(status == Messages.HELLO, myMessage : message)
then
LOG.log(myMessage);
m.setMessage("Goodbye");
m.setStatus(Messages.GOODBYE);
update(m);
end
rule "Process third message" ruleflow-group "ProcessMessage"
when
m : Messages(status == Messages.GOODBYE, myMessage : message)
then
LOG.log(myMessage);
end
[main] INFO pl.speednet.drools.DroolsTests - ------------------------------
[main] INFO pl.speednet.drools.DroolsTests - Rule : Process first message
[main] INFO pl.speednet.drools.DroolsTests - Salience : 0
[main] INFO pl.speednet.drools.DroolsTests - Ruleflow group : ProcessMessage
[main] INFO pl.speednet.drools.DroolsTests - Execution time : 4 ms
[main] INFO pl.speednet.drools.DroolsTests - ------------------------------
[main] INFO pl.speednet.drools.DroolsTests - Rule : Process second message
[main] INFO pl.speednet.drools.DroolsTests - Salience : 0
[main] INFO pl.speednet.drools.DroolsTests - Ruleflow group : ProcessMessage
[main] INFO pl.speednet.drools.DroolsTests - Hello
[main] INFO pl.speednet.drools.DroolsTests - Execution time : 1 ms
[main] INFO pl.speednet.drools.DroolsTests - ------------------------------
[main] INFO pl.speednet.drools.DroolsTests - Rule : Process third message
[main] INFO pl.speednet.drools.DroolsTests - Salience : 0
[main] INFO pl.speednet.drools.DroolsTests - Ruleflow group : ProcessMessage
[main] INFO pl.speednet.drools.DroolsTests - Goodbye
[main] INFO pl.speednet.drools.DroolsTests - Execution time : 1 ms
Dostajemy polecenie, aby po pożegnaniu gościa, przywrócić domyślny stan:
rule "Process third message" ruleflow-group "ProcessMessage"
when
m : Messages(status == Messages.GOODBYE, myMessage : message)
then
LOG.log(myMessage);
m.setMessage("");
m.setStatus(Messages.UNDEFINED);
update(m);
end
Proste, prawda? Po modyfikacjach reguły, zadowoleni, wrzucamy ją do systemu i… zabijamy serwer, a od przełożonych słyszymy kilka urodziwych słów. Dlaczego?!
Przeglądając logi zobaczymy powtarzające się sekwencje wykonywania reguł: reguła 1, reguła 2, reguła 3, reguła 1, reguła 2 itd. Przecież powinno się zakończyć na 3 regule.
I tu odkrywamy dwufazową kontrolę wykonania:
- Rule Runtime Actions
- Agenda Evaluation
W pierwszej fazie silnik reguł sprawdza, które reguły może wykonać (tzn które warunki części WHEN są spełnione). Po zakończonym procesie silnik przełącza się w drugą fazę. W tej fazie wykonuje regułę, jeśli takowa została znaleziona w pierwszej fazie i przełącza się do pierwszej fazy. Sprawdzanie wykonywane jest w tej samej agendzie lub grupie reguł. Proces powtarza się do momentu gdy nie znajdzie żadnej reguły do wykonania.
W naszym przypadku powstanie pętla nieskończona, gdyż reguła trzecia modyfikuje obiekty tak, że są spełnione warunki pierwszej reguły. Cały proces można słownie zobrazować tak:
- Faza 1 – silnik znajduje spełnione warunki 1 reguły, przełącza się w drugą fazę
- Faza 2 – wykonanie reguły 1, powrót do fazy 1 (bo wykonał regułę)
- Faza 1 – silnik znajduje spełnione warunki 2 reguły, przełącza się w drugą fazę
- Faza 2 – wykonanie reguły 2, powrót do fazy 1
- Faza 1 – silnik znajduje spełnione warunki 3 reguły, przełącza się w druga fazę
- Faza 2 – wykonanie 3 reguły, powrót do fazy 1
- patrz pkt 1.
Rozwiązania są przynajmniej dwa:
– użycie lock-on-active przy regule 1 (rule “Process first message” ruleflow-group “ProcessMessage” lock-on-active)
– przeniesienie reguły 3 do innej grupy (ruleflow-group “InnaGrupa”)
Jak widać zrozumienie działania reguł biznesowych jest dość istotne. Przede wszystkim zaoszczędzi czas, a po drugie pozwoli na efektywne i logiczne pisanie reguł.
Na firmowym GitHub znajdziecie maven’owy poligon doświadczalny.