Dziś będzie i filozoficznie i technologicznie.

Część I filozoficzna

Ostatnio była mowa w mediach, że „Polacy mało czytają”. Dla wielu osób to stwierdzenie nic nie oznacza. Przecież świat się nie zawali, a co najwyżej zbankrutują wydawcy i producenci regałów na książki. Rzecz w tym, że im mniej książek czytamy, tym bardziej, jako społeczeństwo, głupiejemy. Można powiedzieć, że czytamy więcej w internecie, ale nie wiedzieć czemu im mniej książek i więcej internetu, tym poważniejsze problemy ze zrozumieniem tego, co czytamy.
Nie wiem, z czego to wynika. Może książki roztaczają jakąś magiczną aurę, która powoduje, że łatwiej nam zrozumieć przekaz? Może po prostu wymagają trochę innej techniki czytania i tym samym zmuszają nasze mózgi do wytężonej pracy. Zresztą nieważne.
Dodatkowym problemem, poza niezrozumieniem czytanego tekstu, jest też zanik umiejętności pisania w sposób łatwy w odbiorze. Może jest to dziwne, ale współczesny człowiek, który nie czyta, nie będzie też wstanie, zapisać informacji w sposób zrozumiały. Tym samym utrudnia jej przetworzenie i udzielenie odpowiedzi. To prowadzi do problemów z komunikacją i realnych strat.
Zresztą ile razy mieliście do czynienia z mailem od klienta, z którego to maila wydobycie jakiejkolwiek informacji graniczyło z cudem? Sztuczny język, niedomówienia oraz tryb „bo myślałem, że” są istotną przeszkodą w szukaniu rozwiązania problemu. Dodatkowo wytwarzają nowe problemy związane z komunikacją.
Wniosek. Społeczeństwo, które nie czyta, nie będzie miało możliwości komunikacji. Bez komunikacji upadnie.

Część II techniczna

Pomysł w fabryczce był taki, by użyć JBoss Drools do opędzenia nowego zadania. Zadaniem tym było stworzenie nowego systemu fakturująco-raportowego. Ogólna zasada działania jest taka. Mamy sobie jakieś dane dotyczące sprzedaży. Ładujemy je do obiektów reprezentujących te dane na kilka prostych sposobów. Mamy zatem jakieś obiekty domenowe tzn. obiekty przechowujące gołe dane z bazy pogrupowane w jakiś sposób tak by było je łatwo obrabiać wzbogacone o metody pozwalające pracować na tych danych w ramach domeny (w praktyce liczące średnie, sumujące ilości dla określonych typów danych itp.). To są nasze „raporty bazowe”, czyli jakieś abstrakcyjne byty reprezentujące różne raporty. Obiekty te można wepchnąć do Jaspera w celu wygenerowania tabelek. To jest część niezmienna rozwiązania. Częścią zmienną jest cennik usług, który będzie zawierał różne kombinacje cen dla naszych klientów. Cena jednostki zależy od tego, jak sobie akurat księgowość z biznesowymi wymyśli. Czasami wszystkie sprzedane jednostki są traktowane równo. Czasami cena jednostki zależy od jej konkretnych właściwości (rodzaju, czasu zakupu itp.). Do tego mamy rabaty. Wykombinowałem zatem, że można by użyć Droolsowego DSLa, zdefiniować listę leksemówW dla warunków i akcji, dać to biznesowym oraz zafundować im krótkie szkolenie i mieć z głowy. Pomysł upadł z powodu braku czasu na wdrożenie nowej technologii, ale wiedza została. Zatem krótka prezentacja jak zbudować sobie własnego DSLa za pomocą JBoss Drools.

Co to jest DSL?

DSL- ang. Domain Specific Language – język specyficzny dla domeny. Najprościej rzecz ujmując, jest to zestaw pojęć, memów i określeń mających znaczenie w danym kontekście. Najlepszym przykładem niech będzie tu grypsera jako DSL gitów.
Język ten ma jeszcze jedną zaletę. Jest zrozumiały dla każdego operującego w danym środowisku (domenie). Dodatkowo może mieć różne znaczenia w zależności od domeny. Tak jak programiści java mówią o Klasie i Obiekcie, tak samo biolodzy mogą mówić o tych rzeczach, ale w całkowicie innym znaczeniu.
Zazwyczaj DSL jest też naturalnym sposobem opisu uniwersum danej domeny. W DSLu nie ma bytów nadmiarowych lub braków w opisie świata. To powoduje, że można opisywać byty domenowe szybciej, prościej i w sposób jednoznaczny.
Oczywiście DSL nie nadaje się do opisywania bytów spoza domeny. Ewentualnie jest to bardzo utrudnione.

Zadanie

Mamy grupę istot. Każdy z nich ma imię, płeć oraz typ (demon/człowiek). W zależności od tego, w jakiej konfiguracji występują płeć i typ, możemy mówić o sukkubach – demonach żeńskich i inkubach – demonach męskich.
Stwórzmy zatem zestaw zasad określających przynależność danej istoty do poszczególnych grup. Zdefiniujmy jednak język pozwalający na tworzenie tych zasad w możliwie naturalny sposób.

Rozwiązanie

Rozwiązanie ma kilka kroków.

maszynka do reguł

Na początek prosta klasa ładująca nam pliki z regułami do Droolsów i tworzącą sesję:

Listing 1. SessionBuilder

package pl.koziolekweb.vaadin.drools;

import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderError;
import org.drools.builder.KnowledgeBuilderErrors;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.io.ResourceFactory;
import org.drools.runtime.StatefulKnowledgeSession;

public class SessionBuilder {

	private void addResourcesFiles(KnowledgeBuilder knowledgeBuilder,
			String[] fileNames, ResourceType type) {
		for (String fileName : fileNames) {
			knowledgeBuilder.add(ResourceFactory.newFileResource(fileName),
					type);
		}
	}

	private KnowledgeBase getKnowledgeBase(KnowledgeBuilder knowledgeBuilder) {
		KnowledgeBase knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
		knowledgeBase.addKnowledgePackages(knowledgeBuilder
				.getKnowledgePackages());
		return knowledgeBase;
	}

	public StatefulKnowledgeSession getStatelesSessionFromDlrFiles(
			String... fileNames) {
		KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory
				.newKnowledgeBuilder();
		addResourcesFiles(knowledgeBuilder, fileNames, ResourceType.DRL);
		KnowledgeBase knowledgeBase = getKnowledgeBase(knowledgeBuilder);
		return knowledgeBase.newStatefulKnowledgeSession();
	}

	public StatefulKnowledgeSession getStatefulSessionFromDslFiles(
			String[] dslFileNames, String[] dslrFileNames) {
		KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory
				.newKnowledgeBuilder();

		addResourcesFiles(knowledgeBuilder, dslFileNames, ResourceType.DSL);
		addResourcesFiles(knowledgeBuilder, dslrFileNames, ResourceType.DSLR);
		
		KnowledgeBuilderErrors errors = knowledgeBuilder.getErrors();
		if (errors.size() > 0){
			for (KnowledgeBuilderError e : errors) {
				System.out.println(e.getMessage());
			}
		}
		KnowledgeBase knowledgeBase = getKnowledgeBase(knowledgeBuilder);
		return knowledgeBase.newStatefulKnowledgeSession();
	}
}

Tu nie ma co do opowiadania. Jedna metoda pozwala na budowę sesji na podstawie zwykłych reguł, druga na podstawie zbioru definicji DSL i reguł zapisanych w tym DSLu (DSLR).

Mapowanie słów kluczowych

Teraz czas na pierwszy zestaw mapowań DSL. Przetłumaczymy sobie słowa kluczowe Drools na polski:

Listing 2. keywords\_pl.dsl

#Zestaw mapowań słów kluczowych na język polski

# szablon reguły
[keyword][][Rr]eguła=rule
[keyword][][Jj]eżeli=when
[keyword][]to=then
[keyword][]koniec=end

# operatory
[keyword][]lub=||
[keyword][]i=and
[keyword][]albo=^

# dodatkowe operatory
[condition][]{value} istnieje={value}!=null
[condition][]nie ma {value} ={value}==null

# wagi
[keyword][][Nn]ajważniejsza= salience 1000
[keyword][][Ww]ażniejsza= salience 500
[keyword][][Ww]ażna= salience 100
[keyword][][Nn]ieważna= salience -1000

Mapowania uwzględniają wielkość liter.

Czas na właściwy DSL

Teraz stworzymy słownik, w którym zawarte będą leksemy naszego języka. W Drools słownik można podzielić na dwie główne części. Pierwsza to warunki, czyli te elementy, które znajdują się po słowie when w klasycznym zapisie reguł. Druga to akcje, czyli to, co stoi po słowie then.

Listing 3. bible.dsl

[condition][]jest osoba która {C}= $p : Person({C});
[condition][]jest człowiekiem=type == Type.Human
[condition][]jest demonem=type == Type.Deamon
[condition][]jest mężczyzną=sex == Sex.Male
[condition][]jest kobietą=sex == Sex.Female
[condition][]ma na imię {value}=name.equals({value});
[condition][]płeć=sex
[condition][]typ=type
[consequence][]zapisz jako {type}=System.out.println($p.getName() + " to {type}");

Reguły są proste. To, co przed pierwszym znakiem = to leksem DSLowy, to co po = odpowiednik w klasycznych regułach Drools.

Piszemy reguły

Na koniec zestaw reguł zapisanych za pomocą naszego DSLa.

Listing 4. bible.dslr

# zestaw reguł

import pl.koziolekweb.vaadin.model.*;

reguła "jest człowiekiem"
Najważniejsza
Jeżeli 
	jest osoba która jest człowiekiem 
	to 
	zapisz jako człowiek.
koniec

reguła "jest demonem"
Jeżeli 
	jest osoba która jest demonem 
	to 
	zapisz jako demon.
koniec

reguła "jest kobietą"
ważniejsza
Jeżeli 
	jest osoba która jest kobietą 
	to 
	zapisz jako kobieta.
koniec

reguła "jest mężczyzną"
ważniejsza
Jeżeli 
	jest osoba która jest mężczyzną 
	to 
	zapisz jako mężczyzna.
koniec

reguła "jest inkubem"
nieważna
Jeżeli 
	jest osoba która jest mężczyzną, jest demonem 
	to 
	zapisz jako inkub.
koniec

reguła "jest sukkubem"
nieważna
Jeżeli 
	jest osoba która jest kobietą, jest demonem 
	to 
	zapisz jako sukkub.
koniec

reguła "chuj go wie"
nieważna
Jeżeli 
	jest osoba która nie ma płeć
	to 
	zapisz jako justinek koffany.
koniec

Ważna rzecz to formatowanie. Niestety trzeba się trzymać zasad i rozbijać zdania na linijki. Trochę to dziwnie wygląda, ale grunt, że działa.

Uruchomienie

Nie będę się macał tylko przez TestNg:

Listing 5. uruchamiamy

package pl.koziolekweb.vaadin.drools.bible;

import org.drools.runtime.StatefulKnowledgeSession;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import pl.koziolekweb.vaadin.drools.SessionBuilder;
import pl.koziolekweb.vaadin.model.Person;
import pl.koziolekweb.vaadin.model.Sex;
import pl.koziolekweb.vaadin.model.Type;

public class DroolsBibleTest {

	private SessionBuilder builder;
	private String[] dslFileNames;
	private String[] dslrFileNames;

	@BeforeClass
	protected void setUp() {
		builder = new SessionBuilder();
		String plDsl = "./src/main/rules/keywords_pl.dsl";
		String bibleDsl = "./src/main/rules/bible.dsl";
		String bibleDslr = "./src/main/rules/bible.dslr";
		dslFileNames = new String[] { plDsl, bibleDsl };
		dslrFileNames = new String[] { bibleDslr };
	}

	@Test
	public void fireRules() {
		StatefulKnowledgeSession session = builder
				.getStatefulSessionFromDslFiles(dslFileNames, dslrFileNames);
		session.insert(new Person("Adam", Sex.Male, Type.Human));
		session.insert(new Person("Behemoth", Sex.Male, Type.Deamon));
		session.insert(new Person("Ewa", Sex.Female, Type.Human));
		session.insert(new Person("Lilith", Sex.Female, Type.Deamon));
		session.insert(new Person("Justin", null, Type.Human));
		session.fireAllRules();
	}
}

No to tyle. Prawda, że proste 😀