隨着面向接口可擴展框架的繼續開發,有些功能開發出現了"瓶頸",有太多的東西要寫死才好做。但寫死的代碼擴展性是非常的不好,迷茫中尋找出入...
進而想到我以前開發的好幾個項目,都已有一定的可配置能力,想想怎么把這些地方的代碼抽象提取出來。進而想到"業務規則引擎",網上找了幾個都不太入"眼",就抽時間再造個"輪子"
業務規則引擎在很多成熟的工作流引擎中都有相應的模塊,是工作流的核心之一。但是除了工作流很多業務都需要業務規則引擎,所以它非常有必要獨立作為一個模塊。
在現實中很多項目開發都需要一些定制化的分支需求,為了這些需求把項目搞的雞飛狗跳。使用業務規則引擎來做邏輯分支路由、參數“矯正”、攔截等,說是如虎添翼應該不為過。
這里使用社區發文章來做個例子
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); } }
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); } }
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); }

/// <summary> /// 驗證規則 /// </summary> public interface IVerifyRule<TEntity> { /// <summary> /// /// </summary> /// <param name="entity"></param> /// <returns></returns> bool Check(ref TEntity entity); }

/// <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); }
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); } }
就是把委托轉化為IVerifyRule和IArgInstance對象了,也是非常簡單吧
我這個規則引擎簡潔明了,很多愛學習的同學就是“愛造輪子“,現在把核心源碼都公布了,大家也都可以定制自己的業務規則引擎了,感興趣的同學馬上動手吧
以上都是使用Fluent代碼來做業務規則配置的,以后我還需要做使用文件配置做動態業務規則的例子,以便在容器配置文件中使用