規(guī)則引擎與商業(yè)CRM的完美邂逅:將智能決策融入商業(yè)擴(kuò)展
一、背景介紹
商業(yè)CRM系統(tǒng)的商機(jī)模塊業(yè)務(wù)復(fù)雜、場(chǎng)景繁多、規(guī)則調(diào)整頻繁,商機(jī)流轉(zhuǎn)效率一定程度決定了銷(xiāo)售開(kāi)單的效率。如何高效配合產(chǎn)品側(cè)完成業(yè)務(wù)規(guī)則調(diào)整,商機(jī)流轉(zhuǎn)經(jīng)歷了硬編碼到半配置化的優(yōu)化升級(jí),過(guò)程中遇到了一些問(wèn)題,也總結(jié)了一些經(jīng)驗(yàn),今天來(lái)和大家掰開(kāi)揉碎了講一講這其中遇到的問(wèn)題和解決方案。
1.1 什么是CRM
先看一下CRM的官方定義:
CRM(Customer Relationship Management):客戶關(guān)系管理,是指企業(yè)為提高核心競(jìng)爭(zhēng)力,利用相應(yīng)的信息技術(shù)以及互聯(lián)網(wǎng)技術(shù)協(xié)調(diào)企業(yè)與顧客間在銷(xiāo)售、營(yíng)銷(xiāo)和服務(wù)上的交互,從而提升其管理效率,向客戶提供創(chuàng)新式的個(gè)性化的客戶交互和服務(wù)的過(guò)程。
其最終目標(biāo)是:吸引新客戶、保留老客戶以及將已有客戶轉(zhuǎn)為忠實(shí)客戶,增加市場(chǎng)。
可以這么簡(jiǎn)單的理解,CRM的核心價(jià)值就是:將潛在客戶更高效的轉(zhuǎn)化為客戶,將客戶更高效的轉(zhuǎn)化為長(zhǎng)期客戶。
圖片
介紹完什么是CRM,那CRM系統(tǒng)就很容易理解了,用于支撐企業(yè)進(jìn)行客戶關(guān)系管理的系統(tǒng),就可以稱作CRM系統(tǒng)。這個(gè)定義比較寬泛,每個(gè)公司對(duì)CRM中功能邊界也不完全一樣,大家初步理解這個(gè)概念就行。比如轉(zhuǎn)轉(zhuǎn)商業(yè)CRM系統(tǒng),主要包含:商機(jī)管理、客戶管理、銷(xiāo)售/運(yùn)營(yíng)人員管理、業(yè)績(jī)管理、效率監(jiān)控等功能模塊。
1.2 商機(jī)業(yè)務(wù)介紹
要想把潛在客戶變成客戶,就需要銷(xiāo)售人員進(jìn)行跟進(jìn),對(duì)潛在客戶進(jìn)行產(chǎn)品的售賣(mài),用戶購(gòu)買(mǎi)產(chǎn)品后,變成客戶。這個(gè)潛在客戶,我們稱作“商機(jī)”,也就是一次成單的機(jī)會(huì),有的CRM系統(tǒng)中,也稱為“線索”。
上圖是潛在用戶到客戶簡(jiǎn)單的流轉(zhuǎn)圖
從商機(jī)生成到最終成單,銷(xiāo)售跟進(jìn)過(guò)程中,涉及一些概念,比如商機(jī)池(公海池、私海池)、商機(jī)狀態(tài)/來(lái)源/類型/等級(jí)、商機(jī)的流轉(zhuǎn)等。
商機(jī)池:各種商機(jī)的集合。公海池:該集合里的商機(jī)當(dāng)前不歸屬于任何銷(xiāo)售,所有銷(xiāo)售均可以看到公海里的商機(jī)。私海池:綁定了特定銷(xiāo)售的商機(jī)集合,比如銷(xiāo)售A的商機(jī)私海池,只有銷(xiāo)售A可以看到和跟進(jìn),其他銷(xiāo)售不可見(jiàn)、不可跟進(jìn)。商機(jī)流轉(zhuǎn):商機(jī)在跟進(jìn)過(guò)程中,被不同的銷(xiāo)售跟進(jìn),狀態(tài)發(fā)生不同的變化。流轉(zhuǎn)規(guī)則:流轉(zhuǎn)過(guò)程中會(huì)有各種各樣的業(yè)務(wù)規(guī)則限制,比如銷(xiāo)售最多可以認(rèn)領(lǐng)100條商機(jī)、負(fù)責(zé)手機(jī)類目的銷(xiāo)售不能認(rèn)領(lǐng)電腦類目的商機(jī)、銷(xiāo)售剛放棄的商機(jī)不能立馬重新認(rèn)領(lǐng)、單位時(shí)間內(nèi)不進(jìn)行電話溝通的商機(jī)將流出銷(xiāo)售私海等等。
二、商機(jī)流轉(zhuǎn)遇到的問(wèn)題
2.1 商機(jī)流轉(zhuǎn)業(yè)務(wù)特點(diǎn)
這里舉一個(gè)例子來(lái)說(shuō)明商機(jī)的流轉(zhuǎn),業(yè)務(wù)背景:商機(jī)對(duì)應(yīng)用戶主營(yíng)類目為手機(jī),銷(xiāo)售A、B負(fù)責(zé)手機(jī)類目,銷(xiāo)售C負(fù)責(zé)電腦類目,銷(xiāo)售D也負(fù)責(zé)手機(jī)類目。
圖片
這是一個(gè)簡(jiǎn)單的流程,實(shí)際流程比這個(gè)復(fù)雜。從這個(gè)簡(jiǎn)易的流程介紹中,可以窺見(jiàn)部分商機(jī)流轉(zhuǎn)模塊的業(yè)務(wù)特點(diǎn),總結(jié)起來(lái)有三點(diǎn):
- 狀態(tài)的多樣性
圖片
- 狀態(tài)間轉(zhuǎn)換場(chǎng)景繁多
圖片
- 流轉(zhuǎn)規(guī)則復(fù)雜多變
圖片
2.2 商機(jī)流轉(zhuǎn)業(yè)務(wù)痛點(diǎn)
之前商業(yè)CRM中關(guān)于流轉(zhuǎn)的處理邏輯,好多都是硬編碼,舉個(gè)銷(xiāo)售認(rèn)領(lǐng)某條商機(jī)的例子:
//狀態(tài)校驗(yàn)
if(checkClueStatus(param)){
return “狀態(tài)不合法”;
}
//綁定人校驗(yàn)
if(checkClueBindUser(param)){
return “上一個(gè)綁定人不可以為···”;
}
//私海容量校驗(yàn)
if(checkPrivateClueCount(param)){
return “私海庫(kù)已滿,無(wú)法操作··”;
}
//類目校驗(yàn)
if(checkClueCate(param)){
return “類目不匹配,無(wú)法操作··”;
}
//任務(wù)是否完成校驗(yàn)
if(checkClueTaskFinished(param)){
return “任務(wù)未完成,無(wú)法操作··”;
} ······
bind(param);//綁定操作
log();//日志記錄操作
從代碼中可以看出,銷(xiāo)售從公海進(jìn)行認(rèn)領(lǐng)一條自己覺(jué)得有價(jià)值的商機(jī)時(shí),并不是直接就讓該商機(jī)流入到該銷(xiāo)售私海中,這個(gè)過(guò)程會(huì)有各種規(guī)則的業(yè)務(wù)校驗(yàn)。
2.2.1 痛點(diǎn)
- 硬編碼實(shí)現(xiàn)的業(yè)務(wù),維護(hù)成本大;
- 業(yè)務(wù)規(guī)則經(jīng)常調(diào)整,難以應(yīng)對(duì)變化,產(chǎn)研配合效率低;
- 業(yè)務(wù)規(guī)則調(diào)整需要配合修改代碼,重新上線后生效。
2.2.2 訴求
- 規(guī)則抽取成可配置項(xiàng),調(diào)整方便;
- 校驗(yàn)相關(guān)閾值,可隨時(shí)動(dòng)態(tài)調(diào)整,無(wú)需上線;
- 代碼優(yōu)雅度適當(dāng)提高。
帶著我們的痛點(diǎn)和訴求,接下來(lái)看看可以找到什么樣的解決方案。然后我們進(jìn)入了調(diào)研階段,發(fā)現(xiàn)對(duì)于這種策略比較密集的業(yè)務(wù),規(guī)則引擎是一個(gè)可參考方向,然后就開(kāi)始了解規(guī)則引擎相關(guān)細(xì)節(jié),了解完后,發(fā)現(xiàn)和業(yè)務(wù)問(wèn)題匹配度很高。接下來(lái),先介紹一下規(guī)則引擎相關(guān)的基本知識(shí)。
三、什么是規(guī)則引擎
3.1 什么是規(guī)則
規(guī)則就是:條件 --> 推理 --> 結(jié)果。一個(gè)完整的規(guī)則是需要這三部分元素的,平時(shí)大家討論規(guī)則,某些時(shí)候只是在討論第一部分的條件,比如學(xué)校的行為規(guī)范,咱們國(guó)家的各種法律條文,可能關(guān)注的部分是需要滿足什么條件(不能做什么什么),后面的推理和結(jié)果省去了。比如學(xué)生準(zhǔn)則里的不辱罵同學(xué),這是一個(gè)條件,那怎么算辱罵呢,這是推理過(guò)程,辱罵后會(huì)發(fā)生什么呢,會(huì)受到老師批評(píng),這是結(jié)果。
那什么是推理引擎和規(guī)則引擎呢?
- 推理引擎:如果由程序來(lái)處理推理過(guò)程,那么這個(gè)程序就叫做推理機(jī)/推理引擎。
- 規(guī)則引擎:基于規(guī)則的推理機(jī)易于理解、易于獲取、易于管理,被廣泛采用。這種推理引擎被稱為“規(guī)則引擎”。是一種嵌入在應(yīng)用程序中的組件,實(shí)現(xiàn)了將業(yè)務(wù)決策從應(yīng)用程序代碼中分離出來(lái),并使用預(yù)定義的語(yǔ)義模塊編寫(xiě)業(yè)務(wù)決策。接受數(shù)據(jù)輸入,解釋業(yè)務(wù)規(guī)則,并根據(jù)業(yè)務(wù)規(guī)則做出業(yè)務(wù)決策。
3.2 推理引擎原理
圖片
- 模式匹配器決定選擇執(zhí)行哪個(gè)規(guī)則,何時(shí)執(zhí)行規(guī)則;
- 議程管理模式匹配器挑選出來(lái)的規(guī)則的執(zhí)行次序;
- 執(zhí)行引擎負(fù)責(zé)執(zhí)行規(guī)則和其他動(dòng)作。
推理引擎通過(guò)決定哪些規(guī)則滿足事實(shí)或目標(biāo),并授予規(guī)則優(yōu)先級(jí),滿足事實(shí)或目標(biāo)的規(guī)則被加入議程,具體過(guò)程:
- 將初始數(shù)據(jù)(fact)輸入至工作內(nèi)存(Working Memory)。
- 使用Pattern Matcher將規(guī)則庫(kù)(Rules repository)的規(guī)則(rule)和數(shù)據(jù)(fact)比較。
- 如果執(zhí)行規(guī)則存在沖突(conflict),即同時(shí)激活了多個(gè)規(guī)則,將沖突的規(guī)則放入沖突集合。
- 解決沖突,將激活的規(guī)則按順序放入Agenda。
- 執(zhí)行Agenda中的規(guī)則。
- 重復(fù)步驟2至5,直到執(zhí)行完畢Agenda中的所有規(guī)則。
推理方式分為正向推理和反向推理
- 正向推理(演繹法):事實(shí)驅(qū)動(dòng),正向推理,直到無(wú)事實(shí)可用或者得出結(jié)論為止;
- 反向推理(歸納法):目標(biāo)驅(qū)動(dòng),提出假設(shè),尋找支持該假設(shè)的證據(jù),如果能找到證明,結(jié)論正確,如果不能,換假設(shè)繼續(xù)。
常見(jiàn)的模式匹配算法有Rete,LFA,TREAI,LEAPS。Rete算法是目前效率最高的一個(gè)演繹法推理算法,許多規(guī)則引擎都是基于Rete算法來(lái)進(jìn)行推理計(jì)算的。
3.3 Rete算法
3.3.1 Rete算法簡(jiǎn)介
- Rete在拉丁語(yǔ)中是“net”,有網(wǎng)絡(luò)的意思。Rete算法由Carnegie Mellon University的Dr Charles L. Forgy設(shè)計(jì)發(fā)明,是一個(gè)用來(lái)實(shí)現(xiàn)產(chǎn)生式規(guī)則系統(tǒng)(production/inference)的高效模式匹配算法。
- RETE算法可以分為兩部分:規(guī)則編譯(rule compilation)和運(yùn)行時(shí)執(zhí)行(runtime execution)。
- 規(guī)則編譯:是指根據(jù)規(guī)則集生成推理網(wǎng)絡(luò)的過(guò)程。
- 運(yùn)行時(shí)執(zhí)行:指將數(shù)據(jù)送入推理網(wǎng)絡(luò)進(jìn)行篩選的過(guò)程。
- 相關(guān)概念:
- 事實(shí)(fact):對(duì)象之間及對(duì)象屬性之間的關(guān)系
- 規(guī)則(rule):是由條件和結(jié)論構(gòu)成的推理語(yǔ)句,一般表示為if…Then。一個(gè)規(guī)則的if部分稱為L(zhǎng)HS(left-hand-side),then部分稱為RHS(right hand side)。
- 模式(module):就是指IF語(yǔ)句的條件。這里IF條件可能是有幾個(gè)更小的條件組成的大條件。模式就是指的不能在繼續(xù)分割下去的最小的原子條件。
3.3.2 Rete算法原理
圖片
這是Rete算法的原理圖,Rete算法涉及兩種網(wǎng)絡(luò)和6種不同作用的節(jié)點(diǎn)。
- 網(wǎng)絡(luò)節(jié)點(diǎn):Type Node、Select Node、Join Node、Teminal Node、Alpha Memory、Beta Memory
- alpha網(wǎng)絡(luò):過(guò)濾working memory,找出符合規(guī)則中每一個(gè)模式的集合,生成alpha memory(滿足該模式的集合)。
- Beta網(wǎng)絡(luò):有兩種類型的節(jié)點(diǎn)Beta Memory和Join Node。前者主要存儲(chǔ)Join完成后的集合。后者包含兩個(gè)輸入口,分別輸入需要匹配的兩個(gè)集合,由Join節(jié)點(diǎn)做合并工作傳輸給下一個(gè)節(jié)點(diǎn)。
3.3.2.1 Rete推理網(wǎng)絡(luò)生成過(guò)程
圖片
- 創(chuàng)建root節(jié)點(diǎn)(根節(jié)點(diǎn)),推理網(wǎng)絡(luò)的入口。
- 拿到規(guī)則1,從規(guī)則1中取出模式1(模式就是最小的原子條件,所以規(guī)則模式的關(guān)系是1:n)。
a) 檢查模式1中的參數(shù)類型,如果是新類型,添加一個(gè)類型節(jié)點(diǎn)。
b) 檢查模式1對(duì)應(yīng)的Alpha節(jié)點(diǎn)是否存在,如果存在記錄下節(jié)點(diǎn)的位置;如果沒(méi)有,將模式1作為一個(gè)Alpha節(jié)點(diǎn)加入到網(wǎng)絡(luò)中。同時(shí)根據(jù)Alpha節(jié)點(diǎn)建立Alpah內(nèi)存表。
c) 重復(fù)b,直到處理完所有模式。
d) 組合Beta節(jié)點(diǎn):Beta(2)左輸入節(jié)點(diǎn)為Alpha(1),右輸入節(jié)點(diǎn)為Alpha(2);Beta(i)左輸入節(jié)點(diǎn)是Beta(i-1),右輸入節(jié)點(diǎn)為Alpha(i),并將兩個(gè)父節(jié)點(diǎn)的內(nèi)存表內(nèi)聯(lián)成為自己的內(nèi)存表。
e) 重復(fù)d,直到所有Beta節(jié)點(diǎn)處理完畢。
f) 將動(dòng)作Then部分封裝成最后節(jié)點(diǎn)做為Beta(n)。
- 重復(fù)2,直到所有規(guī)則處理完畢。
3.3.2.2 Rete匹配過(guò)程
圖片
- 導(dǎo)入需要處理的事實(shí)到facts集合中。
- 如果facts不為空,選擇一個(gè)fact進(jìn)行處理。否則停止匹配過(guò)程。
- 選擇alpha網(wǎng)的第一個(gè)節(jié)點(diǎn)運(yùn)行(建立網(wǎng)絡(luò)的時(shí)候設(shè)定的),通過(guò)該節(jié)點(diǎn)則進(jìn)入alpha網(wǎng)的下一個(gè)節(jié)點(diǎn),直到進(jìn)入alpha memory。否則跳轉(zhuǎn)到下一條判斷路徑。
- 將alpha memory的結(jié)果加入到beta memory中,如果不為T(mén)erminal節(jié)點(diǎn),則檢測(cè)另一個(gè)輸入集合中是否存在滿足條件的事實(shí),滿足則執(zhí)行join,進(jìn)入到下一個(gè)beta memory重復(fù)執(zhí)行3。若另一個(gè)輸入集合無(wú)滿足條件的事實(shí),返回到2。如果該節(jié)點(diǎn)為T(mén)erminal節(jié)點(diǎn),執(zhí)行ACT并添加到facts中。
3.3.3 Rete算法實(shí)例
上面介紹的這些都偏理論化,可能有一點(diǎn)不太容易理解,沒(méi)關(guān)系,接下來(lái)咱們用一個(gè)具體的例子,看看Rete算法到底是如何運(yùn)行的。咱們依然以這個(gè)選拔籃球苗子的例子來(lái)說(shuō)明。
圖片
- 首先看下對(duì)應(yīng)的網(wǎng)絡(luò)圖
圖片
- Rete匹配過(guò)程
- 匹配過(guò)程詳解
A節(jié)點(diǎn):拿StudentFact的年級(jí)數(shù)值進(jìn)行年級(jí)匹配,如果年級(jí)符合條件,則把該StudentFact的引用記錄到A節(jié)點(diǎn)的alpha內(nèi)存區(qū)中,退出年級(jí)匹配。
B節(jié)點(diǎn):拿StudentFact的性別內(nèi)容進(jìn)行性別匹配,如果性別符合條件,則把該StudentFact的引用記錄到B節(jié)點(diǎn)的alpha內(nèi)存區(qū)中,然后找到B節(jié)點(diǎn)左引用的Beta節(jié)點(diǎn),也就是C節(jié)點(diǎn)。
C節(jié)點(diǎn):C節(jié)點(diǎn)找到自己的左引用也就是A節(jié)點(diǎn),看看A節(jié)點(diǎn)的alpha內(nèi)存區(qū)中是否存放了StudentFact的引用,如果存放,說(shuō)明年級(jí)和性別兩個(gè)條件都符合,則在C節(jié)點(diǎn)的Beta內(nèi)存區(qū)中存放StudentFact的引用,退出性別匹配。
D節(jié)點(diǎn):拿StudentFact的年齡數(shù)值進(jìn)行年齡條件匹配,如果年齡符合條件,則把該StudentFact的引用記錄到D節(jié)點(diǎn)的alpha的內(nèi)存區(qū)中,然后找到D節(jié)點(diǎn)的左引用的Beta節(jié)點(diǎn),也就是E節(jié)點(diǎn)。
E節(jié)點(diǎn):E節(jié)點(diǎn)找到自己的左引用也就是C節(jié)點(diǎn),看看C節(jié)點(diǎn)的Beta內(nèi)存區(qū)中是否存放了StudentFact的引用,如果存放,說(shuō)明年級(jí),性別,年齡三個(gè)條件符合,則在E節(jié)點(diǎn)的Beta內(nèi)存區(qū)中存放StudentFact的引用,退出年齡匹配。
F節(jié)點(diǎn):拿StudentFact的身體數(shù)值進(jìn)行身體條件匹配,如果身體條件符合,則把該StudentFact的引用記錄到D節(jié)點(diǎn)的alpha的內(nèi)存區(qū)中,然后找到F節(jié)點(diǎn)的左引用的Beta節(jié)點(diǎn),也就是G節(jié)點(diǎn)。
G節(jié)點(diǎn):G節(jié)點(diǎn)找到自己的左引用也就是E節(jié)點(diǎn),看看E節(jié)點(diǎn)的Beta內(nèi)存區(qū)中是否存放了StudentFact的引用,如果存放,說(shuō)明年級(jí),性別,年齡,身體四個(gè)條件符合,則在G節(jié)點(diǎn)的Beta內(nèi)存區(qū)中存放StudentFact的引用,退出身體匹配。
H節(jié)點(diǎn):拿StudentFact的身高數(shù)值進(jìn)行身高條件匹配,如果身高條件符合,則把該StudentFact的引用記錄到H節(jié)點(diǎn)的alpha的內(nèi)存區(qū)中,然后找到H節(jié)點(diǎn)的左引用的Beta節(jié)點(diǎn),也就是I節(jié)點(diǎn)。
I節(jié)點(diǎn):I節(jié)點(diǎn)找到自己的左引用也就是G節(jié)點(diǎn),看看G節(jié)點(diǎn)的Beta內(nèi)存區(qū)中是否存放了StudentFact的引用,如果存放了,說(shuō)明年級(jí),性別,年齡,身體,身高五個(gè)條件都符合,則在I節(jié)點(diǎn)的Beta內(nèi)存區(qū)中存放StudentFact引用。同時(shí)說(shuō)明該StudentFact對(duì)象匹配了該規(guī)則,形成一個(gè)議程,加入到?jīng)_突區(qū),執(zhí)行該條件的結(jié)果部分:該學(xué)生是一個(gè)籃球苗子。
3.3.4 Rete算法的優(yōu)點(diǎn)
- 可共享node和memory,提高效率;
- memory存儲(chǔ)中間結(jié)果,空間換時(shí)間,避免重復(fù)計(jì)算;
- Rete 匹配速度與規(guī)則數(shù)目無(wú)直接關(guān)系,因?yàn)閒act只有滿足本節(jié)點(diǎn)才會(huì)繼續(xù)向下沿網(wǎng)絡(luò)傳遞。
3.3.5 Rete算法的缺點(diǎn)
規(guī)則的條件與facts的數(shù)目如果過(guò)大,對(duì)應(yīng)memory會(huì)指數(shù)級(jí)升高,極端情況下會(huì)耗盡系統(tǒng)資源。
3.3.6 Rete算法使用建議
- 容易變化的規(guī)則盡量置后匹配,可以減少規(guī)則的變化帶來(lái)規(guī)則庫(kù)的變化。
- 約束性較為通用或較強(qiáng)的模式盡量置前匹配,可以避免不必要的匹配。
- 針對(duì) Rete 算法內(nèi)存開(kāi)銷(xiāo)大和facts增加刪除影響效率的問(wèn)題,技術(shù)上應(yīng)該在 AlphaMemory 和 BetaMemory 中,只存儲(chǔ)指向內(nèi)存的指針,并對(duì)指針建里索引(可用 hash 表或者非平衡二叉樹(shù))。
- Rete 算法 JoinNode 可以擴(kuò)展為 AndJoinNode 和 OrJoinNode,兩種節(jié)點(diǎn)可以再進(jìn)行組合。
看完這個(gè)匹配的過(guò)程,相信大家對(duì)規(guī)則引擎中常用的Rete算法有了一定的了解?;氐揭?guī)則引擎,那為啥用規(guī)則引擎呢,也就是它有何優(yōu)勢(shì),以及有哪些適用的場(chǎng)景呢?
3.4 規(guī)則引擎優(yōu)勢(shì)
- 高靈活性:規(guī)則保存在知識(shí)庫(kù)中,規(guī)則變動(dòng)可以輕易做出修改。
- 容易掌控:規(guī)則比過(guò)程代碼更易于理解,因此可以有效地來(lái)彌補(bǔ)業(yè)務(wù)人員和開(kāi)發(fā)人員之間的溝通問(wèn)題。
- 降低復(fù)雜度:在程序中編寫(xiě)大量的判斷條件,很可能是會(huì)造成一場(chǎng)噩夢(mèng)。使用規(guī)則引擎卻能夠通過(guò)一致的表示形式,更好的處理日益復(fù)雜的業(yè)務(wù)邏輯。開(kāi)發(fā)人員也可以更關(guān)注邏輯處理,而無(wú)需過(guò)多關(guān)注邏輯判斷。
- 可重用性:規(guī)則集中管理,可提高業(yè)務(wù)的規(guī)則的可重用性。而且,傳統(tǒng)的代碼程序通常會(huì)添加不必要的變數(shù),很難進(jìn)行重復(fù)利用。
- 規(guī)則外部化:即有利于規(guī)則知識(shí)的復(fù)用,也可避免改變規(guī)則時(shí)帶來(lái)的代碼變更問(wèn)題。
3.5 規(guī)則引擎使用場(chǎng)景
一般多用于規(guī)則較多且規(guī)則調(diào)整頻繁的業(yè)務(wù)場(chǎng)景,比如:積分規(guī)則、計(jì)費(fèi)系統(tǒng)、信用風(fēng)險(xiǎn)評(píng)估、監(jiān)控告警、工作流系統(tǒng)。
3.6 規(guī)則引擎的分類
網(wǎng)上有兩種分類方式,這里我列舉出來(lái),供大家了解。
- 基于實(shí)現(xiàn)方式
- 基于完成度和功能配置
圖片
四、商機(jī)流轉(zhuǎn)問(wèn)題解決方案
了解了上面這些業(yè)務(wù)背景以及遇到的問(wèn)題,也熟悉了規(guī)則引擎的理論知識(shí),現(xiàn)在需要制定具體的解決方案了,我們?cè)趺醋龅哪??市面有各種各樣的規(guī)則引擎,先進(jìn)行技術(shù)選型,這里列舉下當(dāng)前主流規(guī)則引擎優(yōu)缺點(diǎn)。
圖片
通過(guò)各方面綜合評(píng)估,重點(diǎn)放到了Drools和easyRule兩者,且easyRule最終勝出。
4.1 Drools和easyRule對(duì)比
圖片
確定了要使用easyRule就得知道easyRule如何使用的,先介紹下其相關(guān)概念和使用方法。
4.2 easyRule插件介紹
4.2.1 規(guī)則說(shuō)明
- name:規(guī)則命名空間中的唯一規(guī)則名稱
- description:規(guī)則的簡(jiǎn)要描述
- priority:規(guī)則的優(yōu)先級(jí)
- facts:觸發(fā)規(guī)則時(shí)的一組已知事實(shí)
- conditions:在給定一些事實(shí)的情況下,為了應(yīng)用該規(guī)則,需要滿足的一組條件
- actions:滿足條件時(shí)要執(zhí)行的一組操作(可能會(huì)添加/刪除/修改事實(shí))
4.2.2 規(guī)則定義方式
- 通過(guò)在POJO上添加注解來(lái)聲明
- 通過(guò)RuleBuilder API編程
4.2.3 組合規(guī)則用法
Easy Rules提供了3種CompositeRule的實(shí)現(xiàn)。
- UnitRuleGroup:要么應(yīng)用所有規(guī)則,要么不應(yīng)用任何規(guī)則。--要么全用要么不用
- ActivationRuleGroup:激活規(guī)則組觸發(fā)第一個(gè)適用規(guī)則并忽略組中的其他規(guī)則。--首個(gè)選用
- ConditionalRuleGroup:條件規(guī)則組將具有最高優(yōu)先級(jí)的規(guī)則作為條件,如果具有最高優(yōu)先級(jí)的規(guī)則的計(jì)算結(jié)果為true,那么將觸發(fā)其余的規(guī)則。--優(yōu)先級(jí)最高說(shuō)了算
4.2.4 自定義優(yōu)先級(jí)
值越低優(yōu)先級(jí)越高。要覆蓋此行為,可重寫(xiě)compareTo()方法以提供自定義優(yōu)先級(jí)策略。
4.2.5 引擎執(zhí)行模式
- skipOnFirstAppliedRule:當(dāng)一個(gè)規(guī)則成功應(yīng)用時(shí),跳過(guò)余下的規(guī)則。--一個(gè)成功,不管其他
- skipOnFirstFailedRule:當(dāng)一個(gè)規(guī)則失敗時(shí),跳過(guò)余下的規(guī)則。--一個(gè)失敗,不管其他
- skipOnFirstNonTriggeredRule:當(dāng)一個(gè)規(guī)則未觸發(fā)時(shí),跳過(guò)余下的規(guī)則。--一個(gè)不符合,不管其他
- rulePriorityThreshold:當(dāng)優(yōu)先級(jí)超過(guò)指定的閾值時(shí),跳過(guò)余下的規(guī)則。--優(yōu)先級(jí)達(dá)標(biāo),不管其他
4.2.6 多種監(jiān)聽(tīng)器可供選擇
支持自定義規(guī)則監(jiān)聽(tīng)器、規(guī)則引擎監(jiān)聽(tīng)器。
4.2.7 表達(dá)式語(yǔ)言支持
支持MVEL、SPEL表達(dá)式語(yǔ)言,可通過(guò)編程方式定義規(guī)則。
4.2.8 規(guī)則中的錯(cuò)誤處理
- 對(duì)于條件求值過(guò)程中可能發(fā)生的任何運(yùn)行時(shí)異常(丟失事實(shí)、表達(dá)式中輸入錯(cuò)誤等),引擎將記錄一個(gè)警告,并認(rèn)為條件求值為false。
- 對(duì)于任何在執(zhí)行操作時(shí)可能發(fā)生的運(yùn)行時(shí)異常(丟失事實(shí)、表達(dá)式中輸入錯(cuò)誤等),該操作將不會(huì)執(zhí)行,引擎將記錄一個(gè)錯(cuò)誤。
4.3 easyRule使用樣例
還是用篩選籃球苗子的例子
圖片
- 定義一個(gè)學(xué)生實(shí)體類
public class Student {
/**
* 年級(jí)
*/
private Integer grade;
/**
* 性別
*/
private String gender;
/**
* 年齡
*/
private Integer age;
/**
* 是否強(qiáng)壯
*/
private Boolean isStrong;
/**
* 身高
*/
private Integer height;
/**
* 是否一個(gè)好苗子
*/
private Boolean isGoodSeed = true;
}
- 定義規(guī)則(有多種方式,我列舉幾種)
//創(chuàng)建規(guī)則1-年級(jí)
Rule rule1 = new MVELRule()
.name("grade rule")
.description("判斷一個(gè)學(xué)生是否是一個(gè)籃球好苗子-年級(jí)")
.priority(1)
.when("student.getGrade() <= 3")
.then("System.out.println(\"年級(jí)-不是好苗子\");")
.then("student.setIsGoodSeed(false);");
//創(chuàng)建規(guī)則2-性別
Rule rule2 = new MVELRuleFactory(new YamlRuleDefinitionReader()).
createRule(new FileReader(
ResourceUtils.getFile("classpath:gender-rule.yml")));
規(guī)則2需要的yml文件內(nèi)容如下:
name: "gender rule"
description: "判斷一個(gè)學(xué)生是否是一個(gè)籃球好苗子-性別"
priority: 2
condition: "student.getGender().equals(\"girl\")"
actions:
- "System.out.println(\"性別-不是好苗子\");student.setIsGoodSeed(false);"
//創(chuàng)建規(guī)則3-年齡
Rule rule3 = new MVELRuleFactory(new JsonRuleDefinitionReader()).
createRule(new FileReader(
ResourceUtils.getFile("classpath:age-rule.json")));
//創(chuàng)建規(guī)則4-是否強(qiáng)壯
Condition condition = new MVELCondition("!student.getIsStrong()");
Action action = new Action() {
@Override
public void execute(Facts facts) throws Exception {
Student student1 = (Student) facts.get("student");
student1.setIsGoodSeed(false);
System.out.println("強(qiáng)壯-不是好苗子");
}
};
Rule rule4 = new RuleBuilder()
.name("strong rule")
.description("判斷一個(gè)學(xué)生是否是一個(gè)籃球好苗子-是否強(qiáng)壯")
.priority(4)
.when(condition)
.then(action).build();
@Rule(name = "height rule" ,description = "判斷一個(gè)學(xué)生是否是一個(gè)籃球好苗子-身高")
public class HeightRule {
@Condition
public boolean checkHeight(){ return student.getHeight() <= 170;}
@Action
public void action(){
System.out.println("身高-不是好苗子");
student.setIsGoodSeed(false);
}
private Student student;
public HeightRule(Student student){
this.student = student;
}
}
//創(chuàng)建規(guī)則5-身高
HeightRule rule5 = new HeightRule(student);
- 創(chuàng)建實(shí)例(fact)
//創(chuàng)建一個(gè)Student實(shí)例(Fact)
Student student = new Student(3,"girl",9,true, 160,true);
Facts facts = new Facts();
facts.put("student", student);
- 創(chuàng)建引擎,并執(zhí)行規(guī)則
//注冊(cè)規(guī)則
Rules rules = new Rules();
rules.register(rule1);
rules.register(rule2);
//rules.register(rule3);
rules.register(rule4);
rules.register(rule5);
//創(chuàng)建規(guī)則執(zhí)行引擎,并執(zhí)行規(guī)則
RulesEngine rulesEngine = new DefaultRulesEngine();
System.out.println("開(kāi)始判斷是否是一個(gè)籃球苗子:" + JSON.toJSONString(student));
rulesEngine.fire(rules, facts);
System.out.println("是否為好苗子:" + student.getIsGoodSeed());
- 執(zhí)行結(jié)果
圖片
4.4 商機(jī)流轉(zhuǎn)如何接入easyRule
熟悉了easyRule如何使用的,接下來(lái)看看我們?nèi)绾卧陧?xiàng)目中落地的,我們分了幾步:
- 將easyRule工具包進(jìn)行二次改裝,使其執(zhí)行規(guī)則后能有返回值,封裝成jar包,將規(guī)則引擎抽取成通用能力。
初始化規(guī)則相關(guān)配置(首次初始化+定時(shí)更新);
提供對(duì)外public T fire(String ruleId, V v)通用的規(guī)則引擎api接口。
圖片
這里我們將規(guī)則引擎的處理結(jié)果進(jìn)行了返回,因?yàn)闃I(yè)務(wù)上很多場(chǎng)景需要,比如不符合規(guī)則時(shí)的提醒文案。
- 將現(xiàn)有的流轉(zhuǎn)規(guī)則進(jìn)行整理提取,將各種判斷條件拆解成單一的指標(biāo)判斷。
- 項(xiàng)目中引入easyRule工具。
項(xiàng)目中配置規(guī)則引擎相關(guān)配置;
實(shí)例化RuleEngineTemplate類;
根據(jù)場(chǎng)景,組裝上下文context;
調(diào)用ruleEngineTemplate.fire(ruleId,context)方法。
圖片
引入后,我們的商機(jī)流轉(zhuǎn)流程發(fā)生了如下變化:
圖片
4.5 商機(jī)解綁流程舉例
- 商機(jī)解綁流程
圖片
- 解綁對(duì)應(yīng)的規(guī)則配置application.yml
spring:
easy-rule:
priority-threshold: 100
skip-on-first-failed-rule: false
skip-on-first-applied-rule: true
skip-on-first-non-triggered-rule: false
rules:
- rule-id: "opportunity_unbind"
rule-file-location: "opportunity_unbind" #規(guī)則配置文件
rule-config-type: JSON
rule-factory-type: SPEL
具體的規(guī)則配置json
[
{
"name": "bind_check_cate",
"description": "判斷是否凍結(jié)-72小時(shí)",
"condition": "@opportunityUnbindRuleBll.checkOpportunityNeedFreeze(#context.getOpportunityId(), n,m)",
"priority": 4,
"actions": [
"@clueOpporBll.unbindOpportunity(#context,T(OpportunityStatusEnum).UNBIND, T(com.clue.enums.OpportunityMinorStatusEnum).UNBIND_FROZEN)"
]
},
{
"name": "task_bind_out",
"description": "任務(wù)商機(jī)流回公海",
"condition": "#context.getOpportunityStatus() == T(com.enums.OpportunityStatusEnum).TASK && #context.getOperationTypeEnum() == T(com.OpportunityOperationTypeEnum).TASK_BACK_PUBLIC",
"priority": 5,
"actions": [
"@clueBll.unbindOpportunity(#context,T(com.zhuanzhuan.biz.clue.enums.OpportunityStatusEnum).UNBIND, T(com.OpportunityMinorStatusEnum).UNBIND_NORMAL)"
]
},
{
"name": "unbind_operate",
"description": "判斷解綁后去向,現(xiàn)階段全部回到公海",
"condition": "true",
"priority": 10,
"actions": [
"@clueOpportunityBll.unbindOpportunity(#context,T(com.OpportunityStatusEnum).UNBIND, T(com.enums.OpportunityMinorStatusEnum).UNBIND_NORMAL)"
]
}
]
}
]
- 業(yè)務(wù)代碼
public Result<ParallelExecuteDTO> unbindOpportunity(UnbindOpportunityRequest request) {
return parallelExecutor.parallelExecute(request.getOpportunityIds(), (Long opportunityId) -> {
final Result<String> unbindResult = opportunityUnbindRuleBll.unbindOpportunity(opportunityId, request.getOperator(),
request.getReasonType(), request.getReasonDesc(), request.getOperationType());
logger.info("method=unbindOpportunity, act=unbind, opportunityId={},unbindResult={}", opportunityId, unbindResult);
return unbindResult;
}
);
}
4.6 引入規(guī)則引擎前后效果對(duì)比
圖片
五、總結(jié)
在easyRule引入商機(jī)流轉(zhuǎn)業(yè)務(wù)過(guò)程中,從調(diào)研到選型再到最終落地,遇到了各種大大小小的問(wèn)題,但最終的效果還是比較明顯的,對(duì)團(tuán)隊(duì)的整體效率提升非常明顯,這里有幾點(diǎn)總結(jié)與建議與大家分享。
- 系統(tǒng)引入規(guī)則引擎,一定要場(chǎng)景符合,不能為了引入而引入;
- 業(yè)務(wù)規(guī)則轉(zhuǎn)換為抽象的規(guī)則配置,可以多和業(yè)務(wù)人員交流,他們對(duì)于規(guī)則的理解可能更深刻;
- 選擇規(guī)則引擎方案后,需要定好規(guī)則維護(hù)規(guī)范,后續(xù)執(zhí)行按照規(guī)范維護(hù);
- 對(duì)于集群中沒(méi)有直接引用的代碼,不要直接清理,有可能是在規(guī)則文件里有引用。
作者介紹
楊迎,轉(zhuǎn)轉(zhuǎn)商業(yè)后端開(kāi)發(fā)工程師,目前負(fù)責(zé)商業(yè)B端相關(guān)業(yè)務(wù)系統(tǒng)開(kāi)發(fā)(商機(jī)線索、客戶運(yùn)營(yíng)、銷(xiāo)售運(yùn)營(yíng)管理、廣告發(fā)布等)。