在企業級應用開發中,常常會涉及到流程和狀態,而有限狀態機(FSM)則是對應的一種簡單實現,若是複雜化,就上升到Workflow和BPM了。咱們在Fabric ChainCode的開發過程當中,也極可能涉及到狀態機,這裏咱們就舉一個例子,用FSM實現一個二級審批的狀態轉移。git
咱們有一個表單,員工填寫表單是能夠保存爲Draft狀態,提交後變成Submitted狀態,而後在一級審批的時候,能夠Approve或者Reject,贊成了改成L1Approved,進入下一級審批,拒絕了那麼就以Reject狀態打回給起草人,二級審批人員也是有Approve和Reject兩個操做,贊成了狀態就改成Complete,拒絕了就改成Reject。這是一個很常見的審批例子。github
咱們使用Go來開發ChainCode,那麼能夠採用https://github.com/looplab/fsm 這個FSM庫。這個庫也是Fabric官方採用的狀態機庫。下面是個人操做過程:docker
咱們新建一個項目fsmtest,並在其中創建住ChainCode文件:main.go,而後新建vendor文件夾,將https://github.com/looplab/fsm從GitHub clone下來,並放在vendor/github.com/looplab/fsm文件夾中,最終項目個文件結構以下:數據庫
接下來打開main.go文件,除了編寫ChainCode所必須使用的函數外,最主要的就是編寫定義狀態機轉移的初始化函數了,咱們根據前面流程圖中的流程狀態定義,咱們能夠寫出以下的FSM初始化函數:bash
func InitFSM(initStatus string) *fsm.FSM{ f := fsm.NewFSM( initStatus, fsm.Events{ {Name: "Submit", Src: []string{"Draft"}, Dst: "Submited"}, {Name: "Approve", Src: []string{"Submited"}, Dst: "L1Approved"}, {Name: "Reject", Src: []string{"Submited"}, Dst: "Reject"}, {Name: "Approve", Src: []string{"L1Approved"}, Dst: "Complete"}, {Name: "Reject", Src: []string{"L1Approved"}, Dst: "Reject"}, }, fsm.Callbacks{}, ) return f; }
接下來咱們在ChainCode重定義了4個函數,網絡
因而咱們能夠在Invoke函數中定義4中狀況:
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() fmt.Println("invoke is running " + function) if function == "Draft" { //自定義函數名稱 return t.Draft(stub, args) //定義調用的函數 } else if function == "Submit" { return FsmEvent(stub,args,"Submit") } else if function == "Approve" { return FsmEvent(stub,args,"Approve") } else if function == "Reject" { return FsmEvent(stub,args,"Reject") } return shim.Error("Received unknown function invocation") }
其中Draft函數就是把表單狀態初始化爲Draft並保存到數據庫,並不涉及狀態的修改:
func (t *SimpleChaincode) Draft(stub shim.ChaincodeStubInterface, args []string) pb.Response{ formNumber:=args[0] status:="Draft" stub.PutState(formNumber,[]byte(status))//初始化Draft狀態的表單保存到StateDB return shim.Success([]byte(status)) }
而其餘操做都涉及狀態的修改,因爲咱們引入了狀態機,因此咱們只須要初始化狀態機,併發送對應的Event便可,而最新的狀態是由狀態機根據咱們的定義而得到的。因此咱們雖然有3個操做,去只須要一個函數就能完成,並無冗餘的if else判斷,這就是狀態機的優點!
func FsmEvent(stub shim.ChaincodeStubInterface, args []string,event string) pb.Response{ formNumber:=args[0] bstatus,err:=stub.GetState(formNumber)//從StateDB中讀取對應表單的狀態 if err!=nil{ return shim.Error("Query form status fail, form number:"+formNumber) } status:=string(bstatus) fmt.Println("Form["+formNumber+"] status:"+status) f:=InitFSM(status)//初始化狀態機,並設置當前狀態爲表單的狀態 err=f.Event(event)//觸發狀態機的事件 if err!=nil{ return shim.Error("Current status is "+status+" does not support envent:"+event) } status=f.Current() fmt.Println("New status:"+status) stub.PutState(formNumber,[]byte(status))//更新表單的狀態 return shim.Success([]byte(status));//返回新狀態 }
如今狀態寫完了,咱們須要進行測試,咱們能夠git push到GitHub,而後到Ubuntu中git clone下來,也能夠經過rz命令,把Windows中開發好的ChainCode上傳到Ubuntu中,無論什麼方法,最終咱們整個ChainCode項目放在了~/go/src/github.com/hyperledger/fabric/examples/chaincode/go/fsmtest這個文件夾下。併發
而後使用e2e_cli下面的network_setup.sh up命令啓動整個Fabric網絡。啓動Fabric網絡後,咱們須要進入CLI進行部署和合適fsmtest:函數
docker exec -it cli bash
而後安裝並初始化咱們的ChainCode:oop
peer chaincode install -n fsmtest -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/fsmtest ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n fsmtest -v 1.0 -c '{"Args":[]}'
如今安裝完畢後,咱們能夠起草一個報銷單EXP1:測試
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n fsmtest -c '{"Args":["Draft","EXP1"]}'
咱們能夠看到系統返回的結果:
如今狀態是Draft,而後咱們試一試提交報銷單EXP1:
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n fsmtest -c '{"Args":["Submit","EXP1"]}'
咱們看到狀態已經改成Submitted了。接下來咱們進一步一級審批經過,二級審批經過,都是執行相同的命令:
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n fsmtest -c '{"Args":["Approve","EXP1"]}'
這個時候,狀態已是Complete了,若是咱們再次調用Approve函數會怎麼樣?由於咱們在狀態機中並無定義這麼一個流轉事件,因此確定是報錯,沒法正常執行的:
你們若是也在作這個實驗,也能夠去測試Reject函數,會獲得想要的結果的。
總的來講,在Fabric的ChainCode開發中,引入第三方的庫能夠方便咱們編寫更強大的鏈上代碼。而這個FSM雖然簡單,可是也能夠很好的將狀態流轉的邏輯進行集中,避免了在狀態流轉時編寫大量的Ugly的代碼,讓咱們在每一個函數中更專一於業務邏輯,而不是麻煩的狀態轉移。最後直接粘貼出個人完整ChainCode 源碼,方便你們直接使用。