Ethereum for decentralized applications

位置

2018年5月第18期技术雷达,技术象限,建议评估(最新版技术雷达已经发布,点击这里下载

标签

Blockchain, DApps, Decentralized Applications, Ethereum

目标受众

区块链产品经理、架构师、开发人员

关注问题

区块链技术起源于比特币,由于天生具备数字货币的特质,这项技术在 Fintech 领域受到广泛关注,尤其获得了金融服务业的青睐。不过,区块链技术在以太坊(Ethereum)的拓展下,已经具备开发各种应用的能力,这些部署在区块链上通常含有内部代币激励并且开源的应用被称之为去中心化应用(Decentralized Application, DApp),DApp 就像现在的应用一样,能够惠及人们生活的方方面面,同时融入区块链的独特优势。

解决方案

以太坊是一个部署和运行 DApp 的后端程序——智能合约(Smart Contracts)的去中心化平台。它提供了专门面向合约的编程语言 Solidity 和运行合约的虚拟机(EVM)实现,得益于其周边的开源生态,很多开源工具,如Truffle, Ganache, MetaMask, MyEtherWallet 也让编写和部署智能合约变得更加方便。同时,以太坊还维护了多条测试链,如:Ropsten, Kovan 和 Rinbkey 辅助开发者测试合约,从而减少部署到主网的风险。

解读

以太坊的强大之处在于它不仅内置了可用于转账的以太币,还围绕以太币构建了部署和运行智能合约的去中心化平台。智能合约就是一个“高度权威”的中间机构,任何人都可以利用智能合约定下“如果…那么…”的交易条款,然后把交易中的钱财用以太币的形式存入其中。一旦预设条件满足,合约就会自动执行,比如:把合约中的以太币打给交易的某一方。

有了智能合约,我们甚至可以在一个没有淘宝这种电商平台的情况下,和陌生的个体商户做买卖!

商家发布了一个买卖合约。合约里说(详细见下图):

  1. 商家有一件商品价值1块钱,顾客想要购买,需要往合约里存入1块钱
  2. 商家确认合约里有1块钱之后就会发货
  3. 然后顾客确认收货之后,合约自动把这1块钱打给商家

如果你以为这就能达成交易,就too young too simple。 因为客户往合约里存入1块钱之后,如果商家没有发货,那么合约中规定的流程就没法继续下去,顾客也没法从合约中取出这1块钱。所以只要顾客不傻,他就不会打进去这1块钱,这次交易不可能完成。

商家可以这样改良买卖合约。合约里说(详细见下图):

  1. 商家先往合约里存入1块钱,证明自己有价值1块钱的商品
  2. 顾客想要购买,需要得往合约里存入2块钱(想想为什么不能是1块钱?)
  3. 商家确认合约里有3块钱之后就会发货
  4. 然后顾客确认收货之后,合约自动把3块钱中的2块钱打给商家
  5. 合约剩余的1块钱还给顾客

通过这个例子,我们很惊奇地发现,在智能合约的辅助下,两个陌生人在没有中间人担保的情况下也可以完成一笔买卖的。

相关Blip

延展阅读

支持工具

Share

区块链在汽车金融行业的应用

[摘要]

本文从区块链在汽车金融行业的应用场景出发,对企业在寻找和确定应用场景时,应该如何进行价值和可行性的分析作出了说明,通过案例分享了ThoughtWorks区块链应用的落地实施经验,希望给其他领域的区块链应用提供方法上的参考和借鉴。

1. 区块链是什么

关于区块链技术的讨论文章已经足够多并且足够深入了,我们可能已经熟知它的各种特性——不可篡改、透明、可追溯、去中心化等等;我们也经常听到对于区块链各种共识机制的讨论——PoW、PoS、DPoS、BFT等等;我们甚至还知道它的不同应用——分布式账本、智能合约、Dapp等等;这些对于我们全面了解区块链技术都非常有帮助。当我们来到具体的商业场景中,想要应用区块链技术时,我们只需要关注区块链技术的两个核心功能就够了:记录保存和交易。

记录保存是一种静态信息的存储,比如在溯源、知识产权确权和专利保护等领域,就是应用区块链技术记录了公开透明且不可篡改的静态信息,保存在不同节点上以进行公开验证。记录保存的功能可以不需要代币实现。

而交易是动态的,例如跨境支付中,应用区块链提升点对点支付的效率,就是应用了交易功能。交易的场景一般需要代币来实现,如果没有代币,那就是资产对资产的交易(代币其实就是一种货币资产),区块链会对所有发生的交易行为进行动态登记。理论上,通过区块链的交易功能能够实现交易即账本,保证交易的有效性和安全性,同时还能免除第三方的背书见证。

2. 区块链的应用

2018年5月,随着工信部正式推出《2018 年中国区块链产业白皮书》,报告务实而系统地分析了中国区块链产业的现状,解析了中国当前区块链产业的生态,并且对区块链的发展趋势给出了坚定乐观的展望。下图勾勒出了目前应用区块链的产业生态(不包括数字货币),可以看出不论是金融领域还是实体经济领域,从底层的服务平台到不同领域的应用场景都已经形成了比较全面的覆盖,昭显出当前区块链技术发展的生机和前景。

(图1 来自工信部《2018 年中国区块链产业白皮书》)

关于数字货币和颠覆式创新的“通证经济(token economy,也有译为代币经济)”,从长期来看,商业模式上是可行的,但是需要人们对代币(token)的认知和心态产生改变,需要法律和监管政策的成熟完善,同时商业模式的过渡和转变也需要时间,因此短期来看,我们更倾向于认为,现阶段,企业应用区块链还是应该导向清晰可落地的价值,通过解决当前的业务痛点持续创造价值,才能进一步推动区块链技术的成熟和普及。

金融领域是当前区块链应用最有价值的场景之一,这一点已经取得广泛共识,在此不作过多论述。我们今天想要探讨的是,当我们拿起区块链这把锤子,来到某个具体的行业领域里,比如金融领域里某个非常细分的子领域——汽车金融领域,我们该怎么利用区块链这项技术,解决现有商业/社会环境下的问题和痛点,传递价值。

3 汽车金融行业

随着经济的发展和金融服务的日益成熟,虽然是细分金融领域,但是汽车金融涵盖的场景也已经非常多样化,领域内的参与主体竞争日渐激烈。下图是汽车金融产业内金融产品的简单概览。

(图2 源自华兴资本和逐鹿资本的报告《中国汽车金融研究(2017)》,略作修改)

不同的参与主体的商业模式不同,业务痛点必然也不尽相同,无法一概而论。为了让场景更加清晰,我们选取汽车金融公司作为应用场景来展开分析,这是因为:

  1. 在现在的汽车金融领域,汽车金融公司的发展势头最为迅猛,已经逐渐成为新车市场最主要的金融服务提供商;
  2. 相较于银行、P2P公司等提供通用金融服务的主体,汽车金融公司的业务模式更为专注和单一;
  3. 汽车金融公司拥有独特的优势可以覆盖汽车的全生命周期,可以拓展更多元的应用场景。

汽车金融公司的业务场景

(图3 汽车金融公司的业务场景)

以汽车的生命周期来看:从零配件的生产运输(还可以往上游延伸广阔的供应链金融场景),到主机厂制造整车,然后通过各个区域的销售公司,将整车卖给各区域内下沉的经销商,经销商还可以分为不同层级的二三级经销商,最后才卖到顾客手中。一旦新车完成销售,就迈入了后市场的广阔天地,其中包括二手车再销售、汽车租赁等等交易场景。

(图4 汽车金融公司的业务模式)

汽车金融公司的业务模式比较简单清晰,参考上图,1、2、3是汽车的批发,4、5是汽车的零售。金融公司参与给授信经销商提供贷款进行车辆的批发交易,零售的过程中,金融公司又继续给消费者提供消费金融贷款或融资租赁等服务,缓解用户购车的资金压力,促进汽车销售。因为两次贷款交易,我们可以看到这两条方向相反的资金线,财务上我们管这个叫轧差,也就是债务的互相抵消,而这两次贷款行为的轧差让金融公司以较低的净现金赚取了批发环节和零售环节的两次利息收入。

针对汽车金融公司的应用场景,我们可以简单总结业务痛点:

第一,提升资金利用率是业务关键。金融服务商以提供资金融通服务进行盈利,汽车金融公司的资金很大一部分是来源于汽车集团的财务公司,财务公司需要对现金流进出进行精准预测,以提高资金的利用率。传统的财务记账方式,无法实时透明地彰显资金的实时利用情况:比如有多少现金流即将产生,有多少资金出现了低效的浪费(重复贷款),造成了多少潜在损失(坏账)等等。

第二,财务对账主体数量较多,且效率不高。仅在中国区域内可能就有多家销售公司和金融公司,以及几百家经销商。从会计和审计角度,即使每家公司只有两名财务和审计人员,那么财务审计人员都超过一千名,更别提全球范围内了。

第三,信任主体的审核门槛较高。因为金融贷款要控制风险需要信贷审核,而金融服务机构的信审资源有限,审核流程繁杂且周期较长,经销商的销售网络又比较混乱,因此中小型经销商很容易成为“照顾不过来”的对象,造成经销商融资困难,同时也导致汽车金融公司的业务扩张受限。

区块链切入场景分析

如果从上一节的汽车金融业务痛点继续往下分析,我们从汽车金融交易的场景中可以轻易找出:供应链金融、消费金融、征信、财务审计等应用场景。

可是对于汽车厂商而言,金融并不是唯一的切入点,如果我们从车的视角出发,在车的使用场景上我们还可以发现以下场景同样可以用区块链解决信任成本问题:叫车出行、汽车租赁和共享、为二手车交易提供车史记录、零配件溯源、跟IoT和Big data结合的汽车驾驶行为记录、跟自动驾驶AI结合的智能出行等等。

于是现在很多企业应用区块链都面临这个问题:可用的场景太多了,怎么选择?如何确定这些场景的价值高低和优先级呢?我们可以从价值和可行性两个维度来对场景进行划分,然后选取高价值、高可行性的场景进行优先落地尝试。

(图5 区块链应用场景优先级区分)

随之而来的问题是,价值和可行性要如何度量?对于特定行业和场景,如果已经存在清晰的价值衡量标准和可行性度量方式,可以直接沿用。这里我们也简单介绍一套麦肯锡提供的通用分析框架,用来对多个场景进行比较分析。

我们从以下4个维度来分析价值,相信大家对自己行业的价值分析都不陌生,因此不做过多展开:

  • 收入:可以带来收入多大程度的提升?
  • 成本:可以带来成本多大程度的降低?
  • 资本:具备多大程度的资本效应?
  • 社会:带来多大程度的社会价值?

(图6 区块链的应用场景优先级 – 价值维度)

对于区块链技术可行性的判断,我们从以下四个维度略作说明:

  • 资产:资产数字化的可行性
  • 技术:当前技术可行性
  • 标准和监管:面临多大程度的标准化和监管,标准化和监管要求越高,可行性越低
  • 生态:是否可以帮助构建生态圈

(图7 区块链的应用场景优先级 – 可行性维度)

资产

资产类型决定了该项资产是否能够通过区块链来提升记账或交易的可行性,这里衡量的关键因素是资产的数字化潜力,比如原本就是数字化记录和交易的股票类资产,就可以轻松在区块链系统上实现端到端的管理,或是通过API与现有系统集成;像黄金这样的实物类金融资产就难以用数字化的身份来进行登记和转移。我们曾接触过一个木材行业的溯源项目,希望能用区块链实现行业内从家具到木材再到原木的溯源和证明,技术上第一个难以迈过去的关卡就是如何给树木一个唯一真实且不可篡改的数字化身份。

从某种程度上来说,货币确实是技术上最容易实现的上链资产。

实物资产上链是现在很热门的研究领域,通过IoT(物联网)技术和生物识别等技术将区块链的交易网络延伸至物理世界,这一步研究至关重要,但同时也有了更多的技术依赖。比如要如何保证通过传感器等物联网设备获得的数据在上链之前不被篡改?因此,即使能够很好地实现数字化,实物类资产的上链的可行性整体比数字资产更低一些。另外,还有一个经常与资产上链相提并论的研究领域,就是数字身份的建立。这对于数字资产的确权至关重要,只有当这两头并进延伸到现实中来,以区块链技术作为价值底座的数字世界才具备规模化扩张的基础。

技术

区块链技术的发展目前还处于起步阶段,我们甚至可以认为商业变革走在了技术发展的前面,这有利也有弊。好处在于,追逐商业变革的资本带来的资源投入,能够促进区块链技术的发展和突破;而硬币的另一面则是,追逐短期收益的资本,严重局限了资源在区块链行业内的分配,炒高了技术成本,甚至形成劣币驱逐良币的行业氛围。

区块链技术发展目前最大的挑战就是下面这个“不可能三角”:

(图8 区块链的“不可能三角”)

面对不同的场景,我们需要在这三者之间进行配置和平衡,同时还可以对上链数据进行适当的裁剪,以确保交易的效率和性能。不过我们相信,随着技术不断发展,这些约束将会越来越少。

通常来说,去中心(或者一定程度上去中心)的公链,在保证安全的前提下,只能牺牲性能,这也是比特币、以太坊现在的交易瓶颈所在。而对于支撑商业场景的联盟链来说,在去中心这个角上受到的约束较小,因此通过合适的配置,可以使得区块链在商业应用上变得可行。

当前各个国家对区块链的政策不同,所以不同地区对区块链技术的发展重心也不一样。国内政策倾向于联盟链的发展,腾讯、阿里、京东等领军企业目前也正专注于研发给企业提供的BaaS(区块链即服务)服务,从工信部的白皮书也可以看出大量创业公司正在进行各行各业联盟链的搭建,相信在短期内,联盟链的发展会比公链更快速有效。因此,在判断技术可行性时,底层链平台的成熟度是非常重要的衡量标准。

标准和监管

缺乏共同标准和明确的法律规定很大程度上限制了区块链应用能力的扩张。不过,现在不论是政府还是各个行业间组建的联盟都已经认识到这个问题,并且正在着手解决。例如,今年九月,最高人民法院发布的新法规中明确说明,在能够证明使用技术合法性的前提下,中国互联网法院承认区块链作为存储和认证数字证据的合法性。而如果政府机构可以授权法律地位,那么标准可以相对容易地建立起来。 例如,政府可以让区块链记录的土地登记成为合法的记录。

当需要多个参与者之间共同合作时,这个标准的建立就变得比较复杂,但也更加重要。比如 R3 联盟与全球70多家银行合作开发的金融开源区块链平台Corda,这些平台可以建立起特定行业内区块链系统所需的通用标准。

对于缺乏标准和监管的行业,可行性衡量的是建立标准的难度。而对于某些已经存在严格固定的标准和监管的行业,在这一点上面临的挑战可能会更大。比如会计和审计行业,早已存在清晰的会计和法律要求,我们都知道区块链对于审计行业的颠覆性影响,可是在法律对区块链账本审计有明确的规定和认可之前,企业必须维持原来的财务系统不变,那么投入在区块链财务记账上的成本很长时间内都不会取得任何回报,这对于企业来说也就更难推广,当然,会计师事务所不在此列,相反,他们是现在最积极推动区块链技术的企业之一。

生态

区块链的主要优势在于网络效应,但是随着网络规模的增加,其潜在收益会增加,协调的复杂性也会随之增加。例如,用于数字媒体、许可证和版税支付的区块链解决方案,就需要在数字内容的各个生产者和消费者之间进行大量的协调。如何构建竞争者和合作者之间的竞合关系,构建一个完整的价值激励的闭环,共同促进整个生态的发展,是衡量生态可行性的标准。如果竞合关系越复杂,那么构建生态的不确定因素就越多,可行性就越低。

应用这样的分析框架,我们就可以对不同场景的价值和可行性有一个相对清楚的认知。这套框架对于大多数追求经济价值的行业都是适用的,比如这里我们就可以筛选出供应链金融、征信和二手车交易三个场景进行优先尝试。

(图9 区块链应用高优先级的切入场景)

在此,我们建议企业在进行区块链应用的尝试时:

  1. 从降低成本、提高收益或者是解决信任问题的角度出发;
  2. 进行价值-可行性分析,对价值产生的周期形成清晰的认知;
  3. 快速验证,快速反馈,尽量缩短价值产生到获得回报的周期。

4 落地实施

当我们找出了合适的场景,开始着手进行区块链的尝试,我们面临的问题更大可能是在原有的业务上增加新的技术支持(如果商业模式不变的前提下)。对于怎么实施落地区块链,我们选取一个项目案例来作为落地实施的方法说明。

确定场景和参与主体

以我们在汽车金融领域实施的一个项目为例,场景是搭建横跨多品牌的汽车销售网络的联盟链,涵盖汽车从主机厂制造出厂到完成最终销售。汽车销售是交易行为,直接用区块链的交易功能实现汽车所有权/占用权的转移在流程体验上更为顺滑自然,但是现阶段想要推广落地则会面临政策上的风险,因此我们将实施分为两步:

第一步建链,主要包括:

  • 实现汽车资产上链
  • 交易节点的登记和身份确认
  • 记录汽车交易过程中产生的债
  • 集成银行系统,模拟交易主体之间的现金交易来更新债的信息

第二步则是等到可合法流通的电子货币产生后,再接入联盟链直接完成交易。

场景确认之后需要确定参与主体,也就是联盟链第一步需要部署的节点。这更多是一个业务问题,比如在我们这个案例中,我们需要梳理出所有参与汽车销售的主体,包括:不同品牌的主机厂、各区域销售公司、经销商、金融公司,考虑到汽车的运输,还可能会加上海运和物流公司。

上链数据识别

确认了参与主体,接下来我们要确认将哪些数据上链。对于汽车的销售来说,我们需要分析清楚的问题是车在什么时候转移,车在什么主体之间用什么样的规则转移,车在转移的过程中伴随了什么数据的变化。在分析这块业务的时候,我们尝试了事件风暴,分析了在各个法律参与实体之间发生车转移的业务事件,然后进行了事件排序,通过事件析出数据,包括交易参与方,车的详细信息,车的所有权和占用权以及债等等。

(图10 上链数据识别)

智能合约的设计

智能合约的概念最早在1994年由尼克萨博提出,是一种旨在以信息化方式传播、验证或执行合同的计算机协议。随着以太坊的流行,智能合约的概念也逐渐为大众熟知,未来随着技术的发展,围绕智能合约的想象空间将会越来越大。

智能合约是对上链数据演化过程的封装。对于复杂的金融类合约,往往涉及多种数据的同时改变,以经销商通过汽车金融公司贷款批发车为例,一个可能的合约模板是规定车辆转移的同时产生两笔债,一笔由经销商指向汽车金融公司,另一笔由汽车金融公司指向区域销售公司,以及注明还款截止日期。这个合约强制链上数据变化的同时,交易多方节点必须参与合约合法性的验证和签名。

可以看出,智能合约和现实场景下的买卖合约有很强的相似性。因此,业务上我们所定义的“如果……,当……时,就发生……”这样的场景,就可以通过智能合约来实现。

平台架构

区块链平台一般位于底层(技术上也可以将区块链理解成另一种数据的存储方式),上层应用所产生的数据通过API传送到区块链平台层,也通过API读取区块链平台上记录的账本。设计上,我们对区块链平台也同样进行了分层:上层是rest API层,将平台的能力进行抽象暴露给业务;中间是合约层,提供合约执行各种业务;最底层是账本层,分布式账本记录每笔交易发生的事实,可追溯、不可篡改。

(图11 平台分层架构)

传统的平台,通过API的方式暴露服务从而获得价值输入,但是区块链平台的核心资产其实在最底层的账本中。基于这些交易事实和债务或者支付记录,我们可以很方便清算各个法律实体的数字资产,计算实时的债务信息,进行车辆的价值溯源,而且未来结合大数据分析和AI,更有可能打造出一个完整的供应链生态。

5. 总结

通常来说,中心化解决方案相对于区块链这种去中心化的解决方案,既便宜又高效。那么也就意味着,区块链技术必定是用于传统中心化技术方案难以真正解决的商业场景,又或者是它的发展能带来更好的社会秩序。只有这样,区块链技术才能真正产生价值。我们希望通过这篇文章,分享我们对区块链在汽车金融这样一个细分行业和场景中的经验,给同样在尝试或想要尝试应用区块链的企业提供一些参考。

参考文档:

  • 工信部,2018 年中国区块链产业白皮书
  • Brant Carson, Giulio Romanelli, Patricia Walsh, Askhat Zhumaev (2018). Blockchain beyond the hype. Mckinsey.
  • 华兴资本&逐鹿资本,中国汽车金融研究(2017)

更多精彩商业洞见,请关注微信公众号:ThoughtWorks商业洞见

Share

当我们做区块链时,我们在做什么?

长话短说,我们在建链。

区块链是什么

关于区块链是什么,网络上的解释多如牛毛。这里,我从通常需求的角度总结一下:在记录保存(身份存证)时,它是分布式账本(分布式数据库);在交易或支付(跨境支付)时,它是信任机器。虽然这两种分类方法并不正交,但是对于理解区块链的应用领域有很大的好处。

不论是分布式账本,还是信任机器,其底层的特性——不可篡改、透明、可追溯以及去中心化,最终导向的目的都只有一个,那就是信任。

区块链的可信度来自于人类对数学逻辑严密性的信任,数学理论和加密学实践可以确保链上数据和所有权的可靠程度。区块的确认基于共识算法、不可变的数据结构,再通过 Merkle Tree、Hash Pointer(哈希指针) 保证前向区块链的完整性,再加上经济、人心的博弈、理性经济人假设,共同构成一套完整的信任系统。

然而,企业间的联盟区块链有一些不同,它的信任更多地依赖于发起者品牌的背书。在这样的大环境下,联盟链的设计就变得相当灵活,比如最先腰斩的就是代币。

区块链的行业应用

在工信部最新发表的《2018 年中国区块链产业白皮书》中,区块链产业生态分成了产业应用(包含金融和实体领域),基础设施和平台(如公有链和BaaS),行业服务(如媒体)。而我们的关注点集中在产业应用当中。

金融领域由于本身数字化程度比较高,在证券化以及ABS交易所等方向都有落地案例。在实体产业当中,供应链溯源,身份存证等也多有应用。再加上区块链本身具有“信任穿透”的神奇功效,对于构建供应链金融征信体系,改善小微企业的融资困境也很有帮助。

总体来说,几乎各种产业场景都能应用区块链技术,因为这些场景里都有提升效率,降低成本,优化征信体系的诉求。

汽车金融区块链应用

汽车金融

汽车金融中的核心资产是汽车。汽车金融始终围绕车的生命周期发生金融活动。从零配件的生产,到主机厂制造整车,然后通过各个区域的销售公司将整车卖给各区域内的经销商。实际上在中国,经销商还可以分为不同层级的二三级经销商,最后才到顾客手中。而一旦新车完成销售,就迈入了后市场的广阔天地,以及二手车、三手车的再销售。

从汽车零配件的生产运输和组装到车卖给经销商,这些环节所涉及到的金融活动叫做供应链金融,而顾客通过金融活动来买车,不管是新车还是二手车,都属于消费金融的范畴。

汽车的生命周期和金融公司的参与环节:

它们的业务模式长这样:

通过分析现有业内的业务模式,我们发现:

财务对账成本高昂,且效率不高。这里的财务成本并非某家公司的财务成本,而是整个系统内的财务总成本。仅在中国区可能就有多家销售公司和金融公司,以及几百家经销商,即使每家公司只有两名财务和审计人员,那么财务审计人员都超过一千,更别提全球销售范围内了。

传统的对账方式是怎样的呢?

不同类型的机构进行在对账时,往往要从信息系统中导出电子表格,并用邮件发送。甚至需要打印表格、盖章后邮寄,对方收到后再与系统数据进行比对。

整个业务流程并不复杂,但是消耗了很多人力物力,且中心化的服务还由于对授权机制(多主体之间不太信任或者叫做弱信任)和信息安全等方面的考虑,而导致建设成本高昂,且制约了业务运行效率和用户体验的提升。区块链作为分布式账本,意味着任何机构之间互相发生债务往来的信息都是数据一致的,那么就可以近实时地进行对账。

而我们区块链要做的事情,一言以蔽之,汽车资产上链以及围绕汽车所发生的金融活动而产生的债务的记录。所以不难发现,分布式账本和信任机器在这个场景下都有涉及。

怎么建链

我们把这次建链过程大体总结为5个步骤:识别上链数据,智能合约设计,API设计,部署单元和网络拓扑架构。

  • 识别上链数据指的是识别将哪些交易记在链上;
  • 智能合约设计,指的是买卖车及其相关金融活动如何通过可编程的方式自动完成;
  • API设计,考虑如何对外暴露平台能力,同时限制控制主体;
  • 部署单元和网络部署架构属于实施范围,旨在解答分布式账本如何真正运行在企业当中。

整体技术架构是基于Corda这个分布式账本技术展开的,Corda准确来说不是区块链,而是一种受区块链启发的DLT,即分布式账本技术,它是由金融区块链联盟R3开发和维护的。

上链数据识别

要分析清楚的问题是车在什么时候转移,车在什么参与方之间转移,车在转移的过程中伴随了什么数据的变化。在分析这块业务的时候,我们通过事件风暴,分析了在各个法律参与实体之间发生车转移的业务事件,然后进行了事件排序,通过事件析出数据,包括交易参与方,车的详细信息,车的所有权和占有权以及债等等。这部分数据有一定的取舍,比如订单就不在我们的核心资产当中,所以不上链。

我们开始进行数据建模,在此之前,有必要介绍一下Corda的编程模型——State,因为它会直接影响我们后续的模型设计。Corda中核心概念之一就是State,State是分布式账本上的事实,它代表了交易参与方达成共识的结果。以IOU这个欠条为例,State其实就是欠条关键属性的集合,包含借款方,欠款方,金钱数量,还款截止日期。当欠款部分归还时,这个欠条的内容就会发生变化,变化的方式就是将老的欠条标记成历史的,进而生成包含新内容的欠条。

在我们应用场景中,核心的State就是车和债,因为Corda是运行在JVM上,开发首选语言是Kotlin,所以这里我们直接拿Kotlin中data class对车和债进行建模,而且统一继承了Corda内置的LinearState,LinearState拥有全局唯一ID,在数据演化的过程中不会发生改变。如果有人了解DDD相关概念的话,应该能自动映射到实体概念上。除此之外,Corda中还有一个核心State叫做Fungiable Asset,可以类比成值对象,例如:Cash。

State建模完成之后该怎么演化呢?这就不得不提一个UTXO的概念,UTXO全称 unspent transaction ouput,最开始是比特币网络引入的,它有很多好处,比如可以追溯到每一笔输出的源头,帮助验证是否存在双花现象,Corda一样继承了类似的好处。销售公司把车批发给经销商时,就会将所有权归属自己的车作为交易的输入,产生输出,输出中包含了所有权的变更以及债务的生成。而作为输入的车就会被标记成历史的。这笔交易本身也必须获取到交易双方的签名才能成立。

智能合约设计

上面我们聊到的都是链上的数据以及数据演化过程,不过这些过程都不是自动执行的。对于复杂的金融合约,往往会涉及到多种state的变化,这个时候就必须使用自动化的流程封装这些变化,封装这些变化的东西其实就是智能合约。还是以经销商批发车为例,一个可能的合约模板就是规定车转移的同时产生一笔债,以及对应的还款截止日期。这个合约强制state改变时,交易双方必须参与签名。

在进入智能合约实现之前,需要先了解一下Corda中flow和contract的概念。Flow是Corda中控制参与节点如何更新State的自动化流程,它对如何获取交易对手方的签名进行了封装。一个标准的flow流程包括获取链上数据,创建一笔交易,自签名之后发送到对手方进行交易验证,再签名,最终在双方的账本上分别提交事务。而Contract则是在交易验证环节提供验证所用的脚本。

在我们的应用场景中,智能合约长成这样,在flow中,先从链上取出原有车的数据,拷贝得到一个新的所有权发生转移的车以及对应一笔债;然后通过 txBuilder构建一笔交易,交易的输入是原车,而输出即是新车和债;最后就是验证和签名以及事务提交的过程。你可能已经注意到txBuilder中有个firstNotary的参数,这里提一下notary的概念,notary在corda中是一类特殊的节点,专门用于防止资产双花的问题。所以理论上,每笔交易都需要notary节点参与,并对交易进行签名。在交易验证环节中,我们定义的contract会被执行,这个contract非常简单,简单到只有一个叫做verify的纯函数。它的作用就是断言每一个state的更新是否符合要求。这种设计非常符合Trust But Verify的理念。

API设计

有了智能合约之后,我们就得考虑如何暴露平台的合约能力了。换句话说,从消费者的角度,我们该怎么利用平台提供的能力完成自己的业务。所以这里我们利用了REST API设计的思路,抽象出平台的能力作为资源呈现,定义以车为中心的URI,然后选择合适的HTTP动词,得出 REST api。

从数据上链识别,到智能合约设计,再到API设计,我们在不同层次利用Corda这个分布式账本技术。最底层的分布式账本记录每笔交易发生的事实,不可篡改可追溯;中间的智能合约层提供了合约抽象,甚至可以和现实中的合约一一对应;最上层的REST api以资源的方式呈现了平台的金融活动能力。

部署单元

这样一个汽车金融平台是怎么跑起来的呢?借助Docker,我们把一个物理部署单元打包成了一个镜像,底层是一个全功能的Corda节点,所有的智能合约和state都以jar包的方式部署在这个节点上;同时利用SpringBoot通过RPC的方式连接到Corda节点,调用智能合约,对外暴露REST api;而Corda节点之间则通过消息的方式互相通信。

网络拓扑

打包成docker镜像之后,就可以部署到运行环境中,形成一个分布式账本的P2P网络。这里有2个节点需要留意,最左边的 permission service 是用于对每个入网节点进行证书签发,给予每个参与实体一个身份。中间的Network map类似于微服务中的 service discovery,Corda中节点的互相发现并不是通过广播的方式发生,而是通过注册Network map获取其它节点的信息,进而找到对方。

回顾

最后,我们回顾一下上面的三层架构,用价值的视角重新评估一下整个平台。传统的平台,通过api的方式暴露服务从而获得价值输入,但是区块链平台的核心资产其实在最底层的账本中。基于这些交易事实和债务或者支付记录,我们可以很方便清算各个法律实体的数字资产,计算实时的债务信息,进行车辆的价值溯源,而且未来结合大数据分析和AI,更有可能打造出一个完整的供应链生态。


推荐阅读


更多精彩洞见,请关注微信公众号思特沃克

Share

流水线即代码

2016年11月份的技术雷达中给出了一个简明的定义:流水线即代码(Pipeline as Code)通过编码而非配置持续集成/持续交付(CI/CD)运行工具的方式定义部署流水线。其实早在2015年11月份的技术雷达当中就已经有了类似的概念:

The way to avoid programming in your CI/CD tool is to extract the complexities of the build process from the guts of the tool and into a simple script which can be invoked by a single command. This script can then be executed on any developer workstation and therefore eliminates the privileged/singular status of the build environment.

大意是将复杂的构建流程纳入一个简单的脚本文件,然后用一条命令调用。这样,任意的开发者都能在自己的工作区中执行脚本重建一套一模一样的构建环境,从而消除CI/CD环境由于散乱配置腐化而成的特异性。

这么做的原因很好理解,使用CI/CD工具是为了暴露产品代码中的问题,如果它们自身已经复杂到不稳定的地步,我们还使用它就是自找麻烦。

从某种程度上看,实施流水线即代码是不证自明的。在CI/CD的实践过程中,凡是可以被编码的东西都已经被代码化了,比如:构建、测试、数据库迁移、部署和基础设施/环境配置(Infrastruture as Code)等。说得烂俗点,流水线已经是CI/CD实践过程中的“最后一公里”,让流水线变成软件开发中的“一等公民”(即代码)是大势所趋、民心所向。

不过,这种论断毕竟欠缺说服力,我们接着从实践的痛点出发总结当前流水线遇到的问题。

实践中的痛点

我给客户搭建和配置过不少CI/CD流水线(被同事戏谑地称为“CI/CD搭建兽”),最大的痛苦莫过于每次都得从头来过,即便大部分情况下所用的工具和配置都大同小异。

其次是手工操作产生的配置漂移(configuration drift)。以Jenkins为例,暂且不谈1.0版本无法直接支持流水线这一问题,为了支持构建、测试和部署等,我们一般会先手工安装所需插件,在多个文本框中粘贴大量shell/batch脚本,下载依赖包、设置环境变量等等。

久而久之(实际上用不了多久),这台Jenkins服务器就变成无法替代(特异化)的“怪兽”了,因为没人清楚到底对它做了哪些更改,也不知道这些更改对系统产生了哪些影响,这时的Jenkins服务器就腐化成了Martin Fowler口中的雪花服务器(snowflake server)。雪花服务器有两点显著的特征:

  • 特别难以复现
  • 几乎无法理解

第一点是由于以往所做的更改并没有被记录下来,所以做过的操作都是七零八落的,没有办法复现同样的操作,也无法复制一个同样的系统。

第二点则是由于绝大部分情况下散乱的配置是没有文档描述的,哪部分重要、哪部分不重要已经无从知晓,改动的风险很大。

这些问题会在流水线的演化过程中恶化得越来越严重。

一般来讲,除非不再使用,否则流水线不会保持一成不变。具体实施过程中,考虑到项目,尤其是遗留项目当前的特点和团队成员的“产能”,我们会先将构建和部署自动化;部署节奏稳定后,开始将单元测试和代码分析自动化;接着可以指导测试人员将验收测试自动化;然后尝试将发布自动化。

在这之后,并未结束,团队还要持续优化流水线,包括CI的速度和稳定性等。换句话说,流水线的演化阶段其实是和项目的当前进展密切相关的,保证这样的对应关系有时是有必要的,比如:在多分支的版本控制下,发布分支所需流水线和主干分支会存在不同。发布分支是主干分支某个时刻分出去的,它需要在那时的流水线上才能正常工作。由于前面所说雪花服务器的特征,重建这样一条流水线并不是一件容易的事情。

如何解决

其实,流水线即代码本身已经回答了这个问题。当前实现这一概念的CI/CD工具大体遵循了两种模式:

  • 版本控制
  • DSL(领域特定语言)

对于特别难以复现、没有保证对应关系的痛点,我们就把流水线写成代码放到版本控制工具中管理起来。这样一来,每一次更改都能被记录下来,而且它会始终和此时的项目进展保持同步。

对于几乎无法理解、没有文档支持的痛点,我们就选用领域特定语言描述整条流水线。举个Jenkins2.0例子,它允许我们在项目的特定目录下放置一个Jenkinsfile的文件,内容如下:

node('master') {
   stage('Checkout') {…}
   stage('Code Analysis') {…}
   stage('Unit Test') {…}
   stage('Packing') {…}
   stage('Archive') {…}
   stage('DEV') {…}
}
stage('SIT') {
   timeout(time:4, unit:'HOURS') {
       input "Deploy to SIT?"
   }
   node('master') {…}
}
stage('Acceptance Test') {
   node('slave') {…}
}

Jenkins2.0使用Groovy实现了一套描述流水线的DSL,我们即便不了解Groovy语言,只要对流水线稍微熟悉,就能按照文档中的例子编写出符合要求的代码。

类似的工具还有Concourse.ciλCD(LambdaCD)等。 Concourse.ci使用了基于yaml的DSL,独立抽象出Resource(外部依赖,如:git repo)、Job(函数,对Resource进行get或put操作)以及Task(纯函数,必须明确定义Input/Output)模型。效果图如下:

而λCD则使用Clojure语言实现了DSL,抽象出Pipeline和Step模型,使用了Lisp特有的宏(macro)扩展和自定义普通函数,编写起来简单明了。如下:

(def pipeline-def
 `(
   (either
    manualtrigger/wait-for-manual-trigger
    wait-for-repo)

   (with-workspace
     clone
     (in-parallel
      run-some-tests
      run-smokeing-tests)

     run-package
     deploy)))

上述的pipeline-def就是这条流水线的定义,极为优雅得是,它的代码和UI事实上构成了——映射的关系,简单到极致。

值得一提的是,λCD有别于其它同类型的工具,它本身就是一份用Clojure写就的微服务。换句话说,其它的工具可能需要借助基础设施即代码完成自身的安装,但λCD不用,它完全可以采用其它微服务的部署方式,比如用λCD部署它自己,类似于编译器的自举(bootstraping)。这个时候,我们就需要两套λCD服务,一套用于部署λCD自身,另一套部署开发中的工程。

小结

流水线即代码是个新概念,也就意味着我们还需要花时间去探索与之相关的实践,比如,调试和测试(既然是代码就需要测试)。一旦有了这些实践,我们就可以把流水线本身作为产品放到流水线上运作起来,那时将会看到一种很好玩的现象——旧的流水线会构建并部署新流水线,发生上文所说自举(bootstraping)现象,这也表明流水线是不断进化的。

此外,当流水线成为代码,它在最终的交付物中必然占据一席之地,其潜在的价值还等待我们挖掘,至少从精益的角度,流水线能做的事情还有很多。


更多精彩洞见,请关注微信公众号:思特沃克

Share

改善单元测试的新方法

我们为什么要写单元测试?

“满足需求”是所有软件存在的必要条件,单元测试一定是为它服务的。从这一点出发,我们可以总结出写单元测试的两个动机:驱动(如:TDD)和验证功能实现。另外,软件需求“易变”的特征决定了修改代码成为必然,在这种情况下,单元测试能保护已有的功能不被破坏。

基于以上两点共识,我们看看传统的单元测试有什么特征?

基于用例的测试(By Example)

单元测试最常见的套路就是Given、When、Then三部曲。

  • Given:初始状态或前置条件
  • When:行为发生
  • Then:断言结果

编写时,我们会精心准备(Given)一组输入数据,然后在调用行为后,断言返回的结果与预期相符。这种基于用例的测试方式在开发(包括TDD)过程中十分好用。因为它清晰地定义了输入输出,而且大部分情况下体量都很小、容易理解。

但这样的测试方式也有坏处。

  • 第一点在于测试的意图。用例太过具体,我们就很容易忽略自己的测试意图。比如我曾经看过有人在写计算器kata程序的时候,将其中的一个测试命名为“return 3 when add 1 and 2”,这样的命名其实掩盖了测试用例背后的真实意图——传入两个整型参数,调用add方法之后得到的结果应该是两者之和。我们常说测试即文档,既然是文档就应该明确描述待测方法的行为,而不是陈述一个例子。
  • 第二点在于测试完备性。因为省事省心并且回报率高,我们更乐于写happy path的代码。尽管出于职业道德,我们也会找一个明显的异常路径进行测试,不过这还远远不够。

LC_Times_TheMathsOfWire_VF

为了辅助单元测试改善这两点。我这里介绍另一种测试方式——生成式测试(Generative Testing,也称Property-Based Testing)。这种测试方式会基于输入假设输出,并且生成许多可能的数据来验证假设的正确性。

生成式测试

对于第一个问题,我们换种思路思考一下。假设我们不写具体的测试用例,而是直接描述意图,那么问题也就迎刃而解了。想法很美好,但如何实践Given、When、Then呢?答案是让程序自动生成入参并验证结果。这也就引出“生成式测试”的概念——我们先声明传入数据可能的情况,然后使用生成器生成符合入参情况的数据,调用待测方法,最后进行验证。

Given阶段

Clojure 1.9(Alpha)新内置的Clojure.spec可以很轻松地做到这点:

;; 定义输入参数的可能情况:两个整型参数
(s/def ::add-operators (s/cat :a int? :b int?))
;; 尝试生成数据
(gen/generate (s/gen ::add-operators))
;; 生成的数据
-> (1 -122)

首先,我们尝试声明两个参数可能出现的情况或者称为规格(specification),即参数a和b都是整数。然后调用生成器产生一对整数。整个分析和构造的过程中,都没有涉及具体的数据,这样会强制我们揣摩输入数据可能的模样,而且也能避免测试意图被掩盖掉——正如前面所说,return 3 when add 1 and 2并不代表什么,return the sum of two integers才具有普遍意义。

Then阶段

数据是生成了,待测方法也可以调用,但是Then这个断言阶段又让人头疼了,因为我们根本没法预知生成的数据,也就无法知道正确的结果,怎么断言?

拿定义好的加法运算为例:

(defn add [a b]
 (+ a b))

我们尝试把断言改成一个全称命题: 任取两个整数a、b,a和b加起来的结果总是a、b之和。 借助test.check,我们在Clojure可以这样表达:

(def test-add
(prop/for-all [a (gen/int)
              b (gen/int)]
             (= (add a b) (+ a b))))

不过,我们把add方法的实现(+ a b)写到了断言里,这几乎丧失了单元测试的基本意义。换一种断言方式,我们使用加法的逆运算进行描述: 任取两个整数,把a和b加起来的结果减去a总会得到b。

(def test-add
(prop/for-all [a (gen/int)
            b (gen/int)]
           (= (- (add a b) a) b))))

我们通过程序陈述了一个已知的真命题。变换以后,就可以使用quick-check对多组生成的整数进行测试。

;; 随机生成100组数据测试add方法
(tc/quick-check 100 test-add)


;; 测试结果
-> {:result true, :num-tests 100, :seed 1477285296502}

测试结果表明,刚才运行了100组测试,并且都通过了。理论上,程序可以生成无数的测试数据来验证add方法的正确性。即便不能穷尽,我们也获得一组统计上的数字,而不仅仅是几个纯手工挑选的用例。

至于第二个问题,首先得明确测试是无法做到完备的。很多指导方法保证使用较少的用例做到有效覆盖,比如:等价类、边界值、判定表、因果图、pairwise等等。但是在实际使用过程当中,依然存在问题。举个例子,假如我们有一个接收自然数并直接返回这个参数的方法identity-nat,那么对于输入参数而言,全体自然数都互为等价类,其中的一个有效等价类可以是自然数1;假定入参被限定在整数范围,我们很容易找到一个无效等价类,比如-1。 用Clojure测试代码表现出来:

(deftest test-with-identity-nat
 (testing "identity of natural integers"
   (is (= 1 (identity-nat 1))))
 (testing "throw exception for non-natural integers"
(is (thrown? RuntimeException (identity-nat -1)))))

不过如果有人修改了方法identity-nat的实现,单独处理入参为0的情况,这个测试还是能够照常通过。也就是说,实现发生改变,基于等价类的测试有可能起不到防护作用。当然你完全可以反驳:规则改变导致等价类也需要重新定义。道理确实如此,但是反过来想想,我们写测试的目的不正是构建一张安全网吗?我们信任测试能在代码变动时给予警告,但此处它失信了,这就尴尬了。

如果使用生成式测试,我们规定:

任取一个自然数a,在其上调用identity-nat的结果总是返回a。

(def test-identity-nat
(prop/for-all [a (s/gen nat-int?)]
           (= a (identity-nat a))))


(tc/quick-check 100 test-identity-nat)


-> {:result false, 
:seed 1477362396044, 
:failing-size 0, 
:num-tests 1, 
:fail [0], 
:shrunk {:total-nodes-visited 0,
    :depth 0,
    :result false,
    :smallest [0]}}

这个测试尝试对100组生成的自然数(nat-int?)进行测试,但首次运行就发现代码发生过变动。失败的数据是0,而且还给出了最小失败集[0]。拿着这个最小失败集,我们就可以快速地重现失败用例,从而修正。

当然也存在这样的可能:在一次运行中,我们的测试无法发现失败的用例。但是,如果100个测试用例都通过了,至少表明我们程序对于100个随机的自然数都是正确的,和基于用例的测试相比,这就如同编织出一道更加紧密的安全网——网孔越小,漏掉的情况也越少。

Clojure语言之父Rich Hickey推崇Simple Made Easy哲学,受其影响生成式测试在Clojure.spec中有更为简约的表达。以上述为例:

(s/fdef identity-nat
   :args (s/cat :a nat-int?) ; 输入参数的规格
   :ret nat-int?             ; 返回结果的规格
   :fn #(= (:ret %) (-> % :args :a))) ; 入参和出参之间的约束


(stest/check `identity-nat)

fdef宏定义了方法identity-nat的规格,默认情况下会基于参数的规格生成1000组数据进行生成式测试。除了这一好处,它还提供部分类型检查的功能。

再谈TDD2-gwt

TDD(测试驱动开发)是一种驱动代码实现和设计的过程。我们说要先有测试,再去实现;保证实现功能的前提下,重构代码以达到较好的设计。整个过程就好比演绎推理,测试就是其中的证明步骤,而最终实现的功能则是证明的结果。

对于开发人员而言,基于用例的测试方式是友好的,因为它能简单直接地表达实现的功能并保证其正确性。一旦进入红、绿、重构的节(guai)奏(quan),开发人员根本停不下来,仿佛遁入一种心流状态。只不过问题是,基于用例驱动出来的实现可能并不是恰好通过的。我们常常会发现,在写完上组测试用例的实现之后,无需任何改动,下组测试照常能运行通过。换句话说,实现代码可能做了多余的事情而我们却浑然不知。在这种情况下,我们可以利用生成式测试准备大量符合规格的数据探测程序,以此检查程序的健壮性,让缺陷无处遁形。

凡是想到的情况都能测试,但是想不到情况也需要测试,这才是生成式测试的价值所在。有人把TDD概念化为“展示你的功能”(Show your work),而把生成式测试归纳为“检查你的功能“(Check your work),我深以为然。

小结

回到我们写单元测试的动机上:

1、驱动和验证功能实现;

2、保护已有的功能不被破坏。

基于用例的单元测试和生成式测试在这两点上是相辅相成的。我们可以借助它们尽可能早地发现更多的缺陷,避免它们逃逸到生产环境。 ThoughtWorks 2016年11月份的技术雷达把Clojure.spec移到了工具象限的评估环中,这表明值得我们对它作一番探究。

3-tech-radar

Clojure.spec是Clojure内置的一个新特性,它允许开发人员将数据结构用类型和其他验证条件(例如允许的取值范围)进行封装。这种数据结构一旦建立,Clojure就能利用这种规格来为程序员提供大量的便利:自动生成的测试代码、合法性验证、析构数据结构等等。Clojure.spec提供方法很有前景,它可以让开发者在需要的时候,就能从类型和取值范围中获益。

另外,除了Clojure,其它语言也有相应的生成式测试的框架,你不妨在自己的项目中试一试。

 

更多精彩洞见,请关注微信公众号:ThoughtWorks

Share