编程语言
首页 > 编程语言> > c#-如何定义和执行约束分层实体的复杂规则

c#-如何定义和执行约束分层实体的复杂规则

作者:互联网

如果我有一项政策,并且该政策应包含部分(固定编号).

我的版块是4个预定义的版块:

>工作时间规定.
>借口.
>班次.
>时间表.

每个部分的固定属性与其他部分的属性不同.
如果我可以用类比说明:

>政策—>人体.
>部分—>(手臂,腿,头)
>每个部分都各不相同,例如:(头部包含眼睛,
耳朵等…而不是手臂包含两只手)

例如:

>工作时间规定部分包含名称,工作时间列表.
>借口部分有小时数,原因,借口类型.

Note: based on the domain expert explanation: He want a Save action with each section (as a draft) so that he can update the section
unless the Policy not submitted yet, and a Submit action for the whole
Policy so that after the Policy submission No one can update or delete this
Policy or its sections. (any required update = Define new Policy)

现在,我要设计“策略”,“部分”及其内容.但我被卡住了

首先,我以为我可以将Policy设计为Entity(聚合根),并创建四个类,每个Section一个,并从Section基类(Id,名称)继承它们,而Policy包含Section列表.

其次,我指导我的思想以以下方式概括本节内容:

我将创建:

>接口ISection:SectionType,SectionRule
>每个部门都将实现此接口

然后,我将创建参考表SectionRules:

例如:

rule-key         |default-value|operators|section-type|value-type|rule-type

NumOfhoursInMonth| 5:00        |  =      |  2         | String      |Assignment 
AvailableExcuses |2:00,2:30    |  IN     |  2         | List<String>|Relational 

注意事项:

>部分类型1是借口
>运算符是枚举
>节类型是枚举

当用户启动策略时,我将遍历ref表以表格形式列出规则,以便他可以更改默认值并将其保存在
根据其类型的节如下:

  Id   |Name       |Rule                   |section-type
  1    |Excuses    |NumOfhoursInMonth <= 6 |   2

我现在面临两个问题.

>如果其中一些规则依赖于每个规则,则如何关联这些规则
其他? Ex NumOfExcuses’hoursInMonth应该小于或等于6:00
根据第一个规则,但是如何防止用户
在设置第二条规则时违反了此规则
可用借口IN(5:00,7:00)!现在,我应该防止用户
添加一个大于6的数字,因为第一个规则限制了
第二个 ?第二条规则与第一条规则不一致,因为列表包含(07:00)并且第一条规则指出totalExcuseshoursInMonth< = 06:00小时
>如何使规则更具表现力,以允许条件规则和其他规则
规则?

我的方向正确吗?我可以得到一些建议吗?

解决方法:

我不完全确定哪种设计最合适,您肯定必须经过多次模型迭代才能满意为止,但是我认为问题的核心是假设规则的制定和发现有冲突的规则可以解决.使用Specification Pattern.“规范模式”基本上包括使规则成为模型的一等公民,而不是仅通过条件语言构造来表达规则.

有很多方法可以实现模式,但是这里有一个例子:

Specification Pattern

在我设计的其中一个系统中1,我设法重用了同一套规范,以强制执行命令和授权规则.查询并执行&描述业务规则.

例如,您可以在规范上添加一个describe():字符串方法来描述其约束,或者可以将toSql(string mainPolicyTableAlias)方法转换为SQL.

例如(伪代码)

someSpec = new SomeRule(...).and(new SomeOtherRule(...));
unsatisfiedSpec = someSpec.remainderUnsatisfiedBy(someCandidate);
errorMessage = unsatisfiedSpec.describe();

但是,直接在规范上实施此类操作可能会污染各种应用程序/基础架构.为了避免这种污染,您可以使用Visitor Pattern,它可以使您在正确的层中对各种操作进行建模.但是,这种方法的缺点是,每次添加新类型的具体规范时,您都必须更改所有访问者.

Visitor pattern

#1为此,我必须实现上述论文中描述的其他规范操作,例如restderUnsatisfiedBy等.

自从我用C#编程以来已经有一段时间了,但是我认为C#中的expression trees可以非常方便地实现规范并将其转换为多种表示形式.

validate the correlation between different rules in every section of the policy

我不能完全确定您的想法,但是通过在您的规范中添加诸如conflictsWith(Spec other):bool之类的操作,您可以实现一种冲突检测算法,该算法可以告诉您一个或多个规则是否存在冲突.

例如,在下面的示例中,这两个规则会冲突,因为这两个规则永远不可能是真实的(伪代码):

rule1 = new AttributeEquals('someAttribute', 'some value');
rule2 = new AttributeEquals('someAttribute', 'some other value');
rule1.conflictsWith(rule2); //true

总之,您的整个模型肯定会比这复杂得多,并且您将必须找到描述规则并将它们与正确的组件相关联的正确方法.您甚至可能希望将某些规则与适用性规范链接起来,以便仅在满足某些特定条件的情况下才适用它们,并且您可能有许多不同的规范候选类型,例如Policy,Section或SectionAttribute,因为某些规则可能需要应用于整体给定特定部分的属性,必须解释策略以及其他类型的规则.

希望我的回答会引发一些想法,使您走上正确的道路.我还建议您看一下现有的验证框架和规则引擎以获取更多想法.另请注意,如果您希望所有规则和政策的状态始终保持一致,那么您很有可能会将政策设计为由所有部分和所有部分组成的大型汇总.规则.如果由于性能原因或并发冲突(例如许多用户编辑同一策略的不同部分)而以某种方式无法实现或不合需要(例如,许多用户编辑同一策略的不同部分),那么也许您将被迫分解大型聚合并使用最终的一致性.

当然,您还必须考虑当新规则使现有状态无效时需要执行的操作.也许您会想强制执行规则和可以同时更改状态,也可以实施状态验证指标来将当前状态的某些部分标记为无效等.

1-Could You explain more about describe(),toSql(string mainPolicyTableAlias),I didn’t understand the intent behind these functions.

好吧,describe将对规则进行描述.如果您需要i18​​n支持
或更多地控制消息,您可能想要使用访问者来代替,也许您还想要一个功能,可以在其中使用模板消息来覆盖自动描述,等等.toSql方法是相同的,但是生成可以使用的内容例如在WHERE条件中.

new Required().describe() //required
new NumericRange(']0-9]').if(NotNullOrEmpty()).describe() //when provided, must be in ]0-9] range

This’s a considerable drawback ! Could I ask how to overcome this problem.

直接在对象上支持行为可以轻松添加新对象,但是在使用访问者模式时很难添加新行为,因此可以轻松添加新行为,但是很难添加新类型.那就是著名的Expression Problem.

如果您可以找到不太可能针对所有特定类型更改的通用抽象表示形式,则可以缓解该问题.例如,如果要绘制许多类型的多边形,例如“三角形”,“正方形”等,则最终可以将它们全部表示为一系列有序点.可以肯定地将规格分解为Expression(explored here),但这并不能神奇地解决所有翻译问题.

这是JavaScript&中的示例实现HTML.请注意,某些规范的实现非常幼稚,无法与undefined / blank / null值配合使用,但是您应该明白这一点.

class AttrRule {
  isSatisfiedBy(value) { return true; }
  and(otherRule) { return new AndAttrRule(this, otherRule); }
  or(otherRule) { return new OrAttrRule(this, otherRule); }
  not() { return new NotAttrRule(this); }
  describe() { return ''; }
}

class BinaryCompositeAttrRule extends AttrRule {
  constructor(leftRule, rightRule) {
    super();
    this.leftRule = leftRule;
    this.rightRule = rightRule;
  }
  
  isSatisfiedBy(value) {
    const leftSatisfied = this.leftRule.isSatisfiedBy(value);
    const rightSatisfied = this.rightRule.isSatisfiedBy(value);
    return this._combineSatisfactions(leftSatisfied, rightSatisfied);
  }
  
  describe() {
    const leftDesc = this.leftRule.describe();
    const rightDesc = this.rightRule.describe();
    return `(${leftDesc}) ${this._descCombinationOperator()} (${rightDesc})`;
  }
}

class AndAttrRule extends BinaryCompositeAttrRule {
  _combineSatisfactions(leftSatisfied, rightSatisfied) { return !!(leftSatisfied && rightSatisfied); }
  _descCombinationOperator() { return 'and'; }
}

class OrAttrRule extends BinaryCompositeAttrRule {
  _combineSatisfactions(leftSatisfied, rightSatisfied) { return !!(leftSatisfied || rightSatisfied); }
  _descCombinationOperator() { return 'or'; }
}

class NotAttrRule extends AttrRule {
  constructor(innerRule) {
    super();
    this.innerRule = innerRule;
  }
  isSatisfiedBy(value) {
    return !this.innerRule;
  }
  describe() { return 'not (${this.innerRule.describe()})'}
}

class ValueInAttrRule extends AttrRule {
  constructor(values) {
    super();
    this.values = values;
  }
  
  isSatisfiedBy(value) {
    return ~this.values.indexOf(value);
  }
  
  describe() { return `must be in ${JSON.stringify(this.values)}`; }
}

class CompareAttrRule extends AttrRule {
  constructor(operator, value) {
    super();
    this.value = value;
    this.operator = operator;
  }
  
  isSatisfiedBy(value) {
    //Unsafe implementation
    return eval(`value ${this.operator} this.value`);
  }
  
  describe() { return `must be ${this.operator} ${this.value}`; }
}

const rules = {
  numOfHoursInMonth: new CompareAttrRule('<=', 6),
  excuseType: new ValueInAttrRule(['some_excuse_type', 'some_other_excuse_type']),
  otherForFun: new CompareAttrRule('>=', 0).and(new CompareAttrRule('<=', 5))
};

displayRules();
initFormValidation();

function displayRules() {
  const frag = document.createDocumentFragment();
  Object.keys(rules).forEach(k => {
    const ruleEl = frag.appendChild(document.createElement('li'));
    ruleEl.innerHTML = `${k}: ${rules[k].describe()}`;
  });
  document.getElementById('rules').appendChild(frag);
}

function initFormValidation() {
  const form = document.querySelector('form');
  form.addEventListener('submit', e => {
    e.preventDefault();
  });
  form.addEventListener('input', e => {
    validateInput(e.target);
  });
  Array.from(form.querySelectorAll('input')).forEach(validateInput);
}

function validateInput(input) {
    const rule = rules[input.name];
    const satisfied = rule.isSatisfiedBy(input.value);
    const errorMsg = satisfied? '' : rule.describe();
    input.setCustomValidity(errorMsg);
}
form > label {
  display: block;
  margin-bottom: 5px;
}

input:invalid {
  color: red;
}
<h3>Rules:</h3>
<ul id="rules"></ul>

<form>
  <label>numOfHoursInMonth: <input name="numOfHoursInMonth" type="number" value="0"></label>
  <label>excuseType: <input name="excuseType" type="text" value="some_excuse_type"></label>
  <label>otherForFun: <input name="otherForFun" type="number" value="-1"></label>
</form>

标签:validation,domain-driven-design,rule-engine,c,design-patterns
来源: https://codeday.me/bug/20191024/1923570.html