R3 Corda: 升级 CorDapp(非平台版本升级)- Flow 版本

原文地址:https://docs.corda.net/upgrading-cordapps.html#flow-versioning

任何初始化其他 flows 的 flow 必须要使用 @InitiatingFlow 注解,像下边这样定义:

annotation class InitiatingFlow(val version: Int = 1)

version 属性默认值为1,定义了 flow 的版本。当flow 有任何一个新的 release 的时候并且这个 release 包含的变动是非向下兼容的,这个数值应该增加。一个非向下兼容的改动是一个改变了 flow 的接口的变动。

Flow 的接口是如何定义的?

Flow 的接口是通过在 InitiatingFlowInitiatedBy flow 之间有序的 sendreceive 调用来定义的,包括发送和接受的数据的类型。我们可以将 flow 的接口如下图这样表示:

R3 Corda: 升级 CorDapp(非平台版本升级)- Flow 版本_第1张图片
Flow 接口

在上边的图中, InitiatingFlow

  • 发送了一个 Int
  • 接收了一个 String
  • 发送了一个 String
  • 接收了一个 CustomType
    InitiatedBy flow 恰恰相反:
  • 接收了一个 Int
  • 发送了一个 String
  • 接收了一个 String
  • 发送了一个 CustomType
    只要 IntiatingFlowInitiatedBy flows 遵循这个有序的一系列的动作,那么 flows 就可以按照任何你觉得合适的方式来实现(包括添加不共享给其他节点的业务逻辑)。

哪些是非向下兼容的改动?

Flow 可以有两种主要的方式会变为非向下兼容的:

  • sendreceive 调用的顺序变化:
    1. 一个 send 或者 receiveInitiatingFlow 或者 InitiatedBy flow 中被添加或者删除了
    2. sendreceive 调用的顺序变了
  • sendreceive 调用的类型变了

当运行不兼容版本的 flows 会发生什么?

带有非兼容接口的 InitiatingFlowInitiatedBy flows 可能会出现下边的行为:

  • flows 会没有明确原因地停住了并且永远也不会终止,通常是因为一个 flow 在等待这着一个回复,但是这个回复永远不会从另一方返回来
  • 其中的一个 flow 会带有异常地结束:“Expected Type X but Received Type Y”,因为 send 或者 receive 类型不正确
  • 其中的一个 flow 会带有异常地结束:“Counterparty flow terminated early on the other side”,因为一个 flow 向另外一个 flow 发送了一些数据,但是后边这个 flow 已经结束了

我应该如何升级我的 flows?

  1. 更新 flow 并且测试。在 InitiatingFlow 注解中增加 flow 版本号。
  2. 确保已经存在的所有版本的 flow 已经运行完了并且没有未结束的 SchedulableFlows 在网络中的任何节点中。这个可以通过清理节点的方式来实现,接下来会讲到。
  3. 关闭节点
  4. 用包含新的 flow 的 CorDapp JAR 文件替换掉原来的 CorDapp JAR
  5. 启动节点

如果你关掉了所有的节点并且同时更新了他们的 flow 的话,可能产生任何的不兼容的改动。

对于一些节点可能仍旧继续运行某个 flow 的以前版本的情况,这样你的新版本 flow 可能会跟一个旧版本进行沟通,更新的 flows 需要具备向下兼容性。这可能是任何真正的部署中都会发生的问题,你可能不会很容易地去在整个网络中去协调推出一个新的 code。

我该如何确保 flow 的向下兼容性?

InitiatingFlow 版本号会被包含在 flow session handshake 中并且通过 FlowLogic.getFlowContext 方法暴露给双方。这个方法需要一个 Party 作为输入,然后会返回一个 FlowContext 对象,这个对象描述了在对方节点上正在运行的 flow。它含有一个 flowVersion 的属性,可以使用这个属性来在不同的 flow 版本间来定制你自己的 flows,例如:

@Suspendable
override fun call() {
    val otherFlowVersion = otherSession.getCounterpartyFlowInfo().flowVersion
    val receivedString = if (otherFlowVersion == 1) {
        otherSession.receive().unwrap { it.toString() }
    } else {
        otherSession.receive().unwrap { it }
    }
}

上边的代码演示了当 flow 的第一个版本期望收到一个 Int,但是后续的版本变成了期望收到一个 String。这个 flow 在跟其他仍然运行着包含旧的 flow 的旧的 CorDapp 之间还是能够进行沟通的。

我该如何处理关于 in-lined subflows 的接口变化?

下边是一个 in-lined subflow:

@StartableByRPC
@InitiatingFlow
class FlowA(val recipient: Party) : FlowLogic() {
    @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() {
    @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 是当跟对方初始一个新的 flow session 的时候被调用的 flows。假设 flow A 调用 in-lined subFlow BB 初始了一个跟对方的会话(session)。对方使用的 FlowLogic 类型决定应该调用哪个对应的 flow 应该是由 A 决定的,而不是 B。这意味着 in-lined flow 的 response logic 必须要在 InitiateBy flow 里被显式地实现。这个可以通过调用一个匹配的 in-lined counter-flow,或者在对方的被初始的父的 flow 中显式地实现。In-lined subflows 也会从他们的父 flow 中继承 session IDs。

因此,一个 in-lined subflow 的一个借口的改动必须要考虑对父 flow 接口也要有一个改动。

一个 in-lined subflow 的例子是 CollectSignaturesFlow。他有一个没有 InitiateBy 注解的 response 的 flow 叫 SignTransactionFlow。这是因为这两个 flows 都是 in-lined。这两个 flows 是如何彼此交流的是通过调用他们的父 flows 来定义的。

在代码中,in-lined subflows 看起来就是一个常规的 FlowLogic 的实例,但是没有 InitiatingFlow 或者 InitiatedBy 注解。

In-lined subflows 是没有版本的,因为他们的版本是继承于他们的父 flow 的(InitiatingFlowInitiatedBy)。

不是 InitiatingFlow 或者 InitiatedBy flow,也不是由一个 InitiatingFlow 或者 InitiatedBy flow 调用的 in-lined subflows ,更新的时候可以不考虑向下兼容的问题。这种类型的 flows 包括用来查询 vault 的 utility flows,或者对外部系统进行查询的 flows。

你可能感兴趣的:(R3 Corda: 升级 CorDapp(非平台版本升级)- Flow 版本)