zoukankan      html  css  js  c++  java
  • 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 = 1, Name = "管理员", Sort = 9999 };
            Role expert = new Role { Id = 2, Name = "专家", Sort = 999 };
            User user1 = new User { Id = 1, Name = "张三", Year = 3 };
            User user2 = new User { Id = 2, Name = "李四", Year = 10 };
            User user3 = new User { Id = 3, Name = "王二", Year = 0 };

    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 = 0;
                    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('-', 80));
                    List<Article> list = repository.ListAll();
                    if (list.Count < 1)
                    {
                        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('-', 80));
                }
            }
    显示所有文章代码
    文章处理成功,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 = 0;
                int topLimit = 1;
                engine.When(post => topNum < topLimit && Comparer<Role>.Default.Compare(permission.Get(post.User), expert) >= 0).Then(post => { topNum++; return TopArticles.Add(post.Article); });
                engine.When(post => Comparer<Role>.Default.Compare(permission.Get(post.User), expert) >= 0).Then(post => PerfectArticles.Add(post.Article));
            }
            private void ConfigYear(Engine<ArticlePublish, int> engine)
            {
                engine.When(post => post.User.Year >= 8).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 = 0;
                int topLimit = 1;
                Engine<ArticlePublish, int> perfectEngine = new Engine<ArticlePublish, int>(post => Comparer<Role>.Default.Compare(permission.Get(post.User), expert) >= 0);
                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代码来做业务规则配置的,以后我还需要做使用文件配置做动态业务规则的例子,以便在容器配置文件中使用

  • 相关阅读:
    获取指定函数的函数名称(用于兼容IE)
    opa gatekeeper笔记:AdmissionReview input.request请求对象结构
    团队内部密码共享方案:KeePassXC+微盘(企业微信)
    一个简单的golang项目,实验 gitlab-ci-cd Pipelines
    调用企业微信API拨打紧急通知电话
    使用PAM模块实现普通用户之间su免密切换
    thin_check命令 man手册
    Nginx server_name翻译
    UDP端口检查告警SHELL脚本(企业微信版机器人版)
    从零搭建vsftpd
  • 原文地址:https://www.cnblogs.com/xiangji/p/5485598.html
Copyright © 2011-2022 走看看