Подтвердить что ты не робот

Создание простого правила в java

Я изучаю различные способы создания простого механизма бизнес-правил в Java. Мне нужно представить клиенту простой webapp, который позволяет ему настраивать кучу правил. Пример базы правил может выглядеть так:

Здесь пример:

 IF (PATIENT_TYPE = "A" AND ADMISSION_TYPE="O")
 SEND TO OUTPATIENT
 ELSE IF PATIENT_TYPE = "B" 
 SEND TO INPATIENT

Механизм правил довольно прост, последнее действие может быть всего лишь одним из двух действий, отправляемых в стационарный или амбулаторный. Операторы, участвующие в выражении, могут быть =,>,<,!=, а логические операторы между выражениями AND, OR and NOT.

Я хочу создать веб-приложение, в котором пользователь будет писать небольшим script в textarea, и я бы оценил выражение - таким образом, бизнес-правила объясняются простым английским языком, а бизнес-пользователь имеет полный контроль над логикой.

Из исследования, которое я сделал до сих пор, я столкнулся с ANTLR и написал собственный язык сценариев как возможные варианты решения этой проблемы. Я не изучаю такие опции, как Drools rules engine, потому что у меня такое ощущение, что здесь может быть избыток. Был ли у вас опыт решения таких проблем? Если да, как вы это сделали?

4b9b3361

Ответ 1

Реализация простой системы оценки на основе правил в Java не так уж и трудна. Вероятно, парсер для выражения - это самый сложный материал. В приведенном ниже примере кода используется несколько шаблонов для достижения желаемой функциональности.

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

Выражение, представленное в приведенном выше примере, состоит из операций, переменных и значений. В отношении wiki-example все, что можно объявить, это Expression. Поэтому интерфейс выглядит следующим образом:

import java.util.Map;

public interface Expression
{
    public boolean interpret(final Map<String, ?> bindings);
}

В то время как пример на wiki-странице возвращает int (они реализуют калькулятор), нам нужно только логическое значение возврата здесь, чтобы решить, должно ли выражение запускать действие, если выражение оценивается как true.

Выражение может, как указано выше, быть либо операцией типа =, AND, NOT,... или Variable, либо ее Value. Определение a Variable приводится ниже:

import java.util.Map;

public class Variable implements Expression
{
    private String name;

    public Variable(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return this.name;
    }

    @Override
    public boolean interpret(Map<String, ?> bindings)
    {
        return true;
    }
}

Проверка имени переменной не имеет такого смысла, поэтому true возвращается по умолчанию. То же самое справедливо для значения переменной, которая поддерживается как можно более общая при определении только BaseType:

import java.util.Map;

public class BaseType<T> implements Expression
{
    public T value;
    public Class<T> type;

    public BaseType(T value, Class<T> type)
    {
        this.value = value;
        this.type = type;
    }

    public T getValue()
    {
        return this.value;
    }

    public Class<T> getType()
    {
        return this.type;
    }

    @Override
    public boolean interpret(Map<String, ?> bindings)
    {
        return true;
    }

    public static BaseType<?> getBaseType(String string)
    {
        if (string == null)
            throw new IllegalArgumentException("The provided string must not be null");

        if ("true".equals(string) || "false".equals(string))
            return new BaseType<>(Boolean.getBoolean(string), Boolean.class);
        else if (string.startsWith("'"))
            return new BaseType<>(string, String.class);
        else if (string.contains("."))
            return new BaseType<>(Float.parseFloat(string), Float.class);
        else
            return new BaseType<>(Integer.parseInt(string), Integer.class);
    }
}

Класс BaseType содержит метод factory для генерации конкретных типов значений для определенного типа Java.

An Operation теперь является специальным выражением типа AND, NOT, =,... Абстрактный базовый класс Operation определяет левый и правый операнды, так как операнд может ссылаться на большее, чем одно выражение. F.E. NOT, вероятно, относится только к его правому выражению и отрицает его результат проверки, поэтому true превращается в false и наоборот. Но AND на другой стороне сочетает левое и правое выражение логически, заставляя оба выражения быть истинными при проверке.

import java.util.Stack;

public abstract class Operation implements Expression
{
    protected String symbol;

    protected Expression leftOperand = null;
    protected Expression rightOperand = null;

    public Operation(String symbol)
    {
        this.symbol = symbol;
    }

    public abstract Operation copy();

    public String getSymbol()
    {
        return this.symbol;
    }

    public abstract int parse(final String[] tokens, final int pos, final Stack<Expression> stack);

    protected Integer findNextExpression(String[] tokens, int pos, Stack<Expression> stack)
    {
        Operations operations = Operations.INSTANCE;

        for (int i = pos; i < tokens.length; i++)
        {
            Operation op = operations.getOperation(tokens[i]);
            if (op != null)
            {
                op = op.copy();
                // we found an operation
                i = op.parse(tokens, i, stack);

                return i;
            }
        }
        return null;
     }
}

Возможно, две операции прыгают в глаза. int parse(String[], int, Stack<Expression>); реорганизует логику разбора конкретной операции соответствующему классу операций, так как он, вероятно, лучше знает, что ему нужно для создания действительной операции. Integer findNextExpression(String[], int, stack); используется для поиска правой части операции при разборе строки в выражении. Может показаться странным возвращать int вместо выражения, но выражение помещается в стек, а возвращаемое значение здесь возвращает позицию последнего токена, используемого созданным выражением. Таким образом, значение int используется для пропуска уже обработанных токенов.

Операция AND выглядит примерно так:

import java.util.Map;
import java.util.Stack;

public class And extends Operation
{    
    public And()
    {
        super("AND");
    }

    public And copy()
    {
        return new And();
    }

    @Override
    public int parse(String[] tokens, int pos, Stack<Expression> stack)
    {
        Expression left = stack.pop();
        int i = findNextExpression(tokens, pos+1, stack);
        Expression right = stack.pop();

        this.leftOperand = left;
        this.rightOperand = right;

        stack.push(this);

        return i;
    }

    @Override
    public boolean interpret(Map<String, ?> bindings)
    {
        return leftOperand.interpret(bindings) && rightOperand.interpret(bindings);
    }
}

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

NOT аналогичен в этом случае, но только устанавливает правую сторону, как описано ранее:

import java.util.Map;
import java.util.Stack;

public class Not extends Operation
{    
    public Not()
    {
        super("NOT");
    }

    public Not copy()
    {
        return new Not();
    }

    @Override
    public int parse(String[] tokens, int pos, Stack<Expression> stack)
    {
        int i = findNextExpression(tokens, pos+1, stack);
        Expression right = stack.pop();

        this.rightOperand = right;
        stack.push(this);

        return i;
    }

    @Override
    public boolean interpret(final Map<String, ?> bindings)
    {
        return !this.rightOperand.interpret(bindings);
    }    
}

Оператор = используется для проверки значения переменной, если он фактически равен определенному значению в карте привязок, предоставленной в качестве аргумента в методе interpret.

import java.util.Map;
import java.util.Stack;

public class Equals extends Operation
{      
    public Equals()
    {
        super("=");
    }

    @Override
    public Equals copy()
    {
        return new Equals();
    }

    @Override
    public int parse(final String[] tokens, int pos, Stack<Expression> stack)
    {
        if (pos-1 >= 0 && tokens.length >= pos+1)
        {
            String var = tokens[pos-1];

            this.leftOperand = new Variable(var);
            this.rightOperand = BaseType.getBaseType(tokens[pos+1]);
            stack.push(this);

            return pos+1;
        }
        throw new IllegalArgumentException("Cannot assign value to variable");
    }

    @Override
    public boolean interpret(Map<String, ?> bindings)
    {
        Variable v = (Variable)this.leftOperand;
        Object obj = bindings.get(v.getName());
        if (obj == null)
            return false;

        BaseType<?> type = (BaseType<?>)this.rightOperand;
        if (type.getType().equals(obj.getClass()))
        {
            if (type.getValue().equals(obj))
                return true;
        }
        return false;
    }
}

Как видно из метода parse, значение присваивается переменной с переменной, находящейся в левой части символа =, и значением с правой стороны.

Кроме того, интерпретация проверяет наличие имени переменной в привязках переменных. Если он недоступен, мы знаем, что этот термин не может оцениваться как истинный, поэтому мы можем пропустить процесс оценки. Если он присутствует, мы извлекаем информацию с правой стороны (= Value part) и сначала проверяем, является ли тип класса равным, и если да, если фактическое значение переменной соответствует привязке.

Поскольку фактический синтаксический разбор выражений реорганизуется в операции, фактический синтаксический анализатор довольно тонкий:

import java.util.Stack;

public class ExpressionParser
{
    private static final Operations operations = Operations.INSTANCE;

    public static Expression fromString(String expr)
    {
        Stack<Expression> stack = new Stack<>();

        String[] tokens = expr.split("\\s");
        for (int i=0; i < tokens.length-1; i++)
        {
            Operation op = operations.getOperation(tokens[i]);
            if ( op != null )
            {
                // create a new instance
                op = op.copy();
                i = op.parse(tokens, i, stack);
            }
        }

        return stack.pop();
    }
}

Здесь метод copy, вероятно, самый интересный. Поскольку разбор довольно общий, мы не знаем заранее, какая операция обрабатывается в настоящее время. При возврате найденной операции среди зарегистрированных приводит к модификации этого объекта. Если у нас есть только одна операция такого рода в нашем выражении, это не имеет значения - если мы, однако, имеем несколько операций (например, две или более равных-операции), операция повторно используется и, следовательно, обновляется с новым значением. Поскольку это также изменяет ранее созданные операции такого типа, нам нужно создать новый экземпляр операции - copy() достигает этого.

Operations - это контейнер, который содержит ранее зарегистрированные операции и сопоставляет операцию с указанным символом:

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public enum Operations
{
    /** Application of the Singleton pattern using enum **/
    INSTANCE;

    private final Map<String, Operation> operations = new HashMap<>();

    public void registerOperation(Operation op, String symbol)
    {
        if (!operations.containsKey(symbol))
            operations.put(symbol, op);
    }

    public void registerOperation(Operation op)
    {
        if (!operations.containsKey(op.getSymbol()))
            operations.put(op.getSymbol(), op);
    }

    public Operation getOperation(String symbol)
    {
        return this.operations.get(symbol);
    }

    public Set<String> getDefinedSymbols()
    {
        return this.operations.keySet();
    }
}

Рядом с рисунком одноэлементного перечисления ничего действительно не интересно.

A Rule теперь содержит одно или несколько выражений, которые при оценке могут вызвать определенное действие. Поэтому правилу необходимо удержать ранее разобранные выражения и действие, которое должно быть инициировано в случае успеха.

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class Rule
{
    private List<Expression> expressions;
    private ActionDispatcher dispatcher;

    public static class Builder
    {
        private List<Expression> expressions = new ArrayList<>();
        private ActionDispatcher dispatcher = new NullActionDispatcher();

        public Builder withExpression(Expression expr)
        {
            expressions.add(expr);
            return this;
        }

        public Builder withDispatcher(ActionDispatcher dispatcher)
        {
            this.dispatcher = dispatcher;
            return this;
        }

        public Rule build()
        {
            return new Rule(expressions, dispatcher);
        }
    }

    private Rule(List<Expression> expressions, ActionDispatcher dispatcher)
    {
        this.expressions = expressions;
        this.dispatcher = dispatcher;
    }

    public boolean eval(Map<String, ?> bindings)
    {
        boolean eval = false;
        for (Expression expression : expressions)
        {
            eval = expression.interpret(bindings);
            if (eval)
                dispatcher.fire();
        }
        return eval;
    }
}

Здесь шаблон здания используется, чтобы иметь возможность добавлять несколько выражений, если это необходимо для того же действия. Кроме того, Rule по умолчанию определяет NullActionDispatcher. Если выражение успешно оценивается, диспетчер запускает метод fire(), который будет обрабатывать действие, которое должно быть выполнено при успешной проверке. Нулевой шаблон используется здесь, чтобы избежать обработки нулевых значений в случае, если не требуется выполнение действия, поскольку должны выполняться только проверки true или false. Интерфейс также прост:

public interface ActionDispatcher
{
    public void fire();
}

Как я действительно не знаю, какими должны быть ваши действия INPATIENT или OUTPATIENT, метод fire() запускает вызов метода System.out.println(...);:

public class InPatientDispatcher implements ActionDispatcher
{
    @Override
    public void fire()
    {
        // send patient to in_patient
        System.out.println("Send patient to IN");
    }
}

И последнее, но не менее важное: простой основной метод проверки поведения кода:

import java.util.HashMap;
import java.util.Map;

public class Main 
{
    public static void main( String[] args )
    {
        // create a singleton container for operations
        Operations operations = Operations.INSTANCE;

        // register new operations with the previously created container
        operations.registerOperation(new And());
        operations.registerOperation(new Equals());
        operations.registerOperation(new Not());

        // defines the triggers when a rule should fire
        Expression ex3 = ExpressionParser.fromString("PATIENT_TYPE = 'A' AND NOT ADMISSION_TYPE = 'O'");
        Expression ex1 = ExpressionParser.fromString("PATIENT_TYPE = 'A' AND ADMISSION_TYPE = 'O'");
        Expression ex2 = ExpressionParser.fromString("PATIENT_TYPE = 'B'");

        // define the possible actions for rules that fire
        ActionDispatcher inPatient = new InPatientDispatcher();
        ActionDispatcher outPatient = new OutPatientDispatcher();

        // create the rules and link them to the accoridng expression and action
        Rule rule1 = new Rule.Builder()
                            .withExpression(ex1)
                            .withDispatcher(outPatient)
                            .build();

        Rule rule2 = new Rule.Builder()
                            .withExpression(ex2)
                            .withExpression(ex3)
                            .withDispatcher(inPatient)
                            .build();

        // add all rules to a single container
        Rules rules = new Rules();
        rules.addRule(rule1);
        rules.addRule(rule2);

        // for test purpose define a variable binding ...
        Map<String, String> bindings = new HashMap<>();
        bindings.put("PATIENT_TYPE", "'A'");
        bindings.put("ADMISSION_TYPE", "'O'");
        // ... and evaluate the defined rules with the specified bindings
        boolean triggered = rules.eval(bindings);
        System.out.println("Action triggered: "+triggered);
    }
}

Rules здесь просто класс контейнера для правил и распространяется вызов eval(bindings); для каждого определенного правила.

Я не включаю другие операции, так как сообщение здесь уже давно, но вам не нужно слишком сложно реализовать их самостоятельно, если вы этого хотите. Кроме того, я не включил свою структуру пакета, поскольку вы, вероятно, будете использовать свою собственную. Furhtermore, я не включал обработку исключений, я оставляю это всем, кто собирается скопировать и вставить код:)

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

Вместо использования системы, основанной на правилах, сеть petri или даже BPMN в сочетании с открытым исходным кодом Activiti Engine можно было бы достичь этой задачи. Здесь операции уже определены внутри языка, вам нужно только определить конкретные операторы как задачи, которые могут выполняться автоматически - и в зависимости от результата задачи (т.е. Одного оператора) она будет проходить через" график", Поэтому моделирование обычно выполняется в графическом редакторе или интерфейсе, чтобы избежать использования XML-характера языка BPMN.

Ответ 2

В принципе... Не делайте этого

Чтобы понять, почему:

Я знаю, что это выглядит как отличная идея издалека, но механизм бизнес-правил всегда будет сложнее поддерживать, развертывать и отлаживать, а затем язык программирования, на котором он был написан - не составите свои собственные языки программирования, если вы можете помочь ему.

Я лично спустился по этой дороге в бывшую фирму, и я видел, где она проходит через пару лет (гигантские неудержимые сценарии, сидящие в базе данных, написанные на языке, который исходил прямо из параллельного измерения, где Бог ненавидит нас которые в конце концов никогда не удовлетворяют 100% ожиданий клиентов, потому что они не так сильны, как правильный язык программирования, и в то же время они слишком запутаны и злы для разработчиков (неважно, клиент)).

Я знаю, что есть определенный клиент, который очарован идеей, что они не будут платить часами программиста за "адаптацию бизнес-правил" и мало понимают, что в конце концов они будут хуже и привлечь такого клиента вам нужно что-то сделать в этом направлении - , но все, что вы делаете, не придумывает что-то из ваших собственных.

Существует множество достойных языков сценариев, которые снабжаются хорошими инструментами (которые не требуют компиляции, поэтому их можно загружать динамически и т.д.), которые могут быть слизко связаны и вызываться из кода Java и использовать преимущества реализованной Java apis которые вы предоставляете, см. http://www.slideshare.net/jazzman1980/j-ruby-injavapublic#btnNext, например, Jython,

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

Ответ 3

Я бы предложил использовать что-то вроде Drools. Создание собственного пользовательского решения было бы излишним, потому что вам пришлось бы отлаживать его и по-прежнему предоставлять функциональные возможности, конечно, меньше, чем тот, который предоставляется механизмом правил, например Drools. Я понимаю, что у Drools есть кривая обучения, но я бы не стал сравнивать ее с созданием пользовательского языка или настраиваемого решения...

На мой взгляд, для того, чтобы пользователь мог писать правила, ему или ей нужно было что-то узнать. Хотя я полагаю, что вы могли бы предоставить язык более простой, чем язык правил слюни, вы никогда не поймете все его/ее потребности. Язык правил Drools достаточно прост для простых правил. Кроме того, вы могли бы предоставить ему хорошо оформленную документацию. Если вы планируете контролировать правила, созданные конечным пользователем и применяемые в системе, возможно, было бы разумнее создать gui, который сформировал бы правила, применяемые к drools.

Надеюсь, я помог!

Ответ 4

Из прошлого опыта решение на основе правил "простого текста" - это ОЧЕНЬ плохая идея, оно оставляет много места для ошибок, также, как только вам нужно добавить несколько простых или сложных правил, это станет кошмаром для кодирования/отладки/поддержки/изменения...

То, что я сделал (и оно работает исключительно хорошо), создает строгие/конкретные классы, которые расширяют абстрактное правило (1 для каждого типа правила). Каждая реализация знает, какую информацию она требует, и как обрабатывать эту информацию, чтобы получить желаемый результат.

В интерфейсе web/front-end вы создадите компонент (для каждой реализации правила), который строго соответствует этому правилу. Затем вы можете указать пользователю, какое правило они хотели бы использовать и соответствующим образом обновить интерфейс (путем перезагрузки страницы /javascript ).

Когда правило получает добавленную/измененную итерацию по всем реализациям правил, чтобы получить соответствующую реализацию, и чтобы эта реализация проанализировала необработанные данные (id, рекомендующие использовать json) из front-end, затем выполните это правило.

public abstract class AbstractRule{
  public boolean canHandle(JSONObject rawRuleData){
    return StringUtils.equals(getClass().getSimpleName(), rawRuleData.getString("ruleClassName"));
  }
  public abstract void parseRawRuleDataIntoThis(JSONObject rawRuleData); //throw some validation exception
  public abstract RuleResult execute();
}
public class InOutPatientRule extends AbstractRule{
  private String patientType;
  private String admissionType;

  public void parseRawRuleDataIntoThis(JSONObject rawRuleData){
    this.patientType = rawRuleData.getString("patientType");
    this.admissionType= rawRuleData.getString("admissionType");
  }
  public RuleResultInOutPatientType execute(){
    if(StringUtils.equals("A",this.patientType) && StringUtils.equals("O",this.admissionType)){
      return //OUTPATIENT
    }
    return //INPATIENT
  }
}

Ответ 5

Вы настраиваете себя на неудачу по двум основным причинам:

  • Разбор текстового сообщения от пользователя - HARD.
  • Написание парсеров в Java несколько громоздко

Решение 1. либо направит вас в нечеткий домен НЛП, для которого вы можете использовать такой инструмент, как OpenNLP или что-то из этой экосистемы. Из-за большого количества тонко различных способов, которыми пользователь может писать вещи, вы найдете свое мышление в сторону более формальной грамматики. Выполнение этой работы закончит вас в решении типа DSL, или вам придется создавать свой собственный язык программирования.

У меня были разумные результаты с использованием комбинаторов парсера Scala для анализа как естественного языка, так и более формализованных грамматик. Проблемы одинаковы, но код, который вы должны написать для их решения, более читабельен.

В нижней строке, даже если вы думаете о очень простом правильном языке, вы обнаружите, что недооцениваете объем сценария, который вы должны тестировать. NeilA имеет право советовать вам уменьшить сложность, создав собственный пользовательский интерфейс для каждого типа правил. Не пытайтесь быть слишком общим, или он взорвется вам в лицо.

Ответ 6

Если вы ищете что-то более легкое, чем drools, но с аналогичной функциональностью, вы можете проверить http://smartparam.org/ проект. Он позволяет хранить параметры в файлах свойств, а также в базе данных.

Ответ 7

Вместо того, чтобы создавать собственный механизм правил, вам может потребоваться использовать механизм Open-NUB CUBE, механизм правил Java с открытым исходным кодом, который использует Groovy как доменный язык (DSL).

Это механизм последовательных правил, а не механизм последовательных правил, такой как механизм правил на основе RETE. Преимущество механизма последовательных правил заключается в том, что очень легко отлаживать правила. Попытка расшифровки выводов из действительно больших наборов правил может быть очень сложной, но с помощью механизма последовательных правил, такого как N-CUBE, отслеживание правил очень похоже на следующую последовательную "логику кода".

N-CUBE имеет встроенную поддержку как таблиц решений, так и деревьев принятия решений. Таблицы решений и деревья в N-CUBE позволяют выполнять данные или код внутри ячеек, очень похожи на многомерный Excel. Язык макросов (DSL) - Groovy. При написании кода внутри ячейки вам не нужно определять оператор пакета, импорт, имя класса или функцию - все это добавлено для вас, что позволяет легко читать/записывать фрагменты кода DSL.

Этот механизм правил доступен в GitHub по адресу https://github.com/jdereg/n-cube.

Ответ 8

Вместо textArea, предоставление - это поле выбора для фиксированного состояния (PATIENT_TYPE) и фиксированных операторов(), и с этим вы будете работать. В любом случае вы контролируете, как выглядит веб-приложение.

Ответ 9

Механизм простого правила может быть построен на замыканиях, то есть в Groovy:

def sendToOutPatient = { ... };

def sendToInPatient = { ... };

def patientRule = { PATIENT_TYPE ->
    {'A': sendToOutPatient,
     'B': sendToInPatient}.get(PATIENT_TYPE)
}

static main(){
    (patientRule('A'))()
}

Вы можете определить свои правила как закрытие, повторное использование/переназначение или даже создание DSL над ними.

И Groovy может быть легко встроен в Java, например:

GroovyShell shell = new GroovyShell(binding);
binding.setVariable("foo", "World");
System.out.println(shell.evaluate("println 'Hello ${foo}!';));

Ответ 10

Это то, что я сделал бы. Я создаю набор переменных регулярного выражения, в зависимости от соответствия, я кодирую бизнес-логику. Если набор правил становится сложным, я бы пошел на реализацию apache commons CommandLineParser на сервере.

Но вы можете использовать GUI/HTML и набор выпадающих списков и подвыборки. Таким образом, вы можете четко задавать запросы к базе данных.

Ответ 11

Поскольку синтаксический анализ только с Java - это самоубийство реализации, вы можете написать простой компилятор, используя Jflex и CUP, которые являются Java версии GNU FLEX и YACC. Таким образом, вы можете создавать простые токены с помощью Jflex (токен - это ключевое слово, например IF, ELSE и т.д.), Тогда как CUP будет использовать эти токены для выполнения некоторого кода.

Ответ 12

Хорошо поговорите с вашими пользователями, спросите их, почему это нужно настраивать и какие изменения в конфигурации они ожидают. Узнайте, какие предстоящие изменения являются определенными, вероятно, удаленными, невероятно маловероятными. И как быстро они должны быть реализованы. Для каждого изменения можно было бы написать небольшой выпуск обновлений, или не?

Учитывая такую ​​гибкость, вы должны оценить возможность опрокидывания своего собственного решения против того, чтобы включить полный движок. "Протестируйте" свое простое решение против предстоящих сценариев изменений, кратко описав, как будут выполняться каждое изменение. Это вполне нормально, если некоторые маловероятные сценарии имеют большую стоимость. Однако, если вероятные сценарии также дорогостоящие, вам лучше выбрать более общее решение.

Что касается вариантов для рассмотрения, мне нравятся как слюни, так и предложение написать свой собственный. Третий вариант: при внедрении пакета финансовой регистрации с ежегодными юридическими обновлениями мы успешно реализовали правила в коде, но оставив их настройки настраиваемыми в таблицах sql. Поэтому в вашем случае это может означать таблицу примерно так:

patient_type | admission_type | inpatient_or_outpatient
-------------------------------------------------------
'A'          | 'O'            | 'Outpatient'
'B'          | NULL           | 'Inpatient'

(Наши таблицы имеют столбцы с датой и датой, которые позволяют пользователю создавать изменения)

Если вы закончите писать DSL, посмотрите http://martinfowler.com/books/dsl.html, в котором содержится подробное описание нескольких подходов. В качестве предостережения: в разделе Q и A Мартин Фаулер пишет:

Значит ли это, что люди-крючки сами пишут сами правила?

В общем, я так не думаю. Это большая работа по созданию среды что позволяет деловым людям писать свои собственные правила. Вы должны сделать удобный инструмент для редактирования, инструменты для отладки, инструменты для тестирования и т.д. Вы получаете большую пользу от бизнеса, стоящего перед DSL, делая достаточно, чтобы чтобы деловые люди могли читать правила. Тогда они могут анализировать их на предмет точности, говорить о них с разработчиками и внести изменения в проекты для правильной реализации разработчиками. Получение DSL бизнес-читаемость намного меньше усилий, чем бизнес, доступный для записи, но дает большую часть преимуществ. Бывают моменты, когда стоит стремление сделать DSL доступными для бизнеса, но это более продвинутая цель.

Ответ 13

Есть механизм правил для Clojure под названием Clara, который может использоваться из java, а также Clojure [Java] Script. Я думаю, что было бы легко создать что-то полезное из этого.

Ответ 14

Реализация механизма правил не является тривиальной. Значимая система, основанная на правилах, имеет механизм вывода, который поддерживает как прямую цепочку, так и обратную цепочку, а также стратегии поиска первого и глубинного первого уровня. В Easy Rules этого нет, он просто выполняет все правила один раз и только один раз. Drools поддерживает прямую и обратную привязку, а афайк также сначала поддерживает глубину и ширину. Он объяснил здесь.

По моему опыту, Drools является единственным значимым Rule Engine для java. У этого есть свои ограничения. Должен сказать, я использовал Drools 5+ лет назад.