Подробное введение в простые правила механизма правил Java

Java задняя часть

В последнее время я думаю о техническом рефакторинге выбора на основе правил. Я хочу реализовать его через механизм правил. Я могу воспользоваться этой возможностью, чтобы подробно узнать о механизме правил. В этой статье будет подробно рассказано об использовании механизма правил easy-rules. адрес проекта:GitHub.com/just-easy/easy…

Введение

Easy Rules — простой, но мощный механизм правил Java., который обеспечивает следующие возможности:

  • Легкий фреймворк и простой в освоении API
  • Разработка на основе POJO
  • Поддержка создания комбинированных правил из исходных правил.
  • Поддерживает определение правил с помощью таких выражений, как MVEL, SPEL и JEXL.

начать использовать

импортировать зависимости

<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-core</artifactId>
    <version>4.1.0</version>
</dependency>

Выше представлены только основные зависимости модуля,Если вам нужен другой контент модуля, вы можете импортировать соответствующие зависимости.

определить правила

вводить

Большинство бизнес-правил можно представить следующими определениями:

  • name: Уникальное имя правила в пространстве имен правил.
  • description: краткое описание правила
  • priority: приоритет правила
  • facts: набор известных фактов, когда правило срабатывает
  • conditions: набор условий, которые необходимо выполнить, чтобы правило применялось, с учетом некоторых фактов.
  • actions: набор действий, выполняемых при выполнении условия (может добавлять/удалять/изменять факты)

Easy Rules предоставляет абстракции для каждого ключевого момента определения бизнес-правил. Правила в Easy Rules даныRuleПредставление интерфейса:

public interface Rule extends Comparable<Rule> {

    /**
    * 此方法封装了规则的条件。
    * @return 如果根据提供的事实可以应用规则,则为true,否则为false
    */
    boolean evaluate(Facts facts);

    /**
    * 此方法封装了规则的操作。
    * @throws 如果在执行操作期间发生错误,则抛出异常
    */
    void execute(Facts facts) throws Exception;

    //Getters and setters for rule name, description and priority omitted.

}

evaluate()Метод инкапсулирует должен бытьtrueусловия для срабатывания правила.execute()Метод инкапсулирует действие, которое должно быть выполнено при соблюдении условий правила. условия и действияConditionиActionПредставление интерфейса.

Правила можно определить двумя способами:

  • вPOJOдобавить аннотации для объявления
  • пройти черезRuleBuilderпрограммирование API

Это наиболее распространенные способы определения правил, но вы также можете реализоватьRuleинтерфейс или расширениеBasicRuleДобрый.

Используйте аннотации для определения правил

Простые правила обеспечивают@RuleАннотации, которые могут преобразовывать POJO в правила.

@Rule(name = "my rule", description = "my rule description", priority = 1)
public class MyRule {

    @Condition
    public boolean when(@Fact("fact") fact) {
        // 规则条件
        return true;
    }

    @Action(order = 1)
    public void then(Facts facts) throws Exception {
        // 规则为true时的操作1
    }

    @Action(order = 2)
    public void finally() throws Exception {
        // 规则为true时的操作2
    }
}

@ConditionАннотация используется для обозначения метода оценки условия правила, этот метод должен бытьpublic, который может иметь одну или несколько полос@Factаннотированные параметры и возвращаетbooleanтип. Есть только один способ использования@ConditionТег аннотации.

@ActionАннотации используются для обозначения методов для выполнения действий, а правила могут иметь несколько действий. можно использоватьorderСвойства выполняют операции в указанном порядке.

использоватьRuleBuilderопределить правила

RuleBuilderПозволяет вам определять правила с потоковым API.

Rule rule = new RuleBuilder()
                .name("myRule")
                .description("myRuleDescription")
                .priority(3)
                .when(condition)
                .then(action1)
                .then(action2)
                .build();

В этом примереconditionдаConditionэкземпляр интерфейса,action1иaction2даActionЭкземпляр интерфейса.

Правила комбинирования

Easy Rules позволяет создавать сложные правила из простых правил. ОдинCompositeRuleСостоит из набора правил. Правила композиции — это абстрактное понятие, потому что правила композиции могут запускаться по-разному. Простые правила обеспечивают 3CompositeRuleреализация.

  • UnitRuleGroup: Группа правил объекта представляет собой комбинацию правил, используемых как блок, при этом применяются либо все правила, либо ни одно из них.
  • ActivationRuleGroup: Активация группы правил запускает первое применимое правило и игнорирует другие правила в группе. Сначала правила сортируются в соответствии с их естественным порядком в группе (приоритет по умолчанию).
  • ConditionalRuleGroup: Группа условных правил принимает правило с наивысшим приоритетом в качестве условия, и если правило с наивысшим приоритетом оценивается как истинное, то остальные правила будут активированы.

Комбинированные правила могут быть созданы из исходных правил и зарегистрированы как обычные правила.

// 从两个原始规则创建组合规则
UnitRuleGroup myUnitRuleGroup =
    new UnitRuleGroup("myUnitRuleGroup", "unit of myRule1 and myRule2");
myUnitRuleGroup.addRule(myRule1);
myUnitRuleGroup.addRule(myRule2);

// 像常规规则一样注册组合规则
Rules rules = new Rules();
rules.register(myUnitRuleGroup);

RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, someFacts);

приоритет правила

Каждое правило в Easy Rules имеет приоритет. Это представляет порядок по умолчанию, в котором запускаются правила регистрации. По умолчанию более низкие значения имеют более высокий приоритет. Чтобы переопределить это поведение, вы должны переопределитьcompareTo()метод для предоставления пользовательской стратегии приоритетов.

  • Если это унаследованоBasicRule, вы можете указать приоритет в конструкторе или переопределитьgetPriority()метод.
  • Если вы используете POJO для определения правил, вы можете передать@Ruleаннотированныйpriorityатрибут, чтобы указать приоритет, или использовать@PriorityАннотация отмечает метод. Этот метод должен бытьpublic, без аргументов, но с возвращаемым типомInteger.
  • При использованииRuleBuilderЧтобы определить правила, вы можете использоватьRuleBuilder#priority()Метод указывает приоритет.

Rules API

Набор правил в Easy rules представлен API правил. Он используется следующим образом:

Rules rules = new Rules();
rules.register(myRule1);
rules.register(myRule2);

RulesУказывает пространство имен зарегистрированных правил, поэтому в рамках одного пространства имен каждое зарегистрированное правило должно иметь уникальное имя..

RulesчерезRule#compareTo()методы сравниваются, поэтомуRuleРеализация должна быть реализована правильноcompareTo()метод для обеспечения уникальных имен правил в пределах одного пробела.

определить факты

Факт в простых правилах определяется какFactУказывает:

public class Fact<T> {
   private final String name;
   private final T value;
}

У факта есть имя и значение,Ни один не можетnull. с другой стороны,FactsAPI представляет набор фактов и действует как пространство имен для фактов. Это означает, что вFactsЭкземпляры, факты должны иметь уникальные имена.

Вот пример того, как определить факт:

Fact<String> fact = new Fact("foo", "bar");
Facts facts = new Facts();
facts.add(fact);

Вы также можете использовать более короткую версию, создавая именованные факты с помощью метода put, например:

Facts facts = new Facts();
facts.put("foo", "bar");

можно использовать@FactАннотации вводят факты в условия правил и методы действий.. В следующих правилахrainФакты внедряются вitRainsметодrainСреди параметров:

@Rule
class WeatherRule {

    @Condition
    public boolean itRains(@Fact("rain") boolean rain) {
        return rain;
    }

    @Action
    public void takeAnUmbrella(Facts facts) {
        System.out.println("It rains, take an umbrella!");
        // can add/remove/modify facts
    }

}

ТипFactsВ параметры будут введены все известные факты.

Уведомление:

  • Если введенный факт отсутствует в условном методе, движок зарегистрирует предупреждение и посчитает условную оценку какfalse.
  • Если внедренный факт отсутствует в методе действия, действие не выполняется и выдаетorg.jeasy.rules.core.NoSuchFactExceptionаномальный.

Определение механизма правил

Простые правила обеспечиваютRulesEngineДве реализации интерфейса:

  • DefaultRulesEngine: применять правила в соответствии с их естественным порядком (по умолчанию приоритет).
  • InferenceRulesEngine: Продолжайте применять правила к известным фактам, пока не закончатся доступные правила.

Создать механизм правил

Механизм правил можно создать с помощью конструктора.

RulesEngine rulesEngine = new DefaultRulesEngine();
// or
RulesEngine rulesEngine = new InferenceRulesEngine();

Зарегистрированные правила могут быть активированы следующим образом.

rulesEngine.fire(rules, facts);

Параметры двигателя правил

Движок Easy Rules можно настроить со следующими параметрами:

параметр тип По умолчанию
rulePriorityThreshold int MaxInt
skipOnFirstAppliedRule boolean false
rulePriorityThreshold int false
skipOnFirstFailedRule boolean false
skipOnFirstNonTriggeredRule boolean false
  • skipOnFirstAppliedRule: при успешном применении правила остальные правила пропускаются.
  • skipOnFirstFailedRule: если правило не выполняется, пропустите остальные правила.
  • skipOnFirstNonTriggeredRule: если правило не срабатывает, пропустите остальные правила.
  • rulePriorityThreshold: когда приоритет превышает указанный порог, остальные правила пропускаются.

можно использоватьRulesEngineParametersAPI указывает следующие параметры:

RulesEngineParameters parameters = new RulesEngineParameters()
    .rulePriorityThreshold(10)
    .skipOnFirstAppliedRule(true)
    .skipOnFirstFailedRule(true)
    .skipOnFirstNonTriggeredRule(true);

RulesEngine rulesEngine = new DefaultRulesEngine(parameters);

Если вы хотите получить параметры от вашего движка, вы можете использовать следующий фрагмент кода:

RulesEngineParameters parameters = myEngine.getParameters();

Это позволяет сбросить параметры двигателя после их создания.

Определить прослушиватель правил

в состоянии пройтиRuleListenerAPI для прослушивания событий выполнения правил:

public interface RuleListener {

    /**
     * 在评估规则之前触发。
     *
     * @param rule 正在被评估的规则
     * @param facts 评估规则之前的已知事实
     * @return 如果规则应该评估,则返回true,否则返回false
     */
    default boolean beforeEvaluate(Rule rule, Facts facts) {
        return true;
    }

    /**
     * 在评估规则之后触发
     *
     * @param rule 评估之后的规则
     * @param facts 评估规则之后的已知事实
     * @param evaluationResult 评估结果
     */
    default void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) { }

    /**
     * 运行时异常导致条件评估错误时触发
     *
     * @param rule 评估之后的规则
     * @param facts 评估时的已知事实
     * @param exception 条件评估时发生的异常
     */
    default void onEvaluationError(Rule rule, Facts facts, Exception exception) { }

    /**
     * 在规则操作执行之前触发。
     *
     * @param rule 当前的规则
     * @param facts 执行规则操作时的已知事实
     */
    default void beforeExecute(Rule rule, Facts facts) { }

    /**
     * 在规则操作成功执行之后触发
     *
     * @param rule t当前的规则
     * @param facts 执行规则操作时的已知事实
     */
    default void onSuccess(Rule rule, Facts facts) { }

    /**
     * 在规则操作执行失败时触发
     *
     * @param rule 当前的规则
     * @param facts 执行规则操作时的已知事实
     * @param exception 执行规则操作时发生的异常
     */
    default void onFailure(Rule rule, Facts facts, Exception exception) { }

}

Этот интерфейс может быть реализован для обеспечения пользовательского поведения для выполнения до/после каждого правила. Чтобы зарегистрировать прослушиватель, используйте следующий фрагмент кода:

DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.registerRuleListener(myRuleListener);

Можно зарегистрировать любое количество слушателей, и они будут выполняться в том порядке, в котором были зарегистрированы.

Примечание. При использовании правил композиции слушатели вызываются вокруг правил композиции.

Определение обработчиков правил

в состоянии пройтиRulesEngineListenerAPI для мониторинга событий выполнения механизма правил:

public interface RulesEngineListener {

    /**
     * 在执行规则集之前触发
     *
     * @param rules 要触发的规则集
     * @param facts 触发规则前的事实
     */
    default void beforeEvaluate(Rules rules, Facts facts) { }

    /**
     * 在执行规则集之后触发
     *
     * @param rules 要触发的规则集
     * @param facts 触发规则前的事实
     */
    default void afterExecute(Rules rules, Facts facts) { }
}

RulesEngineListenerПозволяет нам обеспечить пользовательское поведение до/после запуска всего набора правил. Слушатели могут быть зарегистрированы следующим образом.

DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.registerRulesEngineListener(myRulesEngineListener);

Можно зарегистрировать любое количество слушателей, и они будут выполняться в том порядке, в котором были зарегистрированы.

Поддержка языка выражений (EL)

Поддержка простых правилMVEL,SpELиJEXLОпределите правила.

Примечания поставщика EL

У провайдеров EL есть некоторые различия в поведении. Например, если в условии отсутствует факт,MVELвыдает исключение, аSpELОн будет проигнорирован и будет возвращено значение false. Поэтому вы должны понимать эти различия, прежде чем выбирать, какой EL использовать с Easy Rules.

Определение правил программно

Условия, действия и правила определяютсяMVELCondition/SpELCondition/JexlCondition,MVELAction/SpELAction/JexlActionиMVELRule/SpELRule/JexlRuleпредставительство класса. Ниже приведено использованиеMVELПримеры определения правил:

Rule ageRule = new MVELRule()
        .name("age rule")
        .description("Check if person's age is > 18 and marks the person as adult")
        .priority(1)
        .when("person.age > 18")
        .then("person.setAdult(true);");

Определение правил через файл описания правил

Правила можно определить с помощью файла описания правил, используяMVELRuleFactory/SpELRuleFactory/JexlRuleFactoryдля создания правила из файла дескриптора. Ниже приведенalcohol-rule.ymlКитай и ИзраильYAMLформат определенMVELПример правила:

name: "alcohol rule"
description: "children are not allowed to buy alcohol"
priority: 2
condition: "person.isAdult() == false"
actions:
  - "System.out.println("Shop: Sorry, you are not allowed to buy alcohol");"
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
MVELRule alcoholRule = ruleFactory.createRule(new FileReader("alcohol-rule.yml"));

Вы также можете создать несколько правил в одном файле.

---
name: adult rule
description: when age is greater than 18, then mark as adult
priority: 1
condition: "person.age > 18"
actions:
  - "person.setAdult(true);"
---
name: weather rule
description: when it rains, then take an umbrella
priority: 2
condition: "rain == true"
actions:
  - "System.out.println("It rains, take an umbrella!");"

Эти правила можно загрузить вrulesв объекте.

MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rules rules = ruleFactory.createRules(new FileReader("rules.yml"));

Easy Rules также поддерживает загрузку правил из дескрипторов JSON. Конкретные справочные документы здесь не раскрываются.

Обработка ошибок в определениях правил

Поведение движка в отношении некорректных выражений в условиях

Для любых исключений во время выполнения, которые могут возникнуть во время оценки условия (отсутствующие факты, опечатки в выражениях и т. д.), движок регистрирует предупреждение и рассматривает условие, оцененное какfalse. можно использоватьRuleListener#onEvaluationErrorслушать ошибки оценки.

Поведение движка в отношении некорректных выражений в операциях

Для любых исключений во время выполнения, которые могут возникнуть при выполнении операции (отсутствующие факты, опечатки в выражениях и т. д.), операция не будет выполнена, и движок зарегистрирует ошибку. можно использоватьRuleListener#onFailureдля прослушивания исключений выполнения операции. Когда правило не выполняется, механизм переходит к следующему правилу, если оно не установлено.skipOnFirstFailedRuleпараметр.

настоящие каштаны

Этот каштан использует простые правила для реализации приложения FizzBuzz. FizzBuzz — это простое приложение, которое должно считать от 1 до 100 и:

  • Если число кратно 5, выведите «fizz».
  • Если число кратно 7, выведите «buzz».
  • Если число кратно 5 и 7, выведите «fizzbuzz».
  • иначе напечатайте сам номер
public class FizzBuzz {
  public static void main(String[] args) {
    for(int i = 1; i <= 100; i++) {
      if (((i % 5) == 0) && ((i % 7) == 0))
        System.out.print("fizzbuzz");
      else if ((i % 5) == 0) System.out.print("fizz");
      else if ((i % 7) == 0) System.out.print("buzz");
      else System.out.print(i);
      System.out.println();
    }
    System.out.println();
  }
}

Напишем правило для каждого требования:

@Rule
public class FizzRule {

    @Condition
    public boolean isFizz(@Fact("number") Integer number) {
        return number % 5 == 0;
    }

    @Action
    public void printFizz() {
        System.out.print("fizz");
    }

    @Priority
    public int getPriority() {
        return 1;
    }
}
@Rule
public class BuzzRule {

    @Condition
    public boolean isBuzz(@Fact("number") Integer number) {
        return number % 7 == 0;
    }

    @Action
    public void printBuzz() {
        System.out.print("buzz");
    }

    @Priority
    public int getPriority() {
        return 2;
    }
}
public class FizzBuzzRule extends UnitRuleGroup {

    public FizzBuzzRule(Object... rules) {
        for (Object rule : rules) {
            addRule(rule);
        }
    }

    @Override
    public int getPriority() {
        return 0;
    }
}
@Rule
public class NonFizzBuzzRule {

    @Condition
    public boolean isNotFizzNorBuzz(@Fact("number") Integer number) {
        return number % 5 != 0 || number % 7 != 0;
    }

    @Action
    public void printInput(@Fact("number") Integer number) {
        System.out.print(number);
    }

    @Priority
    public int getPriority() {
        return 3;
    }
}

Вот некоторые пояснения к этим правилам:

  • FizzRuleиBuzzRuleПросто они проверяют, является ли ввод кратным 5 или кратным 7, и печатают результат.
  • FizzBuzzRuleявляется комбинационным правилом. пройти черезFizzRuleиBuzzRuleСоздайте. Базовый класс выбирается какUnitRuleGroup, либо выполняются и применяются оба правила, либо ничего не применяется.
  • NonFizzBuzzRuleправило, когда оно не кратно ни 5, ни кратно 7.

Обратите внимание, что мы установили приоритет, чтобы правила срабатывали в том же порядке, что и в примере с Java.

Затем мы должны зарегистрировать эти правила в наборе правил и использовать механизм правил для их запуска:

public class FizzBuzzWithEasyRules {
    public static void main(String[] args) {
        // 创建规则引擎
        RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
        RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);

        // 创建规则
        Rules rules = new Rules();
        rules.register(new FizzRule());
        rules.register(new BuzzRule());
        rules.register(new FizzBuzzRule(new FizzRule(), new BuzzRule()));
        rules.register(new NonFizzBuzzRule());

        // 触发规则
        Facts facts = new Facts();
        for (int i = 1; i <= 100; i++) {
            facts.put("number", i);
            fizzBuzzEngine.fire(rules, facts);
            System.out.println();
        }
    }
}

Обратите внимание, что мы установилиskipOnFirstAppliedRuleпараметр для пропуска последующих правил при успешном применении правила.