在现代软件开发中,业务逻辑的复杂性不断增加,尤其是在金融、贸易等领域。为了应对这种复杂性,需要一种灵活且高效的机制来管理和执行业务规则。本文将介绍一种业务规则引擎的设计和实现,它能够处理复杂的业务逻辑,并在对象属性变更时触发相应的规则。
在实际的业务场景中,例如贸易处理系统,需要处理非常复杂的级联规则。这些规则通常涉及到对象图的多个属性,当一个属性发生变化时,可能会触发整个对象图中其他属性的级联变化。传统的处理方式,如在setter中直接触发业务规则,或者将业务规则编码到setter中,虽然看似简单,但长期维护起来却非常困难。
主要问题包括:
解决方案的灵感来自于ORM框架,它们需要拦截属性变化以生成SQL更新命令。在这种情况下,创建了一个透明的代理对象,它拦截变化并触发所需的规则。
以下是一些示例代码:
public class Abcd : IAbcd
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D { get; set; }
}
public class AbcdRules : MappingRules<IAbcd>
{
public AbcdRules()
{
Set(x => x.B)
.With(x => x.A)
.If(x => x.A < 100)
.OnChanged(x => x.A);
Set(x => x.C)
.With(x => x.B)
.If(x => x.C < 100)
.OnChanged(x => x.B);
Set(x => x.D)
.With(x => x.C)
.If(x => x.D < 100)
.OnChanged(x => x.C);
Set(x => x.A)
.With(x => x.D + 1)
.If(x => x.A < 100)
.OnChanged(x => x.D);
}
}
要使用规则引擎,需要实例化一个“facade”。它接受一个对象实例和规则实例作为参数:
var instance = new Abcd();
var rules = new AbcdRules();
var abcd = new InterfaceWrapper<IAbcd>(instance, rules);
像操作业务对象一样设置facade的值:
abcd.Target.A = 1;
检查对象的状态:
Assert.AreEqual(100, instance.A);
要创建一个对象的类型代理,所有公共属性都需要是虚拟的(显式声明为virtual或从接口继承),并且类不应该被封闭。如果这些条件无法满足,可以使用动态代理作为替代方案。
以下是来自真实交易系统的一个示例。这次,创建了一个包含两个节点trade -> product的对象图的facade。设置一个对象的属性可能会改变另一个属性。
var trade = new CdsTrade
{
Product = new CreditDefaultSwap()
};
var rules = new CdsRules();
dynamic p = new DynamicWrapper<CdsTrade>(trade, rules);
p.CdsProduct.RefEntity = "AXA";
p.Counterparty = "CHASEOTC";
Assert.AreEqual("ICEURO", trade.ClearingHouse);
Assert.AreEqual("MMR", trade.CdsProduct.Restructuring);
Assert.AreEqual("SNR", trade.CdsProduct.Seniority);
只有当属性的值发生变化时,规则才会被触发。对于值类型,为了区分一个属性是否具有其默认值,使用可空类型。
Set(v => v.DealWay)
.With(v => "B")
.If(v => v.BookOcCode == 9826 && v.Folder == "HEDGE_OA_HY")
.OnChanged(v => v.BookOcCode)
.Or(v => v.Folder)
.Or(v => v.DealWay)
.EndRule();