内容


Hyperledger Composer 基础,第 2 部分

完善并部署您的区块链网络

安装开发工具、执行单元测试并将您的网络部署到 IBM Cloud

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Hyperledger Composer 基础,第 2 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:Hyperledger Composer 基础,第 2 部分

敬请期待该系列的后续内容。

本教程以第 1 部分 为基础,您在第一部分中学习了如何在 Hyperledger Composer Playground 的本地版本中建模并测试一个简单的业务网络。现在让我们更深入地探索 Hyperledger Composer,最终将您的网络模型导入到 IBM Cloud 上的 Online Playground。

(当您完成本教程的第 2 部分时,第 3 部分将向您展示如何在您的计算机上安装 Hyperledger Fabric,将业务网络部署到本地实例以及与示例网络区块链应用程序进行交互。)

您需要先安装一些开发人员工具;本教程中的视频将指导您进行安装。然后,您将更改第 1 部分中使用的样本 Perishable Goods 网络。具体来讲,您将通过向 Shipment 资产添加 GPS 读数,在船运集装箱中建模一个 IoT GPS 传感器,然后修改智能合约(链代码),以便在 Shipment 到达目标港口时发送警报。

您还将学习如何使用一个名为 Cucumber 的行为驱动开发 (BDD) 工具,对区块链网络执行单元测试。您将使用一个 Cucumber 特性文件来测试智能合约的逻辑。

最后,会将该模型导入托管在 IBM Cloud 上的 Online Playground 中,在这里可以通过 Playground UI 与该模型进行交互并提交事务,就像在第 1 部分中一样。因为 Online Playground 位于 IBM Cloud 上,所以无需安装即可使用它。

前提条件

除了这些前提条件之外,下一节还将介绍您需要安装的开发人员工具。

1

设置您的环境

在编写本文时,Composer 仅在 Ubuntu Linux 和 MacOS 上受到支持,但 Windows 支持正在开发之中

对于您需要安装的大部分工具,针对 Ubuntu Linux 和 MacOS 的安装说明几乎是相同的,我会在安装过程中指出存在的区别。(要了解如何设置您的计算机,请观看此视频。)

1a

安装 Node.js

安装 Node.js 的最简单方法是使用 nvm,也就是 Node Version Manager。顾名思义,nvm 用于管理安装在您的计算机上的 Node 版本。要安装 nvm,请按照与您的平台匹配的操作说明进行操作。

在 Ubuntu Linux 上安装 nvm

Hyperledger Composer 团队提供了一个脚本来安装 nvm。要安装 nvm,请执行以下脚本:

                curl -O https://hyperledger.github.io/composer/prereqs-ubuntu.sh
                chmod u+x prereqs-ubuntu.sh
                ./prereqs-ubuntu.sh

系统会提示您输入密码(确保您能在系统上使用 sudo)。要获得更多信息,请访问安装 Hyperledger Composer 并进行开发

该脚本运行完成后,就可以安装 Node.js 了。现在跳到“使用 nvm 安装 Node.js”小节。

在 MacOS 上安装 nvm

MacOS 提供了 git、make 和 svn 等许多流行命令行工具特有的版本。要了解是否安装了 xcode 命令行工具,请打开一个终端窗口,输入 git 并按 Enter。

图 1. 如果看到此消息,则表明您需要安装这些命令行工具
如果看到此消息,则表明您需要安装这些命令行工具
如果看到此消息,则表明您需要安装这些命令行工具

如果看到类似图 1 的消息,请单击 Install 按钮安装这些命令行工具。安装用时不到 5 分钟。

从终端运行此命令:

                curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash

备注:请查阅 nvm GitHub 存储库上的自述文件,确保您安装了最新版本(编写本文时为 0.33.6)。

该脚本会更改您的 Bash shell 设置,所以您需要退出终端窗口,然后打开一个新窗口来加载新设置。

要验证 nvm 已正确安装,可从终端窗口执行 nvm --version 命令。您将看到类似这样的输出:

                $ nvm --version
                0.33.6

现在您已准备好使用 nvm 安装 Node.js。

使用 nvm 安装 Node.js

要安装 Node.js,可转到命令行 (Ubuntu) 或打开一个终端窗口 (MacOS),运行 nvm install --lts 命令来安装 Node.js 的 LTS 版本。

nvm install --lts

您将看到类似这样的输出:

                $ nvm install --lts
                Installing latest LTS version.
                Downloading and installing node v8.9.0...
                Downloading https://nodejs.org/dist/v8.9.0/node-v8.9.0-darwin-x64.tar.gz...
                ######################################################################## 100.0%
                Computing checksum with shasum -a 256
                Checksums matched! 
                .
                .
                (LOTS MORE OUTPUT FROM THE COMPILER)
                .
                .
                Now using node v8.9.0 (npm v5.5.1)

因为要针对您的平台来编译代码(至少对于 MacOS 是这样),安装可能会花几分钟的时间,所以请耐心等待。

最后,验证 Node.js 已安装:

                $ node -v
                v8.9.0

在本例中,我安装了 Node.js 8.9.0,这是编写本文时的最新 LTS 版本。

1b

安装 Composer 命令行接口 (CLI)

您会使用该命令行接口 (CLI) 创建、部署和更新业务网络,以及执行与您的区块链网络相关的其他功能

要安装 Composer CLI,可转到命令行 (Ubuntu) 或打开终端窗口 (MacOS),输入此命令:

                npm install -g composer-cli

Npm 代表 Node Package Manager,已在 nvm 安装 Node.js 时安装。通常在通过 npm 安装 Node.js 包时,仅能在已安装它的目录树内使用它。 指定 -g 选项会告诉 npm 对该包进行全局安装,这使得它可用于计算机上的所有 Node.js 项目。

验证 composer-cli 已正确安装。从命令行 (Ubuntu) 或终端窗口 (MacOS) 运行 composer -v,版本号将作为输出出现:

                $ composer -v
                v0.15.0
1c

安装 VSCode

VSCode 是一个来自 Microsoft 的开源编辑器。它的源代码可从 Microsoft 的 VSCode GitHub 存储库免费获得。

执行 Hyperledger Composer 开发不需要安装 VSCode,但 Composer 团队推荐使用它,而且 VSCode 的确非常出色。Hyperledger Composer 有一个用于 VSCode 的扩展,您可以轻松地安装和启用它。它能够顺利集成 Git,还为 Composer 业务网络模型文件提供了语法突出显示功能。

在 Ubuntu Linux 上安装 VSCode

要在 Ubuntu(或其他基于 Debian 的 Linux)上执行安装,可选择以下选项之一:

  • .deb— Debian 包
  • .rpm—RPM Package Manager(最初称为 Red Hat Package Manager)
  • .tar.gz— Tarball

挑选您最喜欢的方法,按照 VSCode 网站上的详细安装说明进行操作。

在 MacOS 上安装 VSCode

要在 MacOS 上执行安装,请单击 Download for Mac 按钮,一个包含 VSCode 应用程序的 zip 文件将会下载到您的 Mac 上。请访问 VSCode 网站了解安装并运行 VSCode 的详细操作说明

1d

安装 Hyperledger Composer 扩展

要在编辑 Hyperledger Composer 文件时充分利用语法突出显示功能,一定要安装针对 VSCode 的 Composer 扩展。启动 VSCode,单击 UI 左侧的 Extensions 图标(或在 Mac 上按 Cmd + X)打开扩展编辑器。

在搜索字段中输入 Hyperledger,您将在 Search 字段下方的列表中看到 Hyperledger Composer 扩展,如图 2 所示。单击 Install 按钮,在完成安装后,重新启动 VSCode 来激活该扩展。

图 2. VSCode 扩展市场
VSCode 扩展市场
VSCode 扩展市场

现在,只要使用 VSCode 来编辑 Hyperledger Composer 项目文件,就会自动突出显示它的语法。图 3 展示了在 VSCode 编辑器窗口中打开的 perishable.cto 模型文件。请注意 namespaceenum 关键字的语法突出显示。

图 3. VSCode 中的 Perishable Goods 网络
VSCode 扩展市场
VSCode 扩展市场

视频:设置您的环境

此视频展示了如何设置您的环境。

设置您的环境
设置您的环境

点击查看视频演示查看抄本

2

运行构建

有了工具之后,是时候运用它们了。您将克隆我提供的 perishable-network GitHub 存储库,然后使用刚安装的 Node.js 工具来构建代码并执行单元测试。

在计算机上选择您将使用 Hyperledger Composer 和网络模型的位置。例如,我使用 ~/HyperledgerComposer 作为我的 Composer 根目录,并在我使用的 Bash shell 中设置一个环境变量:

$ export COMPOSER_ROOT=~/HyperledgerComposer

为了保持本教程与位置无关,我将此目录称为 $COMPOSER_ROOT。在下面的示例中看到 $COMPOSER_ROOT 时,指的是您选择的位置。 建议将 $COMPOSER_ROOT 设置为该位置,就像我上面所做的一样。

转到命令行 (Ubuntu) 或打开终端窗口 (MacOS),导航到您的 $COMPOSER_ROOT 目录,然后输入此命令:git clone https://github.com/makotogo/developerWorks.git,如下所示。

                $ cd $COMPOSER_ROOT
                $ pwd
                /Users/sperry/HyperledgerComposer
                $ git clone https://github.com/makotogo/developerWorks.git
                Cloning into 'developerWorks'...
                remote: Counting objects: 364, done.
                remote: Compressing objects: 100% (45/45), done.
                remote: Total 364 (delta 33), reused 78 (delta 25), pack-reused 277
                Receiving objects: 100% (364/364), 146.69 KiB | 754.00 KiB/s, done.
                Resolving deltas: 100% (162/162), done.

现在您已拥有代码。是时候构建并测试它了。

您将使用 Node.js 包管理器 (npm) 运行一次构建,然后执行我提供的单元测试。执行以下命令:

                cd $COMPOSER_ROOT/developerWorks/perishable-network
                npm install && npm test

这是发生的过程:首先您导航到 perishable-network 代码所在的目录。然后运行 npm install,这会设置本地 Node.js 环境(即 perishable-network 的本地环境)。然后运行 npm test,这将执行项目中包含的单元测试(参见 package.json)。

您将看到大量输出,但它的最后部分类似于以下内容,这表明测试成功完成:

                    .
                    .
                  Perishable Shipping Network
                    #shipment
                Adding temperature 4.5 to shipment SHIP_001
                Received at: Wed Nov 01 2017 10:58:12 GMT-0500 (CDT)
                Contract arrivalDateTime: Thu Nov 02 2017 10:58:12 GMT-0500 (CDT)
                Lowest temp reading: 4.5
                Highest temp reading: 4.5
                Payout: 2500
                Grower: farmer@email.com new balance: 2500
                Importer: supermarket@email.com new balance: -2500
                      ✓ should receive base price for a shipment within temperature range (90ms)
                Adding temperature 1 to shipment SHIP_001
                Received at: Wed Nov 01 2017 10:58:12 GMT-0500 (CDT)
                Contract arrivalDateTime: Thu Nov 02 2017 10:58:12 GMT-0500 (CDT)
                Lowest temp reading: 1
                Highest temp reading: 4.5
                Min temp penalty: 0.2
                Payout: 1500
                Grower: farmer@email.com new balance: 4000
                Importer: supermarket@email.com new balance: -4000
                      ✓ should apply penalty for min temperature violation (81ms)
                Adding temperature 11 to shipment SHIP_001
                Received at: Wed Nov 01 2017 10:58:12 GMT-0500 (CDT)
                Contract arrivalDateTime: Thu Nov 02 2017 10:58:12 GMT-0500 (CDT)
                Lowest temp reading: 1
                Highest temp reading: 11
                Min temp penalty: 0.2
                Max temp penalty: 0.30000000000000004
                Payout: 999.9999999999998
                Grower: farmer@email.com new balance: 5000
                Importer: supermarket@email.com new balance: -5000
                      ✓ should apply penalty for max temperature violation (74ms)
                
                  3 passing (1s)
3

完善 Perishable Goods 业务网络

Perishable Goods 网络将对一个业务网络进行建模,其中包括:一个种植者、一个航运商和一个进口商。具体细节都包含在 README.md 文件中。此网络中各个参与者间的协议是用 CTO 建模语言来建模的,并通过 JavaScript 编写的链代码(智能合约)执行。

在本节的这个理论练习中,已经向货物集装箱中添加了一个 IoT GPS 传感器来提供集装箱船的位置。系统会询问您是否将从此传感器获得的读数添加到网络模型,并在船舶到达目的地时发送一个事件。

要将 GPS 传感器添加到网络模型中,需要更改业务模型并编写更多单元测试。我不会使用 Mocha 编写单元测试,而是介绍如何使用一个名为 Cucumber 的工具编写它们,该工具拥有人类更容易理解的语法,而且同样非常强大。

3a

修改网络定义

通过粘贴我提供的代码来执行了本节中的更改后,您的解决方案应类似于 developerWorks/iot-perishable-network 目录中的解决方案。可以使用此模型作为参考。

要添加 GPS 传感器,您需要对自己的模型做一些更改。启动 VSCode,打开 Perishable Goods 网络的根目录 ($COMPOSER_ROOT)/developerWorks/perishable-network)。打开位于 models 目录中的名为 perishable.cto 的模型文件。

将一个表示罗盘上的主要位置的新 enum 添加到 enum ShipmentStatus 下方:

                /**
                 * Directions of the compass
                 */
                enum CompassDirection {
                  o N
                  o S
                  o E
                  o W
                }

方向中的 N 表示北方、S 表示南方,等等。限制输入来表示一组 GPS 坐标的数据很重要,这个 enum 用于限制可输入模型中的值,以确保它们有效。

每次获得一个 GPS 读数,都会将它作为一个事务记录到区块链中,这意味着您需要向模型中添加一个针对它的事务。在 TemperatureReading 事务下方添加一个新的 GpsReading 事务:

清单 1. 负责将 GPS 读数记录到区块链中的事务
                /**
                 * A GPS reading for a shipment.E.g. received from a device
                 * within a shipping container
                 */
                transaction GpsReading extends ShipmentTransaction {
                  o String readingTime
                  o String readingDate
                  o String latitude
                  o CompassDirection latitudeDir
                  o String longitude
                  o CompassDirection longitudeDir
                }

在获取 GPS 读数时需要包含一些参数,以及经纬度。此信息是作为参数提供给事务的。

接下来,要让事务能够将 GPS 读数存储在区块链中,该信息需要包含在一个区块链资产中。 因为从船运集装箱获取的 GPS 读数在概念上是一批货物的一部分,所以将该读数添加到 Shipment 资产中是合情合理的(就像 TemperatureReading 一样)。将下面突出显示的行(第 7 行)添加到 Shipment 资产中:

                asset Shipment identified by shipmentId {
                  o String shipmentId
                  o ProductType type
                  o ShipmentStatus status
                  o Long unitCount
                  o TemperatureReading[] temperatureReadings optional
                  o GpsReading[] gpsReadings optional
                  --> Contract contract
                }

最后,向模型添加两个事件:一个表示违反温度阈值,另一个表示集装箱船到达目标港口:

清单 2. 新事件 - 当集装箱中的温度超出合同规定的容忍范围时,以及当集装箱船到达港口时
                /**
                 * An event - when the temperature goes outside the agreed-upon boundaries
                 */
                event TemperatureThresholdEvent {
                  o String message
                  o Double temperature
                  --> Shipment shipment
                }
                
                /**
                 * An event - when the ship arrives at the port
                 */
                event ShipmentInPortEvent {
                  o String message
                  --> Shipment shipment
                }
3b

添加链代码

您已对 GPS 传感器和用于将 GPS 读数添加到模型的事务进行建模。现在需要编写 JavaScript 链代码来处理区块链的更新。打开 lib/logic.js。您需要对此文件执行一些更改。

首先,向 temperatureReading 函数添加代码来处理 TemperatureReading 事务。将该方法的全部内容替换为下面这个方法(为方便参考,我们突出显示了添加的行):

                function temperatureReading(temperatureReading) {
                
                    var shipment = temperatureReading.shipment;
                    var NS = "org.acme.shipping.perishable";
                    var contract = shipment.contract;
                    var factory = getFactory();
                
                    console.log('Adding temperature ' + temperatureReading.centigrade + ' to shipment ' + shipment.$identifier);
                
                    if (shipment.temperatureReadings) {
                        shipment.temperatureReadings.push(temperatureReading);
                    } else {
                        shipment.temperatureReadings = [temperatureReading];
                    }
                
                    if (temperatureReading.centigrade < contract.minTemperature ||
                        temperatureReading.centigrade > contract.maxTemperature) {
                        var temperatureEvent = factory.newEvent(NS, 'TemperatureThresholdEvent');
                        temperatureEvent.shipment = shipment;
                        temperatureEvent.temperature = temperatureReading.centigrade;
                        temperatureEvent.message = 'Temperature threshold violated! Emitting TemperatureEvent for shipment: ' + shipment.$identifier;
                        emit(temperatureEvent);
                    }
                
                    return getAssetRegistry(NS + '.Shipment')
                        .then(function (shipmentRegistry) {
                            // add the temp reading to the shipment
                            return shipmentRegistry.update(shipment);
                        });
                }

此代码参照合同规定检查了当前温度读数,如果超过了最低或最高温度限制,则会发出一个 TemperatureThresholdEvent 事件。

接下来,添加一个新函数来处理 GpsReading 事务。
备注:同时添加注释块很重要。它包含您将需要的两个重要注释(@param@transaction

                /**
                 * A GPS reading has been received for a shipment
                 * @param {org.acme.shipping.perishable.GpsReading} gpsReading - the GpsReading transaction
                 * @transaction
                 */
                function gpsReading(gpsReading) {
                
                    var factory = getFactory();
                    var NS = "org.acme.shipping.perishable";
                    var shipment = gpsReading.shipment;
                    var PORT_OF_NEW_YORK = '/LAT:40.6840N/LONG:74.0062W';
                    
                    var latLong = '/LAT:'+ gpsReading.latitude + gpsReading.latitudeDir + '/LONG:'+
                        gpsReading.longitude + gpsReading.longitudeDir;
                    
                    if (shipment.gpsReadings) {
                        shipment.gpsReadings.push(gpsReading);
                    } else {
                        shipment.gpsReadings = [gpsReading];
                    }
                
                    if (latLong == PORT_OF_NEW_YORK) {
                        var shipmentInPortEvent = factory.newEvent(NS, 'ShipmentInPortEvent');
                        shipmentInPortEvent.shipment = shipment;
                        var message = 'Shipment has reached the destination port of ' + PORT_OF_NEW_YORK;
                        shipmentInPortEvent.message = message;
                        emit(shipmentInPortEvent);
                    }
                
                    return getAssetRegistry(NS + '.Shipment')
                    .then(function (shipmentRegistry) {
                        // add the temp reading to the shipment
                        return shipmentRegistry.update(shipment);
                    });
                }

该事务的链代码将此 GPS 读数存储在 Shipment 资产中的 GpsReading 数组中。然后,它会检查此 GPS 读数是否与目标港口对应,如果是,则发出 ShipmentInPort 事件。最后,使用 Shipment 的当前状态更新区块链。

3c

添加 Cucumber 功能测试

让我们总结一下。您向模型添加了一个事务,以及两个新事件。现在是时候对更改进行单元测试,确保它们工作正常了。Hyperledger Composer 团队推荐使用 Cucumber 对 Composer 业务模型进行单元测试。

features 文件夹中创建一个名为 iot-perishable.feature 的新文件,并在 VSCode 中打开它。我将简单解释一下您需要添加的每一节。在“使用 Cucumber 执行单元测试”小节中,我将更全面地解释 Cucumber。但是首先,让我们执行一些相关工作。

首先,告诉 Cucumber 您想测试的特性,以及需要在每个单元测试之前执行的任何后台(设置)。将以下代码添加到您的空 iot-perishable.feature 文件中。

清单 3. Cucumber 特性和后台
                Feature: IoT Perishable Network
                
                    Background: 
                        Given I have deployed the business network definition ..
                        And I have added the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":0},
                        {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":0},
                        {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
                        ]
                        """
                        And I have added the following asset of type org.acme.shipping.perishable.Contract
                            | contractId | grower           | shipper               | importer           | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                            | CON_001    | grower@email.com | supermarket@email.com | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              | 
                        
                        And I have added the following asset of type org.acme.shipping.perishable.Shipment
                            | shipmentId | type    | status     | unitCount | contract |
                            | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                        When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                            | shipment | centigrade |
                            | SHIP_001 | 4          |
                            | SHIP_001 | 5          |
                            | SHIP_001 | 10         |

现在需要添加一些称为场景 (Scenario) 的单元测试。将场景添加到 iot-perishable.feature 文件中的 Background 块下方。

清单 4. 场景:当温度范围在协商的界限内时
        Scenario: When the temperature range is within the agreed-upon boundaries
            When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                | shipment |
                | SHIP_001 |
            
            Then I should have the following participants
            """
            [
            {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":2500},
            {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-2500},
            {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
            ]
            """

此场景确保在货物集装箱中的温度在协商的限制内时,为种植者支付了足额的费用。

现在添加一个场景:超出了最低温度阈值(2 摄氏度) 2 度。

清单 5. 场景:当超出低/最低温度阈值 2 摄氏度时
                Scenario: When the low/min temperature threshold is breached by 2 degrees C
                    Given I submit the following transaction of type org.acme.shipping.perishable.TemperatureReading
                        | shipment | centigrade |
                        | SHIP_001 | 0          |
            
                    When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                        | shipment |
                        | SHIP_001 |
                    
                    Then I should have the following participants
                    """
                    [
                    {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":500},
                    {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-500},
                    {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
                    ]
                    """

在此场景中,最低温度下降到 0 摄氏度,针对低于阈值的每摄氏度,向种植者收取罚金。

现在添加一个场景:超出最高温度阈值 2 摄氏度。

清单 6. 场景:当超出高/最高温度阈值 2 摄氏度时
                Scenario: When the hi/max temperature threshold is breached by 2 degrees C
                    Given I submit the following transaction of type org.acme.shipping.perishable.TemperatureReading
                        | shipment | centigrade |
                        | SHIP_001 | 12          |
            
                    When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                        | shipment |
                        | SHIP_001 |
                    
                    Then I should have the following participants
                    """
                    [
                    {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":1500},
                    {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-1500},
                    {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
                    ]
                    """

在此场景中,最高温度超过了 10 摄氏度,针对高于阈值的每摄氏度,向种植者收取罚金。

最后,向模型中添加 3 个场景(两个 TemperatureThresholdEvent 和一个 ShipmentInPortEvent),如清单 2 所示。

清单 7. 场景:事件
                Scenario: Test TemperatureThresholdEvent is emitted when the min temperature threshold is violated
                    When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                        | shipment | centigrade |
                        | SHIP_001 | 0          |
                    
                    Then I should have received the following event of type org.acme.shipping.perishable.TemperatureThresholdEvent
                        | message                                                                          | temperature | shipment |
                        | Temperature threshold violated! Emitting TemperatureEvent for shipment: SHIP_001 | 0           | SHIP_001 |
                
            
                Scenario: Test TemperatureThresholdEvent is emitted when the max temperature threshold is violated
                    When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                        | shipment | centigrade |
                        | SHIP_001 | 11         |
                    
                    Then I should have received the following event of type org.acme.shipping.perishable.TemperatureThresholdEvent
                        | message                                                                          | temperature | shipment |
                        | Temperature threshold violated! Emitting TemperatureEvent for shipment: SHIP_001 | 11          | SHIP_001 |
                
            
                Scenario: Test ShipmentInPortEvent is emitted when GpsReading indicates arrival at destination port
                    When I submit the following transaction of type org.acme.shipping.perishable.GpsReading
                        | shipment | readingTime | readingDate | latitude | latitudeDir | longitude | longitudeDir |
                        | SHIP_001 | 120000      | 20171025    | 40.6840  | N           | 74.0062   | W            |
            
                    Then I should have received the following event of type org.acme.shipping.perishable.ShipmentInPortEvent
                        | message                                                                           | shipment |
                        | Shipment has reached the destination port of /LAT:40.6840N/LONG:74.0062W | SHIP_001 |

最后,需要修改 package.json,使它看起来类似下面的清单。我突出显示了需要修改(第 18 行)和添加(第 41 和 42 行)的行。

                {
                  "engines": {
                    "composer": "^0.15.0"
                  },
                  "name": "perishable-network",
                  "version": "0.1.11",
                  "description": "Shipping Perishable Goods Business Network",
                  "networkImage": "https://github.com/makotogo/developerWorks/perishable-network/networkimage.svg",
                  "networkImageanimated": "https://github.com/makotogo/developerWorks/perishable-network/networkimageanimated.svg",
                  "scripts": {
                    "prepublish": "mkdirp ./dist && composer archive create  --sourceType dir --sourceName .-a ./dist/perishable-network.bna",
                    "pretest": "npm run lint",
                    "lint": "eslint .",
                    "postlint": "npm run licchk",
                    "licchk": "license-check",
                    "postlicchk": "npm run doc",
                    "doc": "jsdoc --pedantic --recurse -c jsdoc.json",
                    "test": "mocha -t 0 --recursive && cucumber-js",
                    "deploy": "./scripts/deploy.sh"
                  },
                  "repository": {
                    "type": "git",
                    "url": "https://github.com/makotogo/developerWorks.git"
                  },
                  "keywords": [
                    "shipping",
                    "goods",
                    "perishable",
                    "composer",
                    "composer-network",
                    "iot"
                  ],
                  "author": "Hyperledger Composer",
                  "license": "Apache-2.0",
                  "devDependencies": {
                    "browserfs": "^1.2.0",
                    "chai": "^3.5.0",
                    "composer-admin": "^0.14.0-0",
                    "composer-cli": "^0.14.0-0",
                    "composer-client": "^0.14.0-0",
                    "composer-connector-embedded": "^0.14.0-0",
                    "composer-cucumber-steps": "^0.14.0-0",
                    "cucumber": "^2.2.0",
                    "eslint": "^3.6.1",
                    "istanbul": "^0.4.5",
                    "jsdoc": "^3.4.1",
                    "license-check": "^1.1.5",
                    "mkdirp": "^0.5.1",
                    "mocha": "^3.2.0",
                    "moment": "^2.17.1"
                  },
                  "license-check-config": {
                    "src": [
                      "**/*.js",
                      "!./coverage/**/*",
                      "!./node_modules/**/*",
                      "!./out/**/*",
                      "!./scripts/**/*"
                    ],
                    "path": "header.txt",
                    "blocking": true,
                    "logInfo": false,
                    "logError": true
                  }
                }

请注意,您向 package.json 添加了两个新 Node 模块(上面的第 41 和 42 行)。要安装它们,请转到命令行 (Ubuntu) 或打开终端窗口 (MacOS),然后运行 npm install 命令。

3d

运行单元测试

现在,从命令行执行 npm test 来运行单元测试。将会产生许多输出,但这一次,除了 Mocha 测试之外,您还会看到每个 Cucumber 特性场景的一个输出块。Cucumber 功能测试的开头类似于下面这段代码(出于空间考虑,我仅展示了第一个测试)。

                Feature: IoT Perishable Network
                
                  Scenario: When the temperature range is within the agreed-upon boundaries
                  ✔ Given I have deployed the business network definition ..
                  ✔ And I have added the following participants
                      """
                      [
                      {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":0},
                      {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":0},
                      {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
                      ]
                      """
                  ✔ And I have added the following asset of type org.acme.shipping.perishable.Contract
                      | contractId | grower           | shipper               | importer              | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                      | CON_001    | grower@email.com | supermarket@email.com | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              |
                  ✔ And I have added the following asset of type org.acme.shipping.perishable.Shipment
                      | shipmentId | type    | status     | unitCount | contract |
                      | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                  ✔ When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                      | shipment | centigrade |
                      | SHIP_001 | 4          |
                      | SHIP_001 | 5          |
                      | SHIP_001 | 10         |
                  ✔ When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                      | shipment |
                      | SHIP_001 |
                  ✔ Then I should have the following participants
                      """
                      [
                      {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":2500},
                      {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-2500},
                      {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
                      ]
                      """

输出的最后几行类似于下面这段代码,这表明单元测试通过了。

                    .
                    .
                  ✔ And I have added the following asset of type org.acme.shipping.perishable.Shipment
                      | shipmentId | type    | status     | unitCount | contract |
                      | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                  ✔ When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                      | shipment | centigrade |
                      | SHIP_001 | 4          |
                      | SHIP_001 | 5          |
                      | SHIP_001 | 10         |
                  ✔ When I submit the following transaction of type org.acme.shipping.perishable.GpsReading
                      | shipment | readingTime | readingDate | latitude | latitudeDir | longitude | longitudeDir |
                      | SHIP_001 | 120000      | 20171025    | 40.6840  | N           | 74.0062   | W            |
                  ✔ Then I should have received the following event of type org.acme.shipping.perishable.ShipmentInPortEvent
                      | message                                                                  | shipment |
                      | Shipment has reached the destination port of /LAT:40.6840N/LONG:74.0062W | SHIP_001 |
                
                6 scenarios (6 passed)
                44 steps (44 passed)
                0m02.788s
                $

更深入地探索如何使用 Cucumber 执行单元测试

我知道在上一节中介绍了很多内容。让我们花点时间更详细地解释一下这些内容,首先概述 Cucumber。

Cucumber 概述

Cucumber 是一个专为行为驱动开发 (BDD) 而设计的单元测试工具,BDD 是一种与测试驱动开发 (TDD) 相关的开发风格,但更加专注于应用程序的行为,而不只是代码的功能正确性。这是一个细微但很重要的区别。

您使用了 Gherkin 语言编写的 Cucumber 测试场景。在上一节中您已看到该语言。其语法非常简单:

                Feature : The application feature I want to test
                  Background: 
                    Given some pre-condition that applies to each test
                
                  Scenario: The test I want to run
                      Given something
                      And some other thing
                      When I do XYZ
                      Then ABC is the expected outcome

步骤定义是一小段代码,它将 Gherkin 简单语言文本中的一种模式链接到运行测试的实际代码。您需要负责编写步骤定义,否则您的测试不会运行。

当 Cucumber 遇到以下关键字之一时:WhenGivenThenAndBut,它会寻找一个步骤定义来将文本与关键字后的代码相关联。顺便说一下,Cucumber 不会区分关键字;它们对 Cucumber 而言都只是步骤。换句话说,对 Cucumber 而言,When ABCGiven ABC 都表示“找到并执行具有模式 ABC 的步骤”。

例如,当 Cucumber 看到 Given I have added the following participants 时,它会寻找一个模式与 I have added the following 相匹配的步骤,如果找到一个这样的步骤,则执行它。如果未找到,您必须提供该步骤。

好消息是,Hyperledger Composer 团队已为 Composer 客户端 API 的大部分常见用法提供了步骤。这意味着,您可以使用 Gherkin 语言编写 Cucumber 测试,就像您已看到的这些测试一样,无需自行编写任何代码!该代码包含在您在上一节添加到 package.json 中的 JavaScript 库 composer-cucumber-steps 中。

使用 Cucumber

让我们来分析一个场景。我将介绍 Cucumber 的功能,并展示为来自 composer-cucumber-steps Node.js 模块的步骤运行的实际 JavaScript 代码。

参见清单 8。在此场景中,有 3 个关键字:

  • Given I submit the following transaction of type org.acme.shipping.perishable.TemperatureReading
  • When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
  • Then I should have the following participants

Cucumber 不会区分这 3 个关键字(它们只是步骤),但关键字后的模式必须与一个(且仅与一个)步骤相匹配。否则,测试不会运行,因为没有运行它的代码。

在第一个实例中,Cucumber 寻找一种与“I submit the following transaction of type org.acme.shipping.perishable.TemperatureReading”匹配的模式(即一个正则表达式),而且找到了一个这样的模式。执行这一步时运行的 JavaScript 函数看起来类似于:

清单 8. composer-cucumber-steps/lib/transactionsteps.js
                'use strict';
                
                module.exports = function () {
                
                    this.When(/^I submit the following transactions? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.submitTransactions(namespace, name, table);
                    });
                
                    this.When(/^I submit the following transactions?$/, function (docString) {
                        return this.composer.submitTransactions(null, null, docString);
                    });
                
                };

等等。this.Given() 函数在哪里?上述函数都以 this.When 开头。Cucumber 如何知道要在您的 Gherkin 包含 Given 时调用上面的一个函数?请记住,Cucumber 不会区分任何步骤关键字(Given、When、And 等)。它仅与模式进行匹配。事实上,如果您为 GivenWhen 编写一个具有相同模式的 Cucumber 步骤定义,Cucumber 会报告一个步骤定义重复错误

要匹配的模式是括号中的 / 字符间的正则表达式,其中包括捕获组 (([.\w]+)\.(\w+)),捕获组可用来解析传递给被调用的函数的参数:namespace (org.acme.shipping.perishable)、name (TemperatureReading) 和 table(我很快会进一步介绍 table 参数)。

为匹配模式“I should have the following participants”这一步骤所执行的代码类似于此代码:

                'use strict';
                
                module.exports = function () {
                
                    this.Given(/^I have added the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.addParticipants(namespace, name, table);
                    });
                
                    this.Given(/^I have added the following participants?$/, function (docString) {
                        return this.composer.addParticipants(null, null, docString);
                    });
                
                    this.When(/^I add the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.addParticipants(namespace, name, table);
                    });
                
                    this.When(/^I add the following participants?$/, function (docString) {
                        return this.composer.addParticipants(null, null, docString);
                    });
                
                    this.When(/^I update the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.updateParticipants(namespace, name, table);
                    });
                
                    this.When(/^I update the following participants?$/, function (docString) {
                        return this.composer.updateParticipants(null, null, docString);
                    });
                
                    this.When(/^I remove the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.removeParticipants(namespace, name, table);
                    });
                
                    this.When(/^I remove the following participants?$/, function (docString) {
                        return this.composer.removeParticipants(null, null, docString);
                    });
                
                    this.Then(/^I should have the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.testParticipants(namespace, name, table);
                    });
                
                    this.Then(/^I should have the following participants?$/, function (docString) {
                        return this.composer.testParticipants(null, null, docString);
                    });
                
                    this.Then(/^I should not have the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.testNoParticipants(namespace, name, table);
                    });
                
                    this.Then(/^I should not have the following participants?$/, function (docString) {
                        return this.composer.testNoParticipants(null, null, docString);
                    });
                
                };

如您所见,与 Participants 相关的步骤的代码寻找的不仅仅是 "I should have” 模式,还包括 "I remove"、"I add" 等模式。

Gherkin 中的复杂对象

您可能已经注意到,有两种根据 Gherkin 示例中的 Perishable Goods 网络模型来表示对象的方法。第一种方法如下所示:

                | contractId | grower           | shipper               | importer              | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                | CON_001    | grower@email.com | supermarket@email.com | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              |

第二种方法如下所示:

                """
                [
                {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":1500},
                {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-1500}
                ]
                """

第一种类型的对象表示称为数据表,它是一种竖线分隔的元组,其中第一行表示属性名称,后跟一个或多个包含属性值的行。数据表不支持嵌套对象。使用数据表表示对象时,必须告知相关步骤后面跟着的对象的类型。这个示例中的对象是一个 org.acme.shipping.perishable.Contract 对象,它是在与步骤相同的行上指定的(参见清单 2)。当对象不包含来自模型的其他必需对象时(可选对象可以省略),可以使用数据表。

第二种类型使用了 docstring,这是对象的一种 JSON 表示,而且支持嵌套对象(例如,请注意嵌套的 Address 对象)。使用 docstring 时,不必在步骤中指定对象类型,因为您必须在 docstring 中这么做(参见清单 5)。

当您尝试表示的对象包含嵌套的必需对象时,或者您想为嵌套的可选对象提供数据值时,可以使用 docstring 语法。

4

将修改后的网络部署到 IBM Cloud

现在您已修改了该网络并对它执行了单元测试,是时候将它上传到 https://composer-playground.mybluemix.net/ 上的 Online Playground 并在 IBM Cloud 上与它进行交互了。

第 1 部分 中,我展示了如何通过 Playground 使用 Perishable Goods 样板模型来定义一个网络,实例化该模型,并将事务提交到内存中的区块链中。在这一节,根据 Playground 的导入特性,您将使用通过构建(即 npm install)生成的 Perishable Goods 网络的业务网络归档 (BNA) 文件来部署该网络。但是首先需要做一些代码更改,然后运行一次构建来创建该 BNA 文件。

4a

做一些代码更改

确保您在本教程前面完善的 Perishable Goods 网络模型已在 VSCode 中打开。现在,在编辑器窗口中打开 lib/logic.js。向 temperatureReading() 函数添加一条控制台日志消息,如第 22 行所示:

清单 9. logic.js —temperatureReading()
                function temperatureReading(temperatureReading) {
                
                    var shipment = temperatureReading.shipment;
                    var NS = 'org.acme.shipping.perishable';
                    var contract = shipment.contract;
                    var factory = getFactory();
                
                    console.log('Adding temperature ' + temperatureReading.centigrade + ' to shipment ' + shipment.$identifier);
                
                    if (shipment.temperatureReadings) {
                        shipment.temperatureReadings.push(temperatureReading);
                    } else {
                        shipment.temperatureReadings = [temperatureReading];
                    }
                
                    if (temperatureReading.centigrade < contract.minTemperature ||
                        temperatureReading.centigrade > contract.maxTemperature) {
                        var temperatureEvent = factory.newEvent(NS, 'TemperatureThresholdEvent');
                        temperatureEvent.shipment = shipment;
                        temperatureEvent.temperature = temperatureReading.centigrade;
                        temperatureEvent.message = 'Temperature threshold violated! Emitting TemperatureEvent for shipment: ' + shipment.$identifier;
                        console.log(message);
                        emit(temperatureEvent);
                    }

现在找到 gpsReading() 函数并添加一条控制台日志消息,如第 22 行所示:

                function gpsReading(gpsReading) {
                    
                       var factory = getFactory();
                       var NS = "org.acme.shipping.perishable";
                       var shipment = gpsReading.shipment;
                       var PORT_OF_NEW_YORK = '/LAT:40.6840N/LONG:74.0062W';
                        
                       var latLong = '/LAT:'+ gpsReading.latitude + gpsReading.latitudeDir + '/LONG:'+
                           gpsReading.longitude + gpsReading.longitudeDir;
                        
                       if (shipment.gpsReadings) {
                           shipment.gpsReadings.push(gpsReading);
                       } else {
                           shipment.gpsReadings = [gpsReading];
                       }
                    
                       if (latLong == PORT_OF_NEW_YORK) {
                           var shipmentInPortEvent = factory.newEvent(NS, 'ShipmentInPortEvent');
                           shipmentInPortEvent.shipment = shipment;
                           var message = 'Shipment has reached the destination port of ' + PORT_OF_NEW_YORK;
                           shipmentInPortEvent.message = message;
                           console.log(message);
                           emit(shipmentInPortEvent);
                       }
                    
                       return getAssetRegistry(NS + '.Shipment')
                       .then(function (shipmentRegistry) {
                           // add the temp reading to the shipment
                           return shipmentRegistry.update(shipment);
                       });
                   }

现在,当您运行 TemperatureReadingGpsReading 事务,而且它们发出事件时,您可以在 JavaScript 控制台中看到它们。

4b

创建业务网络归档文件

转到命令行 (Ubuntu) 或打开一个终端窗口 (MacOS),导航到 perishable-network 目录(位于您的 $COMPOSER_ROOT 目录中),然后从命令行执行 npm install 来运行一次构建,以创建该 BNA 文件。您将看到类似这样的输出:

                $ cd $COMPOSER_ROOT
                $ pwd
                /Users/sperry/HyperledgerComposer
                $ cd developerWorks/perishable-network/
                $ npm install
                
                > perishable-network@0.1.11 prepublish /Users/sperry/HyperledgerComposer/developerWorks/perishable-network
                > mkdirp ./dist && composer archive create  --sourceType dir --sourceName .-a ./dist/perishable-network.bna
                
                Creating Business Network Archive
                
                
                Looking for package.json of Business Network Definition
                	Input directory: /Users/sperry/HyperledgerComposer/developerWorks/perishable-network
                
                Found: 
                	Description: Shipping Perishable Goods Business Network
                	Name: perishable-network
                	Identifier: perishable-network@0.1.11
                
                Written Business Network Definition Archive file to 
                	Output file: ./dist/perishable-network.bna
                
                Command succeeded
4c

将模型导入到 IBM Cloud 上的 Online Playground 中

现在,转到 IBM Cloud 上的 Online Playground: https://composer-playground.mybluemix.net/。您(现在)应该看到熟悉的欢迎屏幕:

图 4. IBM Cloud 上的 Playground 欢迎屏幕
IBM Cloud Composer Online Playground 欢迎屏幕
IBM Cloud Composer Online Playground 欢迎屏幕

如果没有看到欢迎屏幕,请清除浏览器的本地存储。例如,在 Chrome 中,可以在 Settings > Advanced > Content Settings > Cookies > All cookies and site data > localhost 下,单击垃圾桶图标删除本地存储。如果您使用的是不同的浏览器,请按照特定于该浏览器的操作说明来删除所有本地存储。

单击 Let's Blockchain 按钮开始操作。看到 My Business Networks 视图时,请单击 Deploy a new business network

图 5. My Business Networks
IBM Cloud Composer Online Playground - My Business Networks
IBM Cloud Composer Online Playground - My Business Networks

在下一个屏幕上,单击 Drop here to upload or browse。一个 File 对话框将打开。导航到 $COMPOSER_ROOT/developerWorks/perishable-network/dist 目录,找到名为 perishable-network.bna 的文件,并单击 Open

图 6. 部署新业务网络
IBM Cloud Composer Online Playground - 部署新的业务网络
IBM Cloud Composer Online Playground - 部署新的业务网络

您刚才单击的磁贴的标题更改为 perishable-network。单击 Deploy 按钮部署该模型。

图 7. 部署新业务网络
IBM Cloud Composer Online Playground - 部署新的业务网络
IBM Cloud Composer Online Playground - 部署新的业务网络

现在,该模型已部署到 IBM Cloud 中的 Online Playground。

4d

与模型进行交互

您应该已在本系列的第 1 部分中熟悉了与该模型的交互。事实上,Online Playground 是您在第 1 部分中使用的本地 Playground 的 IBM Cloud 版本,所以我不再赘述如何与您的模型进行交互。如果需要复习一下,请查阅第 1 部分。

在“完善 Perishable Goods 业务网络”小节中,您执行了单元测试来验证新模型中的代码是否在工作正常。现在,您将通过 Online Playground 与该模型进行交互,并使用 JavaScript 控制台验证它们。单击 Admin 卡上的 Connect now 开始执行操作。

首先,单击浏览器地址栏下的 Test 选项卡来实例化该模型。单击 Submit Transaction 按钮,选择 SetupDemo 事务,并单击 Submit

现在打开 JavaScript 控制台。具体操作取决于您的浏览器。如果使用 Chrome,可以选择 View > Developer > JavaScript console

接下来,为 11 摄氏度的 Shipment SHIP_001 提交一个 TemperatureReading 事务,该温度已超过最高温度阈值并会触发一个 TemperatureThresholdEvent,如图 8 所示。

图 8. 提交 TemperatureReading 事务
IBM Cloud Composer Online Playground - 提交 TemperatureReading 事务
IBM Cloud Composer Online Playground - 提交 TemperatureReading 事务

您应该在 JavaScript 控制台中看到以下消息(以及其他许多消息,所以您可能需要仔细查看):Temperature threshold violated! Emitting TemperatureEvent for shipment: SHIP_001。这表明该事件已正确发出(虽然我们已从之前成功运行 Cucumber 单元测试知道了这一点,对吧?)。

现在提交一个包含以下参数的针对 Shipment SHIP_001 的 GpsReading 事务,如图 9 所示。

  • readingTime = 171000
  • readingDate = 20171031
  • latitude = 40.6840
  • latitudeDir = N
  • longitude = 74.0062
  • longitudeDir = W
图 9. 提交 GpsReading 事务
IBM Cloud Composer Online Playground - 提交 GpsReading 事务
IBM Cloud Composer Online Playground - 提交 GpsReading 事务

您会在 JavaScript 控制台中看到以下消息:Shipment has reached the destination port of /LAT:40.6840N/LONG:74.0062W

恭喜您!您已有一个在 IBM Cloud 中正常运行的模型。

第 2 部分的小结

在本教程中,您设置了用于在您的计算机上执行本地 Hyperledger Composer 开发的工具。您修改了 Perishable Goods 网络,并添加了一个新事物和两个新事件,然后使用 Cucumber 对这些事件执行了单元测试。

然后,您利用修改后的 Perishable Goods 网络构建了一个 Composer 业务网络归档 (BNA) 文件,将它部署在 IBM Cloud 上托管的 Online Playground 中,并对它执行了测试。

敬请期待第 3 部分,在这一部分中,将会安装更多的工具,通过生成 REST 界面来真正利用 Hyperledger Composer 的强大功能。您将在计算机上安装并运行 Hyperledger Fabric,生成一个 GUI 用于与计算机上运行的 Perishable Goods 网络进行交互,就像在生产服务器上一样。第 3 部分将不再介绍 Playground!


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Cloud computing, 物联网
ArticleID=1058921
ArticleTitle=Hyperledger Composer 基础,第 2 部分: 完善并部署您的区块链网络
publish-date=04192018