1. 程式人生 > >Asp.net 面向介面可擴充套件框架之業務規則引擎擴充套件元件

Asp.net 面向介面可擴充套件框架之業務規則引擎擴充套件元件

原文: 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程式碼來做業務規則配置的,以後我還需要做使用檔案配置做動態業務規則的例子,以便在容器配置檔案中使用