2

假设我有一个应用程序,让用户创建要应用于域实体的业务规则。规则可以是条件和多个操作的组合,如果条件评估为真,则执行相应的操作。此规则由用户以自由格式的文本格式创建,然后将其转换为规则引擎可以理解和执行的专有格式。

例如,对于员工管理系统,如果有业务规则来检查员工是否在当前角色工作超过一年并且表现好于预期,则可以晋升为下一个角色,加薪 10%。此业务规则可由用户输入,如下所示。

条件:Employee.CurrentRoleLength > 1 && Employee.ExceededExpectations()
操作:Employee.PromoteToNextRole() | Employee.GiveSalaryIncrement(10)

请注意,多个操作用|分隔。. 同样为了执行这个规则,应用程序使用一个单独的规则引擎类库来解析这个条件和两个动作到一个专有的格式,比如说,ExecutableScript也在规则引擎类库中定义。

现在为了使用 DDD 对这个需求进行建模;我想出了以下域对象。

规则(实体)
条件(值对象)
操作(值对象)

其中 Rule 是一个实体,它包含一个条件值对象和一个操作值对象列表,如下所示。

public class Rule : Entity
{
    public Condition Condition { get; private set; }
    public IList<Action> Actions { get; private set;}

    public Rule(Condition condition, IList<Action> actions)
    {
        Condition = condition;
        Actions = actions;
    }
}

public sealed class Condition : ValueObject<Condition>
{
    public string ConditionText { get; private set;}
    public ExecutableScript ExecutableCondition{ get; private set;}

    public Condition(string conditionText)
    {
        ConditionText = conditionText;            
    }     

    public Parse()
    {
        ExecutableCondition = // How to parse using external rule engine ??;            
    }

    public Execute()
    {
        // How to execute using external rule engine ??;            
    }
}      

public sealed class Action : ValueObject<Action>
{
    public string ActionText{ get; private set;}
    public ExecutableScript ExecutableAction{ get; private set;}

    public Action(string actionText)
    {
        ActionText = actionText;            
    }

    public Parse()
    {
        ExecutableAction = // How to parse using external rule engine ??;            
    }

    public Execute()
    {
        // How to execute using external rule engine ??;            
    }
}

基于上述领域模型,我有以下问题。

  1. 如何在不依赖外部规则引擎的情况下解析和执行条件和操作。我理解域层不应该对外层有任何依赖,应该局限于它自己的。

  2. 即使我在它们的域对象之外解析条件和操作,它们解析的 ExceutableScript 值仍然需要存在于它们中,这仍然需要依赖于外部规则引擎。

  3. 是否只是 DDD 不是这种情况的正确方法,我走错了方向。

对不起,很长的帖子。任何帮助将不胜感激。

谢谢。

4

2 回答 2

2

技术领域可能会从 DDD 战术模式中受益,但创建正确抽象的成本通常高于其他领域,因为它通常需要抽象出复杂的数据结构。

开始考虑所需抽象的一个好方法是问问自己,如果要交换底层技术,需要哪些抽象。

在这里,您有一个复杂的基于文本的表达式,ExecutableScript规则引擎根据该表达式创建一个。

如果您考虑一下,这里有三个主要元素:

  1. 专有的基于文本的表达式语法。
  2. ExecutableScript哪个是专有的;我假设这是一个带有嵌入式解释器的抽象语法树(AST)。
  3. 可能是专有的规则评估上下文。

如果你要交换底层技术来执行规则,那么另一个规则引擎的表达式语法可能会有所不同,它肯定会有完全不同的规则解释机制。

至此,我们已经确定了必须抽象的内容,但还没有确定正确的抽象内容。

您可以决定实现您自己的表达式语法、您自己的解析器、您自己的 AST,这将是内存中表达式的基于树的表示,最后是您自己的规则评估上下文。然后,这组抽象将被特定的规则引擎使用。例如,您当前的规则引擎必须将domain.ExpressionAST 转换为ExecutableScript.

像这样的东西(我故意省略了评估上下文,因为你没有提供任何信息)。

在此处输入图像描述

但是,创建抽象集的成本可能很高,尤其是在您不希望交换规则引擎的情况下。如果您当前规则引擎的语法适合您的需要,那么您可以将其用作基于文本的表达式的抽象。您可以这样做,因为它不需要专有数据结构来表示内存中的文本;它只是一个String. 如果您将来要交换规则引擎,那么您仍然可以使用旧引擎来解析表达式,然后依靠生成的 AST 为另一个规则引擎生成新引擎,或者您可以回去编写自己的抽象.

此时,您可能决定简单地将该表达式保存String在您的域中,并将其传递Executor给必须对其进行评估的时间。如果您担心每次重新生成的性能成本,ExecutableScript那么您应该首先确保这确实是一个问题;过早的优化是不可取的。

如果你发现它的开销太大,那么你可以在基础设施执行器中实现记忆ExecutableScript可以存储在内存中或持久化到磁盘。您可能会使用基于字符串的表达式的哈希来识别它(注意冲突)、整个字符串、域分配的 id 或任何其他策略。

最后但并非最不重要的。请记住,如果聚合不处理规则操作,或者如果规则谓词跨越多个聚合,则用于评估表达式的数据可能已经过时。我不会对此进行扩展,因为我不知道您计划如何生成规则评估上下文和流程操作,但我认为它仍然值得一提,因为不变实施是每个领域的重要方面。

如果您确定所有规则最终可能是一致的,或者对陈旧数据做出的决定是可以接受的,那么我也会考虑为此创建一个完全独立的有界上下文,可能称为“规则管理和执行”。

编辑:

下面是一个示例,它显示了从应用程序服务的角度来看创建规则的样子,假设表达式String在域中存储为 s。

//Domain
public interface RuleValidator {
    boolean isValid(Rule rule);
}

public class RuleFactory {
    private RuleValidator validator;

    //...

    public Rule create(RuleId id, Condition condition, List<Action> actions) {
        Rule rule = new Rule(id, condition, actions);

        if (!validator.isValid(rule)) {
            throw new InvalidRuleException();
        }

        return rule;
    }
}

//App
public class RuleApplicationService {
    private RuleFactory ruleFactory;
    private RuleRepository ruleRepository;

    //...
    public void createRule(String id, String conditionExpression, List<String> actionExpressions) {
        transaction {
            List<Action> actions = createActionsFromExpressions(actionExpressions);

            Rule rule = ruleFactory.create(new RuleId(id), new Condition(conditionExpression), actions);


            ruleRepository.add(rule); //this may also create and persist an `ExecutableScript` object transparently in the infrastructure, associated with the rule id.
        }
    }
}
于 2016-06-24T21:08:14.537 回答
1

如何在不依赖外部规则引擎的情况下解析和执行条件和操作。我理解域层不应该对外层有任何依赖,应该局限于它自己的。

这部分很简单:依赖倒置。域定义了一个服务提供者接口,描述了它想如何与一些外部服务对话。通常,域会将其某些内部状态的副本传递给服务,并返回一个答案,然后它可以将其应用于自身。

所以你可能会在你的模型中看到这样的东西

Supervisor.reviewSubordinates(EvaluationService es) {
    for ( Employee e : this.subbordinates ) {
        // Note: state is an immutable value type; you can't
        // change the employee entity by mutating the state.
        Employee.State currentState = e.currentState;


        Actions<Employee.State> actions = es.evaluate(currentState);            
        for (Action<Employee.State> a : actions ) {
            currentState = a.apply(currentState);
        }

        // replacing the state of the entity does change the
        // entity, but notice that the model didn't delegate that.
        e.currentState = currentState;
    }
}
于 2016-06-24T04:21:06.767 回答