Zrozumieć JBoss Drools

avatar icon blog author
Marek Brzozowski
Drools (BRMS) - system zarządzania regułami biznesowymi

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:

  1. Rule Runtime Actions
  2. 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:

  1. Faza 1 – silnik znajduje spełnione warunki 1 reguły, przełącza się w drugą fazę
  2. Faza 2 – wykonanie reguły 1, powrót do fazy 1 (bo wykonał regułę)
  3. Faza 1 – silnik znajduje spełnione warunki 2 reguły, przełącza się w drugą fazę
  4. Faza 2 – wykonanie reguły 2, powrót do fazy 1
  5. Faza 1 – silnik znajduje spełnione warunki 3 reguły, przełącza się w druga fazę
  6. Faza 2 – wykonanie 3 reguły, powrót do fazy 1
  7. 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.