緣起
背景
2020年過年時重構了一下組內數據管理平臺的工單系統,相關文章可參考:工單系統重構過程。
工單系統重構前,不同類型工單在工單生命周期的每個節點都需要有一個接口實現,這樣每加入一種新類型的工單,接口數量就會增加一倍。重構工單系統后,不同類型的工單調用統一的接口,前后端交互時只需要前端傳入工單type,后端根據type去調用不同工廠的工單實例,非常方便。
此時,工單系統的uri大概長這樣:
/worksheet/add
/worksheet/modify
/worksheet/info
/worksheet/submit
...
這幾個接口在線上運行了一段時間非常穩定(這能有啥問題呢~) ,不過隨著業務迭代,一些新的需求需要在管理平臺上實現:
由于組內的 UFS在線數據服務系統 是一個配置化的系統,其支持業務的方式有兩種:
1. 如果業務比較簡單,通過配置直接就可以支持;
2. 如果業務過于定制化,需要通過通過配置+定制化開發支持。
而配置是放在數據庫里面的,所以很多時候只一條SQL就能支持沒有定制業務邏輯的需求。但是這樣一來就有了不可控的地方,公司對于代碼上線有一套完整的SOP,并通過上線系統來規避可能出現的風險。但是對于數據庫里面的配置,沒有一套上線系統來保證,如果哪個同學小手一抖,數據庫里的配置寫錯了,且服務集群導入了錯誤的配置,那整個線上服務可能就炸了。沒錯,我想說的是配置和代碼上線一樣,也需要有個灰度的過程。比如說需要有個SOP,定義清楚當db中的配置更新后,先加載到某集群的某一臺server上運行一段時間,確定無問題后再把配置加載到集群中的其他機器上,如果服務是多集群的,其他集群也應如此。
組內的 UFS 的數據生產來源有多種,可以是用戶讀寫、離線數據導在線和消息隊列等。對于離線數據導在線和消息隊列,是需要和外部系統通過RPC做一些交互的。沒錯,這里想說的是 數據生產的過程需要一些和外部的交互:可能是RPC操作,可能是操作在一些非工單表,此時一個form表單滿足不了需求,系統需要一個pipline的模型。
而單一的pipline還是滿足不了全部的需求:承接的業務有不同的重要程度和復雜程度。對于比較復雜或重要的業務需要QA同學的支持。而簡單且不重要的業務可能在QA同學評估后由業務RD自己把關就可以了。而對于不同的數據來源,這條pipline中需要經過的節點也是不同的。我們可以針對不同數據源的業務分別建立不同的上線SOP的wiki,但是如果只是讓人通過手工去保證,難免會出現一些問題,如果有一個流程系統把這些SOP流程像代碼上線系統一樣也落實下來,那么整個在線系統服務的穩定性也會大大增強。
為什么要造輪子
因此就有了在數據管理平臺上加入流程引擎的需求,流程系統在業內是一個非常常見的需求,業內有標準的建模標準,公司內部也有公共的流程系統,但是在捋清了業務需求之后還是決定自己做一個。原因大概有這些:
前期調研了業界的BPMN(Business Process Modeling Notation: 業務流程建模與標注),它是BPM及workflow的建模語言標準之一,定義了業務流程圖。一般BPMN的模型是一個圖模型,但我們的業務需求更貼近一個鏈表模型(pipline)。在BPMN中,用網關這個對象做為不同條件分支,而我們的業務中沒有網關的概念,條件分支這個概念下沉到了工單狀態中。
由來
模型設計
那么,如何從0到1實現一個能用的流程系統,并和之前的工單系統結合起來使用呢~
首先抽象一下需求:
對于不同的業務,需要建立不同的上線SOP(也就是不同的流水線),一個流程會包含多個節點(也就是鏈表中的元素)
一個工單有若干種狀態,一個流程實例中不同節點在不同工單狀態下對于不同身份的用戶可執行不同動作。 這句話比較繞,舉個例子 :對于一個新建的工單來說,工單的狀態為初始化,此時普通用戶可以執行的動作為填寫工單。當用戶填寫完工單之后,工作流節點進行流轉,工單的狀態為未審批,此時對于管理員可以執行的動作為通過/拒絕工單以及查看工單,而對于普通用戶可以執行的動作只有查看工單。流程中當一個動作的結果為某個狀態時,工單的當前節點需要進行流轉(可能是向前流轉,可能是向后流轉)
用戶可以根據對應id查詢到自己創建的流程實例信息
明確了需求之后,就可以進行模型設計了~
對于需求1:一個流程涉及到的節點是固定的。不同業務需要的流程不同,所以這塊需要靈活配置,我們組的習慣是把配置寫到數據庫里,這樣的好處是不需要上線,這次同樣把流程的元信息寫入了數據庫的表里,一個流程對應多條記錄,一條記錄代表一個流程的一個節點。后續如果流程特別多,這里也可以考慮搞個接口通過前端托拉拽來配置流程節點元信息。
對于需求2:數據庫做一個json結構的字段去處理 在不同工單狀態下對應不同角色可執行的動作不同這個需求,json的schema大概是如下所示,其中 event 用來判斷工單當前的狀態是否滿足條件,action用來表示滿足event時用戶可執行的動作。與前端交互時,當用戶執行一個動作之后工單的狀態發生變化,最新的狀態下用戶可執行那些動作用display字段展示,如果滿足event,display字段為true,前端展示對應的action,反之不展示此action。而對于流轉的需求,無非就是得到鏈表的nextNode,preNode和headNode, 在元信息里配好即可。
{
"user":[
{
"event":"{{status}} == 0",
"action":"AddWorksheet"
},
{
"event":"{{status}} == 1 || {{status}} == -1",
"action":"GetWorksheetInfo"
},
{
"event":" {{status}} == -1",
"action":"ModifyWorkSheet"
}],
"admin":[
{
"event":"{{status}} == 0",
"action":"AddWorksheet"
},
{
"event":"{{status}} == 1 || {{status}} == -1",
"action":"GetWorksheetInfo"
}]
}
對于需求3:需要一個getworkflowInstanceInfo的接口,根據數據庫里流程實例的id,結合流程元信息表,拼接出一個完整的流程鏈表給前端。這里同樣給前端返回一個大json結構,schema大概是這樣:
{
"id": 666,
"workflow_ins": [
{
"name": "aaa",
"cname": "開始",
"next": "bbb",
"activity": "xxx",
"description": "開始的描述"
},
{
"name": "bbb",
"cname": "新建xx工單",
"next": "ccc",
"activity": "[{\"event\":\"\",\"action\":\"GetWorksheetInfo\",\"caction\":\"查看\",\"display\":true}]",
"description": "新建xx工單的描述"
},
{
"name": "ccc",
"cname": "結束",
"next": "",
"activity": "yyy",
"description": "結束的描述"
}
],
"cur_node": "xxx",
"worksheet_type": "hzhzh",
"user_name": "xiaoming",
"status": "wait",
"create_time": "2020-05-20 00:00:00",
"update_time": "2020-05-21 00:00:00"
}
這樣捋下來,模型涉及到對象就比較清晰了:
對于流程節點,用鏈表來表示;
對于工單狀態,是一個有限狀態機(FSM)的模型;
對于用戶角色,我們系統中的用戶角色比較簡單,直接用白名單表示管理員用戶即可;
對于可執行動作,這塊是對應之前的工單系統的接口,復用即可;
代碼設計
對照著需求,三個api就可以完成流程引擎和前端交互的需求, 第一個API用來 初始化流程引擎 ,根據用戶傳入的參數調用不同的流程引擎實例;第二個API用來 執行流程動作; 第三個API用來查看當前流程引擎實例的信息。
對于執行流程動作這個接口,首先根據前端傳入的actionParam確認需要執行的動作,這里對應之前的工單系統中的動作,而各個動作有一些公共的前置后置操作,這里實現了一個動作工廠(ActionFactory),拿到工廠實例之后再根據工單類型去調用不同工單所執行的操作,這里需要一個工單工廠(WorksheetFactory),同樣會執行一些公共操作。交互邏輯如圖所示:
工單工廠的UML圖:
動作工廠的UML圖:
在Get(Action/Worksheet)Ins這個方法中,由于后續工單和動作可能會很多,沒有用if-else 或者是switch-case,用一個Map來得到實例。在DoAction的具體實現中,由于傳入的參數是一個map, 各種工單參數是struct,所以這里還需要用點反射把map轉換為struct(吐槽一下, golang的反射好難用....)。
這樣,如果需要引入新類型的工單,在工單工廠注冊實例并實現工單動作即可,如果要引入新的工單動作,需要在動作工廠描述好對應的行為。
交互設計
有了流程系統之后,前后端交互方式也發生了變化,從api上看,原來的5個api是圍繞工單的生命周期進行前后端交互的,后續有新的工單動作加入,則需要加入新的api。
而新的API是圍繞流程引擎而來的,只需要三個就可以和前端進行交互了(初始化流水線、執行動作、查看流水線信息)。前后端交互變得簡單,后續即使工單需要加入新的動作,也不需要引入新的API。
反思
目前這套流程引擎已經上線跑了一段時間,對這套流程引擎做一個小總結:
優點
有了這套系統,可以解決業務上線過程中,流程不規范,信息沒有及時同步到上下游等問題,同時整個pipline直觀的展示給用戶,用戶對整個業務上線流程會更熟悉,這套系統是可以提高穩定性和提升效率的~
缺點
對于工單狀態的流轉,更多是和動作綁定在一起。比如執行一個接口后,系統用一條SQL去修改工單的狀態,這樣代碼里有一些當前狀態的檢查,這樣大量的if不是太美觀,后續考慮用一個模型統一管理工單的狀態,比如說行為樹(BehaviourTree)。
參考
歡迎關注我的個人公眾號: 薯條的自我修養