原文地址:https://docs.corda.net/upgrading-cordapps.html#flow-versioning
任何初始化其他 flows 的 flow 必須要使用 @InitiatingFlow 注解,像下邊這樣定義:
annotation class InitiatingFlow(val version: Int = 1)
version 屬性默認(rèn)值為1,定義了 flow 的版本。當(dāng)flow 有任何一個(gè)新的 release 的時(shí)候并且這個(gè) release 包含的變動(dòng)是非向下兼容的,這個(gè)數(shù)值應(yīng)該增加。一個(gè)非向下兼容的改動(dòng)是一個(gè)改變了 flow 的接口的變動(dòng)。
Flow 的接口是如何定義的?
Flow 的接口是通過(guò)在 InitiatingFlow 和 InitiatedBy flow 之間有序的 send 和 receive 調(diào)用來(lái)定義的,包括發(fā)送和接受的數(shù)據(jù)的類型。我們可以將 flow 的接口如下圖這樣表示:

在上邊的圖中,
InitiatingFlow:
- 發(fā)送了一個(gè)
Int - 接收了一個(gè)
String - 發(fā)送了一個(gè)
String - 接收了一個(gè)
CustomType
InitiatedByflow 恰恰相反: - 接收了一個(gè)
Int - 發(fā)送了一個(gè)
String - 接收了一個(gè)
String - 發(fā)送了一個(gè)
CustomType
只要IntiatingFlow和InitiatedByflows 遵循這個(gè)有序的一系列的動(dòng)作,那么 flows 就可以按照任何你覺(jué)得合適的方式來(lái)實(shí)現(xiàn)(包括添加不共享給其他節(jié)點(diǎn)的業(yè)務(wù)邏輯)。
哪些是非向下兼容的改動(dòng)?
Flow 可以有兩種主要的方式會(huì)變?yōu)榉窍蛳录嫒莸模?/p>
-
send和receive調(diào)用的順序變化:- 一個(gè)
send或者receive從InitiatingFlow或者InitiatedByflow 中被添加或者刪除了 -
send和receive調(diào)用的順序變了
- 一個(gè)
-
send和receive調(diào)用的類型變了
當(dāng)運(yùn)行不兼容版本的 flows 會(huì)發(fā)生什么?
帶有非兼容接口的 InitiatingFlow 和 InitiatedBy flows 可能會(huì)出現(xiàn)下邊的行為:
- flows 會(huì)沒(méi)有明確原因地停住了并且永遠(yuǎn)也不會(huì)終止,通常是因?yàn)橐粋€(gè) flow 在等待這著一個(gè)回復(fù),但是這個(gè)回復(fù)永遠(yuǎn)不會(huì)從另一方返回來(lái)
- 其中的一個(gè) flow 會(huì)帶有異常地結(jié)束:“Expected Type X but Received Type Y”,因?yàn)?
send或者receive類型不正確 - 其中的一個(gè) flow 會(huì)帶有異常地結(jié)束:“Counterparty flow terminated early on the other side”,因?yàn)橐粋€(gè) flow 向另外一個(gè) flow 發(fā)送了一些數(shù)據(jù),但是后邊這個(gè) flow 已經(jīng)結(jié)束了
我應(yīng)該如何升級(jí)我的 flows?
- 更新 flow 并且測(cè)試。在
InitiatingFlow注解中增加 flow 版本號(hào)。 - 確保已經(jīng)存在的所有版本的 flow 已經(jīng)運(yùn)行完了并且沒(méi)有未結(jié)束的
SchedulableFlows在網(wǎng)絡(luò)中的任何節(jié)點(diǎn)中。這個(gè)可以通過(guò)清理節(jié)點(diǎn)的方式來(lái)實(shí)現(xiàn),接下來(lái)會(huì)講到。 - 關(guān)閉節(jié)點(diǎn)
- 用包含新的 flow 的 CorDapp JAR 文件替換掉原來(lái)的 CorDapp JAR
- 啟動(dòng)節(jié)點(diǎn)
如果你關(guān)掉了所有的節(jié)點(diǎn)并且同時(shí)更新了他們的 flow 的話,可能產(chǎn)生任何的不兼容的改動(dòng)。
對(duì)于一些節(jié)點(diǎn)可能仍舊繼續(xù)運(yùn)行某個(gè) flow 的以前版本的情況,這樣你的新版本 flow 可能會(huì)跟一個(gè)舊版本進(jìn)行溝通,更新的 flows 需要具備向下兼容性。這可能是任何真正的部署中都會(huì)發(fā)生的問(wèn)題,你可能不會(huì)很容易地去在整個(gè)網(wǎng)絡(luò)中去協(xié)調(diào)推出一個(gè)新的 code。
我該如何確保 flow 的向下兼容性?
InitiatingFlow 版本號(hào)會(huì)被包含在 flow session handshake 中并且通過(guò) FlowLogic.getFlowContext 方法暴露給雙方。這個(gè)方法需要一個(gè) Party 作為輸入,然后會(huì)返回一個(gè) FlowContext 對(duì)象,這個(gè)對(duì)象描述了在對(duì)方節(jié)點(diǎn)上正在運(yùn)行的 flow。它含有一個(gè) flowVersion 的屬性,可以使用這個(gè)屬性來(lái)在不同的 flow 版本間來(lái)定制你自己的 flows,例如:
@Suspendable
override fun call() {
val otherFlowVersion = otherSession.getCounterpartyFlowInfo().flowVersion
val receivedString = if (otherFlowVersion == 1) {
otherSession.receive<Int>().unwrap { it.toString() }
} else {
otherSession.receive<String>().unwrap { it }
}
}
上邊的代碼演示了當(dāng) flow 的第一個(gè)版本期望收到一個(gè) Int,但是后續(xù)的版本變成了期望收到一個(gè) String。這個(gè) flow 在跟其他仍然運(yùn)行著包含舊的 flow 的舊的 CorDapp 之間還是能夠進(jìn)行溝通的。
我該如何處理關(guān)于 in-lined subflows 的接口變化?
下邊是一個(gè) in-lined subflow:
@StartableByRPC
@InitiatingFlow
class FlowA(val recipient: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(FlowB(recipient))
}
}
@InitiatedBy(FlowA::class)
class FlowC(val otherSession: FlowSession) : FlowLogic() {
// Omitted.
}
// Note: No annotations. This is used as an inlined subflow.
class FlowB(val recipient: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val message = "I'm an inlined subflow, so I inherit the @InitiatingFlow's session ID and type."
initiateFlow(recipient).send(message)
}
}
In-lined subflows 是當(dāng)跟對(duì)方初始一個(gè)新的 flow session 的時(shí)候被調(diào)用的 flows。假設(shè) flow A 調(diào)用 in-lined subFlow B,B 初始了一個(gè)跟對(duì)方的會(huì)話(session)。對(duì)方使用的 FlowLogic 類型決定應(yīng)該調(diào)用哪個(gè)對(duì)應(yīng)的 flow 應(yīng)該是由 A 決定的,而不是 B。這意味著 in-lined flow 的 response logic 必須要在 InitiateBy flow 里被顯式地實(shí)現(xiàn)。這個(gè)可以通過(guò)調(diào)用一個(gè)匹配的 in-lined counter-flow,或者在對(duì)方的被初始的父的 flow 中顯式地實(shí)現(xiàn)。In-lined subflows 也會(huì)從他們的父 flow 中繼承 session IDs。
因此,一個(gè) in-lined subflow 的一個(gè)借口的改動(dòng)必須要考慮對(duì)父 flow 接口也要有一個(gè)改動(dòng)。
一個(gè) in-lined subflow 的例子是 CollectSignaturesFlow。他有一個(gè)沒(méi)有 InitiateBy 注解的 response 的 flow 叫 SignTransactionFlow。這是因?yàn)檫@兩個(gè) flows 都是 in-lined。這兩個(gè) flows 是如何彼此交流的是通過(guò)調(diào)用他們的父 flows 來(lái)定義的。
在代碼中,in-lined subflows 看起來(lái)就是一個(gè)常規(guī)的 FlowLogic 的實(shí)例,但是沒(méi)有 InitiatingFlow 或者 InitiatedBy 注解。
In-lined subflows 是沒(méi)有版本的,因?yàn)樗麄兊陌姹臼抢^承于他們的父 flow 的(InitiatingFlow 和 InitiatedBy)。
不是 InitiatingFlow 或者 InitiatedBy flow,也不是由一個(gè) InitiatingFlow 或者 InitiatedBy flow 調(diào)用的 in-lined subflows ,更新的時(shí)候可以不考慮向下兼容的問(wèn)題。這種類型的 flows 包括用來(lái)查詢 vault 的 utility flows,或者對(duì)外部系統(tǒng)進(jìn)行查詢的 flows。