在企业级应用开发中,常常会涉及到流程和状态,而有限状态机(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 源码,方便你们直接使用。