在Fabric ChainCode中匯入第三方包(以狀態機為例)

深藍發表於2017-09-08

在企業級應用開發中,經常會涉及到流程和狀態,而有限狀態機(FSM)則是對應的一種簡單實現,如果複雜化,就上升到Workflow和BPM了。我們在Fabric ChainCode的開發過程中,也很可能涉及到狀態機,這裡我們就舉一個例子,用FSM實現一個二級審批的狀態轉移。

我們有一個表單,員工填寫表單是可以儲存為Draft狀態,提交後變成Submitted狀態,然後在一級審批的時候,可以Approve或者Reject,同意了改為L1Approved,進入下一級審批,拒絕了那麼就以Reject狀態打回給起草人,二級審批人員也是有Approve和Reject兩個操作,同意了狀態就改為Complete,拒絕了就改為Reject。這是一個很常見的審批例子。

image

我們使用Go來開發ChainCode,那麼可以採用https://github.com/looplab/fsm 這個FSM庫。這個庫也是Fabric官方採用的狀態機庫。下面是我的操作過程:

1.新建ChainCode專案並引入fsm庫

我們新建一個專案fsmtest,並在其中建立住ChainCode檔案:main.go,然後新建vendor資料夾,將https://github.com/looplab/fsm從GitHub clone下來,並放在vendor/github.com/looplab/fsm資料夾中,最終專案個檔案結構如下:

image

2.定義FSM初始化函式

接下來開啟main.go檔案,除了編寫ChainCode所必須使用的函式外,最主要的就是編寫定義狀態機轉移的初始化函式了,我們根據前面流程圖中的流程狀態定義,我們可以寫出如下的FSM初始化函式:

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;
}

3.在ChainCode中呼叫FSM Event

接下來我們在ChainCode重定義了4個函式,

  • Draft
  • Submit
  • Approve
  • Reject
於是我們可以在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));//返回新狀態
}

4.部署並測試ChainCode

現在狀態寫完了,我們需要進行測試,我們可以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:

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"]}'

我們可以看到系統返回的結果:

image

現在狀態是Draft,然後我們試一試提交報銷單EXP1:

peer chaincode invoke -o orderer.example.com:7050  --tls true --cafile $ORDERER_CA -C mychannel -n fsmtest -c '{"Args":["Submit","EXP1"]}'

image

我們看到狀態已經改為Submitted了。接下來我們進一步一級審批通過,二級審批通過,都是執行相同的命令:

peer chaincode invoke -o orderer.example.com:7050  --tls true --cafile $ORDERER_CA -C mychannel -n fsmtest -c '{"Args":["Approve","EXP1"]}'

image

這個時候,狀態已經是Complete了,如果我們再次呼叫Approve函式會怎麼樣?因為我們在狀態機中並沒有定義這麼一個流轉事件,所以肯定是報錯,無法正常執行的:

image

大家如果也在做這個實驗,也可以去測試Reject函式,會得到想要的結果的。

5.總結

總的來說,在Fabric的ChainCode開發中,引入第三方的庫可以方便我們編寫更強大的鏈上程式碼。而這個FSM雖然簡單,但是也可以很好的將狀態流轉的邏輯進行集中,避免了在狀態流轉時編寫大量的Ugly的程式碼,讓我們在每個函式中更專注於業務邏輯,而不是麻煩的狀態轉移。最後直接貼上出我的完整ChainCode 原始碼,方便大家直接使用。

相關文章