Статьи

Слюни исполняемая модель

Исполняемая модель — это реконструкция модели самого низкого уровня Drools, управляемой двигателем. В текущей серии (до 6.x) исполняемая модель органически развивалась за последние 8 лет и никогда не предназначалась для конечных пользователей. Желающим программно написать правила посоветовали сделать это через генерацию кода и целевой drl; который не был идеальным. Не было никакого способа сделать это более доступным для конечных пользователей, потому что широкое использование анонимных классов в Java было громоздким. С Java 8 и Lambda это меняется, и становится возможной возможность сделать более привлекательную модель, доступную для конечных пользователей.

Эта новая модель генерируется в процессе компиляции языков более высокого уровня, но также может использоваться сама по себе. Цель состоит в том, чтобы эта Исполняемая модель была автономной и избегала необходимости дальнейшего манипулирования байтовым кодом (анализа, преобразования или генерации); С точки зрения этой модели, все обеспечивается либо кодом, либо языковыми уровнями более высокого уровня. Например, индексы и т. Д. Должны предоставляться аргументами, которые язык более высокого уровня генерирует посредством анализа, когда он нацелен на модель Исполнимого файла.

Он предназначен для точного сопоставления с создателями уровней Fluent, используя лямбды Java 8. Это сделает его более привлекательным для разработчиков Java и разработчиков языков. Также это позволит разрабатывать и тестировать низкоуровневые функции движка независимо от языка. Это означает, что мы можем внедрять инновации на уровне движка, не беспокоясь о языковом уровне.

Исполняемая модель должна быть достаточно общей для сопоставления с несколькими доменами. Это будет низкоуровневая модель потока данных, в которой вы можете обращаться к моделям функционально-реактивного программирования, но все же ее можно будет использовать для построения системы на основе правил.

В следующем примере показано первое представление беглого DSL, использованного для построения исполняемой модели:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
DataSource persons = sourceOf(new Person("Mark", 37),
                              new Person("Edson", 35),
                              new Person("Mario", 40));
                      
Variable<Person> markV = bind(typeOf(Person.class));
 
Rule rule = rule("Print age of persons named Mark")
        .view(
            input(markV, () -> persons),
            expr(markV, person -> person.getName().equals("Mark"))
        )
        .then(
            on(markV).execute(mark -> System.out.println(mark.getAge())
        )
);

Предыдущий код определяет DataSource, содержащий несколько экземпляров person, и объявляет переменную markV типа Person. Само правило состоит из двух обычных частей: LHS определяется набором входных данных и выражений, передаваемых методу view (), а RHS — это действие, определяемое лямбда-выражением, передаваемым методу then ().

Анализируя LHS более подробно, утверждение:

1
input(markV, () -> persons)

связывает объекты из источника данных Persons с переменной markV, сопоставляя шаблон с классом объекта. В этом смысле источник данных можно рассматривать как эквивалент точки входа Drools.

Наоборот выражение:

1
expr(markV, person -> person.getName().equals("Mark"))

использует Predicate для определения условия, которому должен соответствовать объект, связанный с переменной markV, для того, чтобы ядро ​​успешно соответствовало ему. Обратите внимание, что, как и ожидалось, оценка сопоставления с образцом не выполняется ограничением, сгенерированным в результате какого-либо анализа или процесса компиляции, а просто выполняется путем применения лямбда-выражения, реализующего предикат (в данном случае person — > person.getName (). equals («Mark»)) к сопоставляемому объекту. Другими словами, первый DSL создает исполняемую модель правила, эквивалентную модели, полученной в результате анализа следующего drl.

1
2
3
4
5
6
rule "Print age of persons named Mark"
when
    markV : Person( name == "Mark" ) from entry-point "persons"
then
    System.out.println(markV.getAge());
end

Он также находится в стадии разработки и может быть скорректирован по правилам, определенным этим DSL. В частности, можно добавить эти правила в CanonicalKieBase, а затем создать из него KieSessions, как и для любого другого обычного KieBase.

1
2
3
4
5
CanonicalKieBase kieBase = new CanonicalKieBase();
kieBase.addRules(rule);
 
KieSession ksession = kieBase.newKieSession();
ksession.fireAllRules();

Конечно, DSL также позволяет определять более сложные условия, такие как соединения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Variable<Person> markV = bind(typeOf(Person.class));
Variable<Person> olderV = bind(typeOf(Person.class));
 
Rule rule = rule("Find persons older than Mark")
        .view(
            input(markV, () -> persons),
            input(olderV, () -> persons),
            expr(markV, mark -> mark.getName().equals("Mark")),
            expr(olderV, markV, (older, mark) -> older.getAge() > mark.getAge())
        )
        .then(
            on(olderV, markV)
                .execute((p1, p2) -> System.out.println(p1.getName() + " is older than " + p2.getName())
        )
);

или экзистенциальные модели:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
Variable<Person> oldestV = bind(typeOf(Person.class));
Variable<Person> otherV = bind(typeOf(Person.class));
 
Rule rule = rule("Find oldest person")
        .view(
            input(oldestV, () -> persons),
            input(otherV, () -> persons),
            not(otherV, oldestV, (p1, p2) -> p1.getAge() > p2.getAge())
        )
        .then(
            on(oldestV)
                .execute(p -> System.out.println("Oldest person is " + p.getName())
        )
);

Здесь not () обозначает отрицание любого выражения, поэтому используемая выше форма на самом деле является всего лишь ярлыком для:

1
not( expr( otherV, oldestV, (p1, p2) -> p1.getAge() > p2.getAge() ) )

Также накопление уже поддерживается в следующей форме:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Variable<Person> person = bind(typeOf(Person.class));
Variable<Integer> resultSum = bind(typeOf(Integer.class));
Variable<Double> resultAvg = bind(typeOf(Double.class));
 
Rule rule = rule("Calculate sum and avg of all persons having a name starting with M")
        .view(
            input(person, () -> persons),
            accumulate(expr(person, p -> p.getName().startsWith("M")),
                       sum(Person::getAge).as(resultSum),
                       avg(Person::getAge).as(resultAvg))
        )
        .then(
            on(resultSum, resultAvg)
                .execute((sum, avg) -> result.value = "total = " + sum + "; average = " + avg)
);

Чтобы предоставить еще один более полный вариант использования, с помощью этого DSL можно определить исполняемую модель классического примера пожарной тревоги и сигнализации следующим образом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
Variable<Room> room = any(Room.class);
Variable<Fire> fire = any(Fire.class);
Variable<Sprinkler> sprinkler = any(Sprinkler.class);
Variable<Alarm> alarm = any(Alarm.class);
 
Rule r1 = rule("When there is a fire turn on the sprinkler")
        .view(
            input(fire),
            input(sprinkler),
            expr(sprinkler, s -> !s.isOn()),
            expr(sprinkler, fire, (s, f) -> s.getRoom().equals(f.getRoom()))
        )
        .then(
            on(sprinkler)
                .execute(s -> {
                    System.out.println("Turn on the sprinkler for room " + s.getRoom().getName());
                    s.setOn(true);
                })
                .update(sprinkler, "on")
);
 
Rule r2 = rule("When the fire is gone turn off the sprinkler")
        .view(
            input(sprinkler),
            expr(sprinkler, Sprinkler::isOn),
            input(fire),
            not(fire, sprinkler, (f, s) -> f.getRoom().equals(s.getRoom()))
        )
        .then(
            on(sprinkler)
                .execute(s -> {
                    System.out.println("Turn off the sprinkler for room " + s.getRoom().getName());
                    s.setOn(false);
                })
                .update(sprinkler, "on")
);
 
Rule r3 = rule("Raise the alarm when we have one or more fires")
        .view(
            input(fire),
            exists(fire)
        )
        .then(
            execute(() -> System.out.println("Raise the alarm"))
                .insert(() -> new Alarm())
);
 
Rule r4 = rule("Lower the alarm when all the fires have gone")
        .view(
            input(fire),
            not(fire),
            input(alarm)
        )
        .then(
            execute(() -> System.out.println("Lower the alarm"))
                .delete(alarm)
);
 
Rule r5 = rule("Status output when things are ok")
        .view(
            input(alarm),
            not(alarm),
            input(sprinkler),
            not(sprinkler, Sprinkler::isOn)
        )
        .then(
            execute(() -> System.out.println("Everything is ok"))
);
 
CanonicalKieBase kieBase = new CanonicalKieBase();
kieBase.addRules(r1, r2, r3, r4, r5);
 
KieSession ksession = kieBase.newKieSession();
 
// phase 1
Room room1 = new Room("Room 1");
ksession.insert(room1);
FactHandle fireFact1 = ksession.insert(new Fire(room1));
ksession.fireAllRules();
 
// phase 2
Sprinkler sprinkler1 = new Sprinkler(room1);
ksession.insert(sprinkler1);
ksession.fireAllRules();
 
assertTrue(sprinkler1.isOn());
 
// phase 3
ksession.delete(fireFact1);
ksession.fireAllRules();

В этом примере можно отметить еще несколько вещей:

  • Некоторые повторения необходимы для привязки параметров выражения к формальным параметрам лямбда-выражения, оценивающего его. Надеюсь, что эту проблему JDK удастся преодолеть, используя аргумент компиляции -parameters.
  • any (Room.class) — это ярлык для связывания (typeOf (Room.class))
  • Входные данные не объявляют источник данных. Это ярлык для указания того, что эти объекты получены из пустого источника данных по умолчанию (соответствует точке входа по умолчанию Drools). Фактически в этом примере факты программно вставляются в KieSession.
  • Использование входа без предоставления какого-либо выражения для этого входа на самом деле является ярлыком для входа (аварийный сигнал), expr (аварийный сигнал, a -> true)
  • Таким же образом экзистенциальный шаблон без каких-либо условий, таких как not (огонь), является еще одним ярлыком для not (expr (fire, f -> true))
  • Синтаксис Java 8 также позволяет определять предикат как ссылку на метод, обращаясь к логическому свойству факта, подобного expr (sprinkler, Sprinkler :: isOn)
  • RHS вместе с блоком кода, который должен быть выполнен, также обеспечивает свободный интерфейс для определения действий с рабочей памятью (вставки / обновления / удаления), которые должны выполняться при запуске правила. В частности, обновление также получает переменные Strings, сообщающие имя свойств, измененных в обновленном факте, как в обновлении (sprinkler, «on»). Еще раз, эта информация должна быть явно предоставлена, потому что исполняемая модель должна создаваться без необходимости какого-либо анализа кода.
Ссылка: Исполняемая модель Drools от нашего партнера JCG Марио Фуско в блоге Drools & jBPM .