Asp.net 面向接口可扩展框架之业务规则引擎扩展组件
随着面向接口可扩展框架的继续开发,有些功能开发出现了"瓶颈",有太多的东西要写死才好做。但写死的代码扩展性是非常的不好,迷茫中寻找出入...
进而想到我以前开发的好几个项目,都已有一定的可配置能力,想想怎么把这些地方的代码抽象提取出来。进而想到"业务规则引擎",网上找了几个都不太入"眼",就抽时间再造个"轮子"
业务规则引擎在很多成熟的工作流引擎中都有相应的模块,是工作流的核心之一。但是除了工作流很多业务都需要业务规则引擎,所以它非常有必要独立作为一个模块。
在现实中很多项目开发都需要一些定制化的分支需求,为了这些需求把项目搞的鸡飞狗跳。使用业务规则引擎来做逻辑分支路由、参数“矫正”、拦截等,说是如虎添翼应该不为过。
这里使用社区发文章来做个例子
1、先看文章相关模型

A:ArticleRepository是文章数据仓储,实际是把文章存放在内存的List中
B:User是文章作者,Article就是文章了,ArticlePublish是DTO,包含文章和作者的信息
2、文章有不同的分类
ArticleRepository TopArticles = new ArticleRepository { Name = "置顶" };
ArticleRepository PerfectArticles = new ArticleRepository { Name = "精华" };
ArticleRepository NetArticles = new ArticleRepository { Name = ".Net" };
ArticleRepository OtherArticles = new ArticleRepository { Name = "其他" };
A:其中分为.net文章和其他文章,另外还有“置顶”和“精华”两个推送分类
B:文章现在分为.net文章和其他文章两个分类,以后很难说不用增加Java、PHP等其他分类,所以这个地方需要可扩展
C:不是每个人发的文章都置顶和精华,要不就没法愉快的玩耍了
3、可以按授权进行推送
先看授权相关建模

为了简单明了,这里都是用内存对象来模拟存储
A:Role是角色,角色有个权重的字段(Sort),Sort值越大权限越大
(有人说角色不应该是平等的吗?通过绑定资源来控制权限?我想问国家主席和你们村主任能平等吗?角色特权和资源绑定等手段应该综合来用。再者我们是演示业务规则引擎的,不是专门讨论权限系统的,先打住)
B:RolePermission用于授权(Grant)和获取权限(判断权限),维护者一个用户和权限的关联关系
public class RolePermission : IEntityAccess<User, Role>, IComparer<Role>
{
private Dictionary<User, Role> _permission = new Dictionary<User, Role>();
public bool Grant(User user, Role role)
{
Role role0 = Get(user);
if (role0 != null && role0.Sort >= role.Sort)
return true;
_permission[user] = role;
return true;
}
public Role Get(User user)
{
Role role = null;
_permission.TryGetValue(user, out role);
return role;
}
int IComparer<Role>.Compare(Role x, Role y)
{
return Comparer<Role>.Default.Compare(x, y);
}
}
RolePermission
4、继续场景设置
Role manager = new Role { Id = , Name = "管理员", Sort = };
Role expert = new Role { Id = , Name = "专家", Sort = };
User user1 = new User { Id = , Name = "张三", Year = };
User user2 = new User { Id = , Name = "李四", Year = };
User user3 = new User { Id = , Name = "王二", Year = };
5、现在可以开始发文章了
RolePermission permission = new RolePermission();
ConfigRole(permission);
ArticlePublish post1 = new ArticlePublish(user1, new Article { Content = ".Net" });
ArticlePublish post2 = new ArticlePublish(user2, new Article { Content = "Java" });
ArticlePublish post3 = new ArticlePublish(user3, new Article { Content = "Php" });
Engine<ArticlePublish, int> engine = new Engine<ArticlePublish, int>();
ConfigCategory(engine);
Post(engine, post1, post2, post3);
Show(TopArticles, PerfectArticles, NetArticles, OtherArticles);
private void ConfigRole(RolePermission permission)
{
permission.Grant(user3, expert);
}
授权代码
private static void Post(Engine<ArticlePublish, int> engine, params ArticlePublish[] articles)
{
foreach (var item in articles)
{
int id = ;
if (engine.Run(item, ref id))
Console.WriteLine(string.Concat("文章处理成功,Id=", id.ToString()));
}
}
发表文章代码
private static void Show(params ArticleRepository[] repositorys)
{
foreach (var repository in repositorys)
{
Console.WriteLine(new string('-', ));
List<Article> list = repository.ListAll();
if (list.Count < )
{
Console.WriteLine(string.Concat(repository.Name, " 无"));
continue;
}
Console.WriteLine(repository.Name);
foreach (var item in list)
{
Console.WriteLine(string.Concat("Article{Id=", item.Id, ",Content=", item.Content, "}"));
}
Console.WriteLine(new string('-', ));
}
}
显示所有文章代码
文章处理成功,Id=1
文章处理成功,Id=2
文章处理成功,Id=3
-------------------------------------------------------------------------------- 置顶 无
-------------------------------------------------------------------------------- 精华 无
-------------------------------------------------------------------------------- .Net
Article{Id=1,Content=.Net}
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- 其他
Article{Id=2,Content=Java}
Article{Id=3,Content=Php}
--------------------------------------------------------------------------------
三篇文章发表成功,一篇.net,两篇其他,效果非常不错
先等等,Engine<ArticlePublish, int>是什么鬼,要发文章不应该是ArticleRepository吗?
Engine就是大名鼎鼎的业务规则引擎了,ArticleRepository发表文章不假,但是都是由Engine决定发不发,用谁发,这些就是业务规则,
(ArticleRepository就是只负责存储和读取,职责非常单一)
6、把业务规则定义看一下
private void ConfigCategory(Engine<ArticlePublish, int> engine)
{
engine.When(post => post.Article.Content.Contains(".Net")).Then(post => NetArticles.Add(post.Article));
engine.Then(post => OtherArticles.Add(post.Article));
}
非常简单,如果文章包含.net关键字,使用NetArticles存储,否则使用OtherArticles存储(分表就是这么简单!!!)
7、继续推送的例子
Engine<ArticlePublish, int> pushEngine = new Engine<ArticlePublish, int>();
ConfigPush(permission, pushEngine);
ConfigYear(pushEngine);
Post(pushEngine, post1, post2, post3);
Show(TopArticles, PerfectArticles, NetArticles, OtherArticles);
文章处理成功,Id=2
文章处理成功,Id=3
-------------------------------------------------------------------------------- 置顶
Article{Id=3,Content=Php}
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- 精华
Article{Id=2,Content=Java}
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- .Net
Article{Id=1,Content=.Net}
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- 其他
Article{Id=2,Content=Java}
Article{Id=3,Content=Php}
--------------------------------------------------------------------------------
这次在置顶和精华都各有一篇了
8、我们看一下推送规则是怎么定义的
private void ConfigPush(RolePermission permission, Engine<ArticlePublish, int> engine)
{
int topNum = ;
int topLimit = ;
engine.When(post => topNum < topLimit && Comparer<Role>.Default.Compare(permission.Get(post.User), expert) >= ).Then(post => { topNum++; return TopArticles.Add(post.Article); });
engine.When(post => Comparer<Role>.Default.Compare(permission.Get(post.User), expert) >= ).Then(post => PerfectArticles.Add(post.Article));
}
private void ConfigYear(Engine<ArticlePublish, int> engine)
{
engine.When(post => post.User.Year >= ).Then(post => PerfectArticles.Add(post.Article));
}
解读一下
A:if 专家及以上权限且可以发置顶,推送到置顶(先来先得)
B:else if 专家发的文章推送到精华
C:else if 8年以上会员发的文章推送到精华
D:else 什么都不做
注:先不要和我掰扯以上业务规则的合理性,只是个测试例子而已
就是这么简单,老板再也不用担心我不会写业务规则了
9、业务规则引擎(Engine<TArg, TResult>)主要源码解析
public class Engine<TArg, TResult> : ArgInstance<TArg, TResult>, IDefinition<TArg, TResult>
{
private List<IDefinition<TArg, TResult>> _definitions = new List<IDefinition<TArg, TResult>>();
#region IDefinition<TArg, TResult>
/// <summary>
/// 条件
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
public IDefinition<TArg, TResult> When(IVerifyRule<TArg> condition)
{
if (condition == null)
return this;
Definition<TArg, TResult> definition = new Definition<TArg, TResult> { Rule = condition };
_definitions.Add(definition);
return definition;
}
/// <summary>
/// 执行
/// </summary>
/// <param name="action"></param>
public void Then(IArgInstance<TArg, TResult> action)
{
Instance = action;
}
#region IVerifyRule<TArg>
/// <summary>
///
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public bool Check(ref TArg entity)
{
return true;
}
#endregion
#endregion
/// <summary>
/// 执行
/// </summary>
/// <param name="arg"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool Run(TArg arg, ref TResult result)
{
foreach (var definition in _definitions)
{
if (definition.Check(ref arg))
return Operate.Run<TArg, TResult>(definition, arg, ref result);
}
return Operate.Run<TArg, TResult>(Instance, arg, ref result);
}
}
Engine
A:规则引擎逻辑很简单,就是When、Then和Run
B:另外包含一个IDefinition列表,IDefinition是规则定义;Engine本身也是一个IDefinition,其一是为了实现链式语法,使得规定定义非常优美,其二可以实现Engine的分支嵌套
C:Engine的Run就是规则适配,匹配上哪条规则(If/ElseIf)就执行哪条规则的Then,都匹配不上就执行自己的Then(也就是Else)
10、使用Engine的分支嵌套,优化一下推送的例子
private void ConfigPush2(RolePermission permission, Engine<ArticlePublish, int> engine)
{
int topNum = ;
int topLimit = ;
Engine<ArticlePublish, int> perfectEngine = new Engine<ArticlePublish, int>(post => Comparer<Role>.Default.Compare(permission.Get(post.User), expert) >= );
perfectEngine.When(post => topNum < topLimit).Then(post => { topNum++; return TopArticles.Add(post.Article); });
perfectEngine.Then(post => PerfectArticles.Add(post.Article));
engine.Add(perfectEngine);
}
解读一下:
A:先定义一个专家(分支)规则引擎(perfectEngine) 条件是if 专家
B:在专家规则引擎中增加置顶逻辑 if 没有置顶 设置置顶(先到先得)
C:else 推送到精华
D:把专家规则引擎(perfectEngine)添加到规则引擎(engine)的分支中
E:以上看上去逻辑更复杂了一点。但代码上没有重复出现专家判断逻辑,实际执行也没有,所以性能会更好,基本逻辑如下
if 专家
if 没有置顶 推送到置顶
else 推送到精华
以上就是演示复杂逻辑分支的例子
11、为了更好了解Engine,再看一下规则定义(IDefinition<TArg, TResult>)
IDefinition接口又继承了IVerifyRule和IArgInstance接口也一起看一下
/// <summary>
/// 逻辑分支定义
/// </summary>
public interface IDefinition<TArg, TResult> : IVerifyRule<TArg>, IArgInstance<TArg, TResult>
{
/// <summary>
///
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
IDefinition<TArg, TResult> When(IVerifyRule<TArg> condition);
/// <summary>
///
/// </summary>
/// <param name="action"></param>
void Then(IArgInstance<TArg, TResult> action);
}
IDefinition
/// <summary>
/// 验证规则
/// </summary>
public interface IVerifyRule<TEntity>
{
/// <summary>
///
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
bool Check(ref TEntity entity);
}
IVerifyRule
/// <summary>
/// 参数化工作单元
/// </summary>
/// <typeparam name="TArg">参数类型</typeparam>
/// <typeparam name="TResult">结果类型</typeparam>
public interface IArgInstance<in TArg, TResult>
{
/// <summary>
/// 执行操作
/// </summary>
/// <param name="arg"></param>
/// <param name="result"></param>
/// <returns></returns>
bool Run(TArg arg, ref TResult result);
/// <summary>
/// 成功回调
/// </summary>
/// <param name="arg"></param>
void OnSuccess(TArg arg);
/// <summary>
/// 失败回调
/// </summary>
/// <param name="arg"></param>
void OnFail(TArg arg);
/// <summary>
/// 异常回调
/// </summary>
/// <param name="arg"></param>
/// <param name="ex"></param>
void OnException(TArg arg, Exception ex);
}
IArgInstance
A:IVerifyRule是判断逻辑接口,根据参数得到True/False,非常简单
B:IArgInstance是执行接口,按一个参数得到一个结果,并定义了三个“事件”,成功回调、失败回调、异常回调
C:IDefinition就简单多了,只是把判断逻辑IVerifyRule和执行对象使用When和Then组合起来
但是这里有一个问题,上面的例子是使用Linq实现的,看上去很高大上,Engine没看到对Linq的支持,其实以上Linq表达式就是生成委托
规则引擎都是面向IDefinition、IVerifyRule和IArgInstance,和委托也没什么关系啊,怎么回事
12、这个简单,使用静态方法扩展就可以得到的
/// <summary>
/// 规则定义(IDefinition)扩展(链式语法)
/// </summary>
public static class ExtensionDefinition
{
/// <summary>
///
/// </summary>
/// <typeparam name="TArg"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="definition"></param>
/// <param name="condition"></param>
/// <returns></returns>
public static IDefinition<TArg, TResult> When<TArg, TResult>(this IDefinition<TArg, TResult> definition, Func<TArg, bool> condition)
{
if (condition == null)
return definition;
IVerifyRule<TArg> rule = new FuncRule<TArg>(condition);
return definition.When(rule);
}
/// <summary>
///
/// </summary>
/// <typeparam name="TArg"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="definition"></param>
/// <param name="action"></param>
public static void Then<TArg, TResult>(this IDefinition<TArg, TResult> definition, Func<TArg, TResult> action)
{
if (action == null)
return;
IArgInstance<TArg, TResult> instance = new FuncInstance<TArg, TResult>(action);
definition.Then(instance);
}
}
ExtensionDefinition
就是把委托转化为IVerifyRule和IArgInstance对象了,也是非常简单吧
我这个规则引擎简洁明了,很多爱学习的同学就是“爱造轮子“,现在把核心源码都公布了,大家也都可以定制自己的业务规则引擎了,感兴趣的同学马上动手吧
以上都是使用Fluent代码来做业务规则配置的,以后我还需要做使用文件配置做动态业务规则的例子,以便在容器配置文件中使用
Asp.net 面向接口可扩展框架之业务规则引擎扩展组件的更多相关文章
- Asp.net 面向接口可扩展框架之使用“类型转化基础服务”测试四种Mapper(AutoMapper、EmitMapper、NLiteMapper及TinyMapper)
Asp.net 面向接口可扩展框架的“类型转化基础服务”是我认为除了“核心容器”之外最为重要的组成部分 但是前面博文一出,争议很多,为此我再写一篇类型转化基础服务和各种Mapper结合的例子,顺便对各 ...
- Asp.net 面向接口可扩展框架之“Mvc扩展框架及DI”
标题“Mvc扩展框架及DI”有点绕口,我也想不出好的命名,因为这个内容很杂,涉及多个模块,但在日常开发又密不可分 首先说Mvc扩展框架,该Mvc扩展就是把以前的那个Mvc分区扩展框架迁移过来,并优化整 ...
- Asp.net 面向接口可扩展框架之核心容器(含测试代码下载)
新框架的容器部分终于调通了!容器实在太重要了,所以有用了一个名词叫“核心容器”. 容器为什么那么重要呢?这个有必要好好说道说道. 1.首先我们从框架名称面向接口编程说起,什么是面向接口编程?(这个度娘 ...
- Asp.net 面向接口可扩展框架之应用程序上下文作用域组件
在团队中推广面向接口开发两年左右,成果总体来说我还是挺满意的,使用面向接口开发的模块使用Unity容器配置的功能非常稳定,便于共享迁移(另一个项目使用只需要复制配置和调用接口即可),如果再配合上DI那 ...
- Asp.net 面向接口可扩展框架之数据处理模块及EntityFramework扩展和Dapper扩展(含干货)
接口数据处理模块是什么意思呢?实际上很简单,就是使用面向接口的思想和方式来做数据处理. 还提到EntityFramework和Dapper,EntityFramework和Dapper是.net环境下 ...
- Asp.net 面向接口可扩展框架之消息队列组件
消息队列对大多数人应该比较陌生.但是要提到MQ听说过的人会多很多.MQ就是英文单词"Message queue"的缩写,翻译成中文就是消息队列(我英语差,翻译错了请告知). PS: ...
- Asp.net 面向接口可扩展框架之类型转化基础服务
新框架正在逐步完善,可喜可贺的是基础服务部分初具模样了,给大家分享一下 由于基础服务涉及面太广,也没开发完,这篇只介绍其中的类型转化部分,命名为类型转化基础服务,其实就是基础服务模块的类型转化子模块 ...
- 面向接口可扩展框架之“Mvc扩展框架及DI”
面向接口可扩展框架之“Mvc扩展框架及DI” 标题“Mvc扩展框架及DI”有点绕口,我也想不出好的命名,因为这个内容很杂,涉及多个模块,但在日常开发又密不可分 首先说Mvc扩展框架,该Mvc扩展就是把 ...
- net 面向接口框架
Asp.net 面向接口框架之应用程序上下文作用域组件 在团队中推广面向接口开发两年左右,成果总体来说我还是挺满意的,使用面向接口开发的模块使用Unity容器配置的功能非常稳定,便于共享迁移(另一个项 ...
随机推荐
- 个人网站对xss跨站脚本攻击(重点是富文本编辑器情况)和sql注入攻击的防范
昨天本博客受到了xss跨站脚本注入攻击,3分钟攻陷--其实攻击者进攻的手法很简单,没啥技术含量.只能感叹自己之前竟然完全没防范. 这是数据库里留下的一些记录.最后那人弄了一个无限循环弹出框的脚本,估计 ...
- 不懂CSS的后端难道就不是好程序猿?
由于H5在移动端的发展如日中天,现在大部分公司对高级前端需求也是到处挖墙角,前端薪资也随之水涨船高,那公司没有配备专用的前端怎么办呢? 作为老板眼中的“程序猿” 前端都不会是非常无能的表现,那作为后端 ...
- java常用的设计模式
设计模式:一个程序员对设计模式的理解:"不懂"为什么要把很简单的东西搞得那么复杂.后来随着软件开发经验的增加才开始明白我所看到的"复杂"恰恰就是设计模式的精髓所 ...
- iOS之开发中常用的颜色及其对应的RGB值
R G B 值 R G B 值 R G B 值 黑色 0 0 0 #000000 黄色 255 255 0 #FFFF00 浅灰蓝色 176 224 230 #B0E0E6 象牙黑 41 ...
- SVN版本冲突,导致出现Files 的值“ < < < < < < < .mine”无效
只要根据错误提示,找到相应文件夹下的\obj\Debug文件夹下的 相应名字.csproj.FileListAbsolute.txt, 打开并删除含有'<<<<<< ...
- 在VMware上安装CentOS -7
1.下载好VMware 2.准备好CentOS的镜像文件 3.打开VMware创建新的虚拟机 选择自定义高级后按下一步 继续下一步 选择稍后安装操作系统 客户机操作系统选择Linux,版本选择Cent ...
- mono for android学习过程系列教程(4)
今天要讲的事情是构建安卓程序的UI界面. 首先给大家上点小点心,如图: 上面就是我们界面的设计模块,仔细看中间大块的下方,有一个Source,这就类似webform里面的设计和源代码界面. 在这个页面 ...
- JAVA通信系列三:Netty入门总结
一.Netty学习资料 书籍<Netty In Action中文版> 对于Netty的十一个疑问http://news.cnblogs.com/n/205413/ 深入浅出Nettyhtt ...
- 《深入理解Java虚拟机》调优案例分析与实战
上节学习回顾 在上一节当中,主要学习了Sun JDK的一些命令行和可视化性能监控工具的具体使用,但性能分析的重点还是在解决问题的思路上面,没有好的思路,再好的工具也无补于事. 本节学习重点 在书本上本 ...
- Egret3D 研究报告(一)初试
了解的朋友应该知道我最近一直都在鼓吹webgl. 今天有一点时间,加了一个Egret3D的群,就开始了这个坑. 耳听为虚,眼见为实.让我们荡起双桨,一起去刷一下egret 打开姿势 至于以什么姿势打开 ...