Hyperledge 区块链开发教程(二)

原文:zh.annas-archive.org/md5/7f932e9670331dae388d1a76f72881d8

译者:飞龙

协议:CC BY-NC-SA 4.0

第五章:暴露网络资产和交易

如果您已经走到这一步,恭喜您!您已经建立了区块链应用程序的核心以及直接读取、更重要的是操纵您网络的记录系统的智能合约。但是,您还没有完成。正如您所能想象的那样,合约是一段敏感的代码,必须受到不当使用或篡改的保护。

要开发一个健壮且安全的应用程序,安全地发布给业务用户,您必须将智能合约与一层或多层保护包装起来,并将其设计为客户端可以通过适当的保障远程访问的服务。此外,希望共享分类帐和智能合约的各方可能具有独特和特定的业务逻辑需求,这些需求只有他们需要实现,其他人不需要。因此,运行一个智能合约的区块链应用程序可能最终会为不同的利益相关者提供不同的视图和功能。

在本章中,您将首先学习如何从头开始构建一个完整的区块链应用程序,使用我们的贸易应用程序作为指南和示例。稍后,您将了解到设计这个应用程序所需考虑的各种因素,以及如何将该应用程序与现有系统和流程集成。

本章将涵盖以下主题:

  • 构建完整的应用程序

  • 将应用程序与现有系统和流程集成

构建完整的应用程序

在本节中,您将学习如何围绕核心智能合约构建一个完整的应用程序,该应用程序可以被已经加入一起形成网络的商业实体方便地使用。我们将从回顾 Hyperledger Fabric 交易流程开始,以提醒读者用户(或客户端)的角度,一个区块链应用程序是如何做的(以及如何做的)。通过代码示例,我们将向您展示如何根据商业实体的需求构建、设计和组织网络,创建适当的配置,并完成从开始到结束的区块链交易的不同阶段。在此过程结束时,读者将了解如何设计一个 Fabric 应用程序,并通过一个简单的 web 界面展示其功能。在本章开始时,我们唯一需要拥有的资产是合约或链代码,它是使用 Go 编程(请参阅 第四章使用 Golang 设计数据和交易模型)手工开发的。

在本章的后端,我们将引导经验丰富的企业开发人员深入了解更高级的主题,例如服务设计模式、可靠性和其他常见的工程问题。尽管这些问题适用于每个分布式应用程序,但我们将讨论基于区块链的应用程序的特殊需求和问题。

Hyperledger Fabric 应用的性质

在之前的章节中,我们看到 Hyperledger Fabric 可以被视为一个分布式事务处理系统,具有分阶段的操作流水线,这些操作最终可能导致由网络对等方维护的共享复制分类帐的状态发生变化。对于开发者来说,区块链应用程序是一组过程,通过这些过程,用户可以向智能合约提交事务,或者从智能合约中读取状态。在底层,开发者必须将用户请求引导到事务流水线的不同阶段,并提取结果以在流程结束时提供反馈。基本上,无论合约是手工实现的(参见 第四章使用 Golang 设计数据和事务模型)还是使用 Hyperledger Composer 实现的(参见 第六章业务网络),应用程序开发者的工作都是在智能合约周围实现一个或多个包装层。

以智能合约(或资产实体模型)为核心开发的应用可以被视为具有一组视图或服务 API 的事务处理数据库应用程序。然而,开发人员必须牢记每个 Hyperledger Fabric 事务都是异步的,也就是说,事务的结果在提交的通信会话中不会立即可用。这是因为,正如我们在之前的章节中所看到的,一个事务必须通过共识被网络中的对等方集体批准。因此,共识可能需要花费不确定的时间,并且事务结果的通信被设计为发布/订阅机制。以下图表从开发者的角度说明了区块链应用程序和事务流水线:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5.1:区块链应用程序创建和运行中的阶段

在接下来的部分中,将详细描述图中提到的操作,并将其映射到特定的 Fabric 机制。

应用程序和事务阶段

创建应用程序的第一步是实例化区块链,或者说共享分类帐本身。在 Fabric 的术语中,区块链的一个实例被称为通道,因此区块链应用程序的第一步是创建一个通道,并使用通道的创世区块引导网络排序服务。

下一步是对等网络的初始化,所有被选中运行应用程序的对等节点必须加入通道,这个过程允许每个对等方维护一个分类帐的副本,该副本被初始化为一个空白的键值存储。加入通道的每个对等方都将拥有分类帐提交特权,并可以参与八卦协议以与其他对等方同步分类帐状态。

在对等网络创建完成后,将在该网络上安装智能合约。在此之前连接到通道的一部分对等方将被选中来运行智能合约;换句话说,它们将拥有背书特权。合约代码将部署到这些对等方并构建以进行后续操作。如你所知,到这一步,合约在 Fabric 术语中被称为链码,本章的其余部分将使用这个术语。

一旦链码被安装在背书对等方上,将根据嵌入其中的逻辑进行初始化(参见第四章使用 Golang 设计数据和交易模型,以获取示例)。

到这一点为止,除非在前面的一个或多个步骤中出现了问题,否则应用程序已经启动并运行。现在,可以将交易发送到链码,以更新分类帐的状态(调用)或读取分类帐状态(查询)在应用程序的生命周期内。

应用可能随着时间的推移而发生变化或演化,需要执行一些未在图 5.1:区块链应用创建和运行阶段中捕获的特殊操作。这些将在第九章中描述,区块链网络中的生活

在标题为构建应用及其后面的部分,我们将展示如何围绕第四章中开发的链码构建贸易应用,使用适当的代码和说明。

应用模型和架构

编写 Fabric 应用程序的过程始于链码,但最终开发人员必须就终端用户或软件代理如何与该链码进行接口设计做出明智的决策。链码的资产以及运行该链码的区块链网络的操作应该如何暴露给用户是一个需要仔细处理的问题。如果这些功能暴露出去而没有限制,特别是涉及区块链引导和配置的功能,可能会造成严重的损害。链码本身的正确运行不仅依赖于其内部逻辑,还依赖于在其上构建的适当访问控制。正如我们在前一节中看到的,设置应用程序并为其准备使用是一个复杂的过程。此外,分类帐更新事务的异步性质需要在链码和用户之间设置一个仲裁层。为了让用户专注于影响应用程序而不是网络模块的细节,所有这些复杂性都应该尽可能隐藏起来。因此,一个三层架构作为 Fabric 应用程序的标准已经成为标准,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5.2 Hyperledger Fabric 应用程序的典型三层架构

在最底层是直接在共享账本上运行的智能合约,可以使用一个或多个链码单元编写。这些链码在网络对等体上运行,为调用和查询提供服务 API,并发布事务结果的事件通知,以及通道上发生的配置更改。

在中间层中,存在着协调区块链应用程序各个阶段的功能(参见图 5.1:区块链应用程序创建和运行的各个阶段)。Hyperledger Fabric 提供了一个 SDK(目前在Node.js和 Java 中都可用)来执行诸如通道创建和加入、用户注册和注册、以及链码操作等功能。此外,SDK 提供了机制来订阅来自网络的事务和配置相关事件。根据应用程序的需求,可以维护一个离链数据库以方便使用,或者作为分类帐状态的缓存。

最顶层是一个面向用户的应用程序,导出一个服务 API,主要由特定于应用程序的功能组成,尽管管理员操作(如频道和链码操作)也可能暴露给系统管理员。通常,提供用户界面以便使用,尽管如果用户是软件代理,一个明确定义的 API 可能就足够了。我们简单地称这一层为应用程序,因为这是最终用户(或代理)将看到的内容。此外,考虑到任何区块链应用程序和网络都是多样参与者的聚合体,这一层通常将由为不同参与者量身定制的多个应用程序堆栈组成。

这种架构不应该被定死;它的目的纯粹是为开发人员提供一个指导方针。根据应用程序的复杂性,层的数量和垂直方面(或不同的应用程序)可能会有所不同。对于一个非常简单的应用程序,具有少量功能的开发人员甚至可以选择将中间件和应用程序层压缩到一起。然而更普遍的是,这种解耦使不同的能力集可以暴露给不同的网络参与者。例如,在我们的交易用例中,监管机构和出口商会以不同的方式查看区块链并有不同的需求,因此建立为它们构建不同服务集比强行将所有功能适配成一个单片应用程序并具有统一界面更有用。但是这两个应用程序都应该隐藏网络操作的复杂性,比如频道的创建和加入,或特权操作,例如将链码安装到对等体上,这样从一个共同的中间件层中可以受益。

用户直接互动的应用程序层可以设计出许多选择和复杂性,我们将在本章的后半部分深入探讨这些内容。不过,首先,我们将描述如何实现 Fabric 应用程序的关键部分,重点放在基本要素上。出于指导目的,我们的最顶层将是一个简单的 Web 服务器,提供RESTful服务 API。

这种架构背后的思想和推动它的原则独立于基础的区块链技术。要在与 Hyperledger Fabric 不同的区块链平台上实现相同的应用程序,只需重新实现智能合约和一些中间件的部分。应用程序的其余部分可以保持不变,最终用户不会注意到任何差异。

构建应用程序

现在我们不仅了解了设计分层 Fabric 应用程序的方法,还了解了其背后的哲学,我们可以深入实施了。在前两章中,我们讨论了如何实现和测试最底层,或者说链代码。因此,我们可以假设读者现在已经准备好添加中间件和应用层,这正是我们将在以下章节中演示的内容。

测试中间件和应用程序代码的先决条件是运行网络。在继续下一节之前,请确保我们在第三章中配置和启动的示例四组织网络仍在运行中。

中间件 - 封装和驱动链代码

以下图示了在《应用与交易阶段》部分讨论的交易阶段,并在图 5.1:区块链应用创建和运行阶段中加以说明,以 Fabric 术语和使用 Fabric 术语来表示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5.3:区块链应用创建和运行阶段

Fabric 对等方、排序器和 CA(或 MSP)使用 gRPC(grpc.io/)进行通信,以及由对等方生成的用于运行链代码的进程(该进程实际上是一个 Docker 容器)。该进程导出一个实现 JSON RPC 2.0 规范(www.jsonrpc.org/specification)的服务端点,用于通道和链代码操作。我们可以编写一个直接使用服务规范与链代码通信的包装应用程序,但然后我们将不得不编写逻辑来解析和解释负载。由于 Fabric 平台及其规范可能会在未来发生变化,因此这不一定是编写应用程序的最佳和最可维护的方式,特别是对于生产目的而言。幸运的是,Hyperledger Fabric 提供了两种不同的方式来运行链代码操作,同时隐藏接口规范和通信协议的细节:

  • 命令行界面CLI):Fabric 提供了可以从终端运行的命令,用于执行图 5.3:区块链应用创建和运行阶段中指示的各种操作。运行这些命令的工具是peer,它在下载 Fabric 源代码并构建它(使用make或只是make peer)时生成。可以使用不同的开关与此命令一起使用以执行不同的通道和链代码操作,在本节中您将看到一些示例。

  • 软件开发工具包SDK):Hyperledger 提供了一个工具包和一组库,用于轻松开发多种语言(如 Node.js、Java、Go 和 Python)的应用程序,以包装通道和链码操作。这些 SDK 还提供了与 MSPs 或 Fabric CA 实例进行交互的功能。

尽管 CLI 工具可用于测试和演示目的,但它们不足以用于应用程序开发。SDK 库除了前面提到的功能外,还提供了订阅来自网络的事件的能力,传达关于需要驱动应用程序逻辑的状态变化的信息。我们将使用 Node.js SDK 演示如何构建我们的中间件和更高层次的应用程序。读者可以使用其他 SDK 之一来构建自己选择的其他语言中的等效应用程序。

工具和依赖项的安装

我们将展示如何在我们的中间件中构建的函数可以在代码库的中间件文件夹中找到。

创建和运行中间件的先决条件

阅读者应该熟悉 Node.js/JavaScript 编程(尤其是Promise模式)以及 Node.js 和npm工具的用法:

  1. 安装 Node.js (nodejs.org/en/download/) 和 npm (www.npmjs.com/get-npm)。

  2. 安装 fabric-clientfabric-ca-client npm 库:

    • 您可以从npm注册表中安装这些包,可以手动运行 npm install <package-name> 或者通过在 package.json 文件中设置名称和版本。例如,中间件文件夹中的 package.json 在依赖项部分包含以下条目:

      • fabric-ca-client: ¹.1.0

      • fabric-client: ¹.1.0

  3. 这告诉npm安装这两个包的 1.1.0 版本:

    • 或者,您可以克隆 Fabric SDK node (github.com/hyperledger/fabric-sdk-node/) 源代码库,并按如下方式本地导入这两个库:

      • fabric-clientfabric-ca-client 文件夹中运行 npm install

      • 安装这些包作为依赖项,可以手动在中间件/package.json文件中指定路径,也可以使用npm链接命令将符号链接添加到中间件/node_modules中的包中

在接下来的章节中,我们将使用 fabric-client 库执行涉及对等体和订购者的通道和链码操作,以及 fabric-ca-client 库执行涉及 CA(或 MSP)的用户注册和注册操作。

依赖项的安装

在中间件文件夹中运行 npm install 来安装 package.json 中指定的包(库及其依赖项)。您应该会看到这些包下载到 node_modules 文件夹中。

安装依赖项和配置中间件以进行常规操作的更干净的方法是使用 Makefile 进行自动构建。您只需在 middleware 文件夹中运行 make;有关设置和构建您的开发和测试环境的更多详细信息,请参阅 第八章 区块链网络中的灵活性

创建和运行中间件

我们现在将编写函数来执行和编排图 5.3 中所示的阶段:区块链应用程序的创建和操作中的各个阶段。但首先,我们将概述必须设置的各种配置参数,以使应用程序按预期工作。

网络配置

编写中间件的第一步是收集所有必要的配置信息,以识别并连接到我们在上一节中创建和启动的网络的各个元素。在 JavaScript 中编写代码时,将这些配置表示为 JSON 格式是很有用的。在我们的示例代码中,config.json 文件用于此目的。此文件包含一个网络的描述,其属性包含在 trade-network 对象中。此对象的每个属性描述了网络中每个唯一组织的配置,除了一个称为 orderer 的属性,它只是指向订购者节点。(注意:这对于我们的简单网络足够了,只包含一个订购者节点。)让我们通过以 Exporterorg 属性为例来检查每个组织描述中必须指定的内容:

"exporterorg": {
  "name": "peerExporterOrg",
  "mspid": "ExporterOrgMSP",
  "ca": {
    "url": "https://localhost:7054",
    "name": "ca-exporterorg"
  },
  "peer1": {
    "requests": "grpcs://localhost:7051",
    "events": "grpcs://localhost:7053",
    "server-hostname": "peer0.exporterorg.trade.com",
    "tls_cacerts": "../network/crypto-config/peerOrganizations/exporterorg.trade.com/peers/peer0.exporterorg.trade.com/msp/tlscacerts/tlsca.exporterorg.trade.com-cert.pem"
  }
},

mspid 值必须与在 network/configtx.yaml 中指定的值相匹配,以使我们的中间件与为网络创建的通道工件和加密材料兼容。 CA 的名称和端口信息必须与在 network/docker-compose-e2e.yaml 中指定的内容相匹配。由于每个组织只有一个对等体,我们为方便起见将其命名为对等体,尽管可以轻松地为多对等体组织设置定义不同的模式。请注意,对等体导出对等体请求和事件订阅的服务,端口与 network/base/docker-compose-base.yaml 中公开的端口相匹配。server-hostname 也必须与 configtx.yaml 和 docker-compose 配置中指定的内容相匹配。由于我们的网络元素使用 TLS 连接,因此还必须在此指定对等体的 TLS 证书路径。

最后,如果您将前面的模式片段与其他组织的配置进行比较,您将注意到列出的端口与 docker-compose 配置中公开的端口完全匹配。例如,出口商、进口商、承运商和监管机构组织中的对等体分别在端口 70518051905110051 上监听请求。 URL 中的主机名仅指向 localhost,因为这是我们所有网络元素容器运行的地方。

支持政策

下一步是为我们的链码制定一个认可策略,该策略将在实例化期间提交到分类帐。该认可策略规定了需要多少个角色和组织的对等方认可分类帐承诺事务(或调用)。在示例代码中,constants.js列出了不同的认可策略,其中包含我们中间件使用的各种设置和关键字。我们将使用的是ALL_FOUR_ORG_MEMBERS

var FOUR_ORG_MEMBERS_AND_ADMIN = [
  { role: { name: 'member', mspId: 'ExporterOrgMSP' } },
  { role: { name: 'member', mspId: 'ImporterOrgMSP' } },
  { role: { name: 'member', mspId: 'CarrierOrgMSP' } },
  { role: { name: 'member', mspId: 'RegulatorOrgMSP' } },
  { role: { name: 'admin', mspId: 'TradeOrdererMSP' } }
];
var ALL_FOUR_ORG_MEMBERS = {
  identities: FOUR_ORG_MEMBERS_AND_ADMIN,
  policy: {
    '4-of': [{ 'signed-by': 0 }, { 'signed-by': 1 }, { 'signed-by': 2 }, { 'signed-by': 3 }]
  }
};

主体列表在策略的 identities 属性中指定,并且引用了四个对等组织的成员(或普通)用户,以及订购者组织的管理员用户。此处的策略属性声明,需要从每个四个对等组织的成员那里获得认可;总共需要四个签名。

constants.js中,变量TRANSACTION_ENDORSEMENT_POLICY默认设置为ALL_FOUR_ORG_MEMBERS,将在本节后面用于配置通道认可策略。

用户记录

对于通道状态和各自组织的用户密钥和证书,我们将使用基于文件的存储,如clientUtils.js中所指定的那样:

var Constants = require('./constants.js');
var tempdir = Constants.tempdir;
module.exports.KVS = path.join(tempdir, 'hfc-test-kvs');
module.exports.storePathForOrg = function(org) {
  return module.exports.KVS + '_' + org;
};

constants.js中,tempdir被初始化如下:

var tempdir = "../network/client-certs";

或者,您还可以使用os.tmpdir()函数将存储位置设置为位于操作系统临时文件夹中的子文件夹(比如<folder-name>)。在典型的 Linux 系统上,此存储位置将默认为/tmp/<folder-name>/,并且将在那里为每个组织创建文件夹。在我们运行各种操作时,我们会看到这些文件夹被生成,并且文件被添加到其中。

客户端注册和注册

尽管可以使用cryptogen工具静态创建组织用户的加密材料,但是我们必须在中间件中构建能力,动态创建用户身份和凭证,并使这些用户登录到网络以提交交易和查询分类帐状态。这些操作需要特权访问的用户(或管理员)的介入,在fabric-ca-server启动时必须创建他们。默认情况下,管理用户被赋予 IDadmin和密码adminpw,这是我们在本节练习中将要使用的。我们创建和启动的网络使用这些默认值,读者可以在fabric-ca-servernetwork/docker-compose-e2e.yaml的开始命令中进行修改(以下是exporter-ca部分的内容)。

fabric-ca-server start --ca.certfile /etc/hyperledger/fabric-ca-server-config/ca.exporterorg.trade.com-cert.pem --ca.keyfile /etc/hyperledger/fabric-ca-server-config/cc58284b6af2c33812cfaef9e40b8c911dbbefb83ca2e7564e8fbf5e7039c22e_sk -b admin:adminpw -d

通过管理员创建用户的步骤如下:

  1. 从本地存储加载管理员用户凭据

  2. 如果凭据不存在,则管理员需要注册或登录到 Fabric CA 服务器,并获取他们的凭据(私钥和注册证书)

  3. 由管理用户注册另一个具有给定 ID 的用户,并指定角色和 affiliations 与 Fabric CA 服务器

  4. 使用注册时返回的秘钥,enroll 新用户并获得该用户的凭据

  5. 将凭据保存到本地存储

可以在clientUtils.js中找到相关示例代码,以下代码片段大多来自getUserMember函数,该函数使用管理员凭据,需要用户必须 enrolled 的组织名称/ID。还需要传递一个客户端的 handle(fabric-client的实例,或一个客户端对象)给该函数:

var cryptoSuite = client.getCryptoSuite();
if (!cryptoSuite) {
  cryptoSuite = Client.newCryptoSuite();
  if (userOrg) {
    cryptoSuite.setCryptoKeyStore(Client.newCryptoKeyStore({path: module.exports.storePathForOrg(ORGS[userOrg].name)}));
    client.setCryptoSuite(cryptoSuite);
  }
}

上述代码将客户端句柄与本地存储(按组织进行分区)关联,以存储管理员和临时创建的其他用户的凭据:

var member = new User(adminUser);
member.setCryptoSuite(cryptoSuite);

该代码确保管理员用户句柄将与我们的存储关联:

var copService = require('fabric-ca-client/lib/FabricCAClientImpl.js');
var caUrl = ORGS[userOrg].ca.url;
var cop = new copService(caUrl, tlsOptions, ORGS[userOrg].ca.name, cryptoSuite);
return cop.enroll({
  enrollmentID: adminUser,
  enrollmentSecret: adminPassword
}).then((enrollment) => {
  console.log('Successfully enrolled admin user');
  return member.setEnrollment(enrollment.key, enrollment.certificate, ORGS[userOrg].mspid);
})

在这里,我们使用fabric-ca-client库连接到与给定组织关联的fabric-ca-server实例(其 URL 可以从我们的config.json获得;例如,出口商组织的caUrl将是https://localhost:7054)。enroll 函数允许管理员使用 MSP 登录,并获取 enrollment 密钥和证书。

现在我们已经获取了管理员用户的句柄,形式上是 member 对象,我们可以使用它来为用户 ID(用户名表示)enroll 一个新用户,如下所示:

var enrollUser = new User(username);
return cop.register({
  enrollmentID: username,
  role: 'client',
  affiliation: 'org1.department1'
}, member).then((userSecret) => {
  userPassword = userSecret;
  return cop.enroll({
    enrollmentID: username,
    enrollmentSecret: userSecret
  });
}).then((enrollment) => {
  return enrollUser.setEnrollment(enrollment.key, enrollment.certificate, ORGS[userOrg].mspid);
}).then(() => {
  return client.setUserContext(enrollUser, false);
}).then(() => {
  return client.saveUserToStateStore();
})

在注册过程中,我们可以指定用户的角色,上面的代码中是客户端,允许用户名提交调用和查询到链码。此处指定的 affiliation 是组织内的一个细分,它在 Fabric CA 服务器的配置中指定(hyperledger-fabric-ca.readthedocs.io/en/latest/serverconfig.html)(更新此配置留给读者作为练习;在这里,我们将使用默认的 affiliation)。使用返回的秘钥,用户名现在已经与服务器 enrolled,并且其密钥和 enrollment 证书被保存。

调用client.setUserContext将该用户与客户端句柄关联,client.saveUserToStateStore将用户的凭据保存到我们在文件系统上的本地存储。

与获取管理员用户句柄相似的函数还有getAdmingetMember,也在clientUtils.js中定义。前者检索使用cryptogen创建的管理员用户凭据,而后者动态创建一个新的admin member。

创建一个通道

要创建我们的交易通道,我们首先需要实例化一个fabric-client实例,并使用config.json中的配置获得一个到 orderer 的 handle(参见create-channel.js中的createChannel函数):

var client = new Client();
var orderer = client.newOrderer(
  ORGS.orderer.url,
  {
    'pem': caroots,
    'ssl-target-name-override': ORGS.orderer['server-hostname']
  }
);

我们使用基于文件的键值存储来保存分类帐的世界状态,如下所示(读者可以尝试其他类型的存储,比如使用 CouchDB,使用 CouchDBKeyValueStore.js):

utils.setConfigSetting('key-value-store', 'fabric-client/lib/impl/FileKeyValueStore.js');

接下来,我们必须为订购者注册一个管理员用户(使用前一部分讨论的机制)。成功注册后,必须提取使用 configtxgen 工具创建的通道配置(参见 network/channel-artifacts/channel.tx)。此配置文件的路径在 constants.js 中设置:

let envelope_bytes = fs.readFileSync(path.join(__dirname, Constants.networkLocation, Constants.channelConfig));
config = client.extractChannelConfig(envelope_bytes);

现在,我们需要为我们的四个组织中的每个管理员用户注册。这四个管理员以及订购者管理员必须对通道配置进行签名,签名如下收集:

ClientUtils.getSubmitter(client, true /*get the org admin*/, org)
.then((admin) => {
  var signature = client.signChannelConfig(config);
  signatures.push(signature);
});

getSubmitter 函数定义在 clientUtils.js 中,它是将给定组织的成员(普通成员或管理员)与客户端对象关联的间接方式。换句话说,它将客户端对象与用户的签名身份(凭证和 MSP 标识)关联起来。在底层,getSubmitter 使用了我们在前面章节中描述的 getAdmingetUserMembergetMember 函数。

getOrderAdminSubmitter 类似于 getSubmitter,返回一个与订购者组织的 admin 用户关联的句柄。

最后,我们准备构建一个通道创建请求,并将其提交给订购者:

let tx_id = client.newTransactionID();
var request = {
  config: config,
  signatures : signatures,
  name : channel_name,
  orderer : orderer,
  txId : tx_id
};
return client.createChannel(request);

实际创建通道可能需要几秒钟,因此应用逻辑在返回成功结果之前应该等待一段时间。channel_name 参数在 clientUtils.js 中设置为 tradechannel,这是我们启动网络时设置的(参见 network/trade.sh)。

通道创建步骤涉及使用 configtxgen 在本章早些时候创建的创世块来初始化区块链。创世块只是追加到链上的第一个配置块。配置块包含对通道的规范以及其中所包含的组织等内容;这样的块不包含链码交易。在第九章区块链网络中的生活中,我们将再次处理配置块,讨论如何扩展网络。

现在,我们创建一个通道所需做的就是调用 createChannel('tradechannel') 函数并等待结果。这是我们测试代码 createTradeApp.js 的第一步,它执行了图 5.3:区块链应用创建和操作阶段中所示的基本操作序列:

var Constants = require('./constants.js');
var createChannel = require('./create-channel.js');
createChannel.createChannel(Constants.CHANNEL_NAME).then(() => { ...... })

我们用于将不同签名标识与一个通用客户端对象关联起来,然后在单个过程中对频道配置进行签名的代码纯粹是为了演示目的。在实际生产应用中,属于不同组织的不同用户的签名标识是私有的,并且必须受到保护;因此,没有将它们汇集到一个共同位置的问题。相反,频道配置必须由不同组织的管理员独立签名,并使用某种带外机制传递以累积签名(并验证签名)。在更新配置时也必须使用类似的机制(见第九章区块链网络中的生活)。加入通道和安装链码时也必须遵循独立的、去中心化的程序,尽管我们为了方便演示使用了集中式过程来展示基本机制。

加入通道

现在 tradechannel 已经创建,我们的四个对等方,每个组织一个,必须加入通道,这一步骤在每个节点上初始化分类帐,并准备对等方在其上运行链码和交易。为此,我们需要重用在前一步骤中创建的客户端句柄,或者使用类似操作序列实例化一个。此外,我们必须实例化一个通道句柄,注册订购方,并获取创世块(在使用通道配置在创建步骤中隐式发送到订购方),如 join-channel.jsjoinChannel 函数中的以下代码片段所示:

var channel = client.newChannel(channel_name);
channel.addOrderer(
  client.newOrderer(
    ORGS.orderer.url,
    {
      'pem': caroots,
      'ssl-target-name-override': ORGS.orderer['server-hostname']
    }
  )
);
tx_id = client.newTransactionID();
let request = { txId : tx_id };
return channel.getGenesisBlock(request);

在前述的 getGenesisBlock 调用中,交易 ID 参数是可选的。现在,对于每个组织,我们必须获取一个管理员用户的句柄,然后为属于该组织的对等方提交通道加入请求:

return ClientUtils.getSubmitter(client, true /* get peer org admin */, org);
for (let key in ORGS[org])
  if (ORGS[org].hasOwnProperty(key)) {
    if (key.indexOf('peer') === 0) {
      data = fs.readFileSync(path.join(__dirname, ORGS[org][key]['tls_cacerts']));
      targets.push(
        client.newPeer(
          ORGS[org][key].requests,
          {
            pem: Buffer.from(data).toString(),
            'ssl-target-name-override': ORGS[org][key]['server-hostname']
          }
        )
      );
    }
  }
}
tx_id = client.newTransactionID();
let request = {
  targets : targets,
  block : genesis_block,
  txId : tx_id
};
let sendPromise = channel.joinChannel(request, 40000);

与通道创建过程一样,getSubmitter 函数在提交通道加入请求之前将特定组织的管理员的签名标识与客户端对象关联起来。此请求包含创世块以及该组织中每个对等方的配置(从 config.json 中每个组织中包含 peer 前缀的属性中加载,如上面的代码所示)。

如上所示,给出了宽裕的等待时间,因为此过程可能需要一段时间才能完成。这个加入过程需要由每个组织的管理员独立执行;因此,在我们的测试脚本 createTradeApp.js 中,processJoinChannel 主函数依次调用了 4 次 joinChannel(<org-name>) 函数:

var joinChannel = require('./join-channel.js');
joinChannel.processJoinChannel();

在典型的生产网络中,每个组织将独立运行加入过程,但仅针对其对等方。我们在存储库中使用的编排代码(join-channel.js 中的 processJoinChannel)旨在方便和测试。

链码的安装

链码的安装导致将源代码复制到我们选择作为背书人的对等方,并且每个安装都与用户定义的版本相关联。主要函数installChaincodeinstall-chaincode.js中实现。该函数依次为每个组织调用installChaincodeInOrgPeers函数;后者在给定组织的对等体上安装链码。与频道加入一样,在给定组织创建客户端和频道句柄,为该组织的管理员用户注册,并将该用户与客户端句柄关联。下一步是创建安装建议并将其提交给订购者,如下所示:

var request = {
  targets: targets,
  chaincodePath: chaincode_path,
  chaincodeId: Constants.CHAINCODE_ID,
  chaincodeVersion: chaincode_version
};
client.installChaincode(request);

目标指的是组织中认可的对等体的配置,从config.json加载。chaincodeIdchaincodeVersion可以由调用者设置(默认值在constants.js中分别设置为tradeccv0),但chaincodePath必须指向包含源代码的位置。在我们的场景中,该位置指的是本地文件系统上的路径:github.com/trade_workflow

在 SDK 内部,安装请求将链码的源代码打包到一种称为ChaincodeDeploymentSpec(CDS)的规定格式中(github.com/hyperledger/fabric/blob/release-1.1/protos/peer/chaincode.proto)。然后对该包进行签名(由与客户端对象关联的组织管理员签名),以创建SignedChaincodeDeploymentSpecgithub.com/hyperledger/fabric/blob/release-1.1/protos/peer/signed_cc_dep_spec.proto),然后将其发送到生命周期系统链码(LSCC)进行安装。

上述过程描述了每个 Signed CDS 实例仅具有签发安装请求的客户端相关联的标识的签名的简单情况。Fabric 支持更复杂的场景,其中 CDS 可以(在带外)传递给不同组织的不同客户端,并在收到安装请求之前由每个客户端签名。鼓励读者使用可用的 API 函数和 Fabric 数据结构尝试此变体(hyperledger-fabric.readthedocs.io/en/latest/chaincode4noah.html)。

安装请求的成功取决于检查每个目标对等体的建议响应,如下所示:

if (proposalResponses && proposalResponses[i].response && proposalResponses[i].response.status === 200) {
  one_good = true;
  logger.info('install proposal was good');
}

最后,为了在整个网络上进行安装协调,我们调用了install-chaincode.js中定义的installChaincode函数。为了让fabric-client知道从哪里加载chaincode源代码,我们暂时将进程中的GOPATH设置为指向我们项目中的正确位置,即chaincode文件夹:

这仅适用于用 Go 编写的chaincode

process.env.GOPATH = path.join(__dirname,Constants.chaincodeLocation);

为了成功安装,chaincode 文件夹必须包含一个名为src的子文件夹,在其中,安装提案中发送的chaincode路径必须指向实际代码。正如你所看到的,这最终解析为我们代码库中的chaincode/src/github.com/trade_workflow,其中确实包含了我们在第四章中开发的源代码,使用 Golang 设计数据和事务模型

在我们的createTradeApp.js脚本中,我们现在可以简单地调用:

var installCC = require('./install-chaincode.js');
installCC.installChaincode(Constants.CHAINCODE_PATH, Constants.CHAINCODE_VERSION);

在典型的生产网络中,每个组织将独立运行安装过程(在installChaincodeInOrgPeers函数中定义),但仅针对其背书对等体。我们在仓库中使用的编排代码(install-chaincode.js中的installChaincode)是为了方便和测试而设计的。

链码的实例化

现在网络中的背书对等体有了链码后,我们必须在我们的通道上实例化该链码,以确保所有分类账副本都使用正确的数据集(或键值对)初始化。这是我们的智能合约设置的最后一步,然后我们才能将其用于常规操作。实例化是调用 LSCC 来初始化通道上的链码的事务,从而将两者绑定并将前者的状态隔离到后者。

此操作应由授权初始化链码的任何组织中心触发(在我们的示例代码中,我们使用进口商组织的管理员)。同样,这遵循了先前安装部分中描述的简单场景,其中链码包由单个组织管理员签名。

默认的通道实例化策略要求任何通道 MSP 管理员触发该操作,但如果需要,可以在签名的 CDS 结构中设置不同的策略。此外,触发实例化操作的实体还必须在通道上配置为写入者。我们使用configtxgen隐含地授予了 4 个组织的管理员写入权限,以创建通道配置的过程。(关于通道配置策略的详细讨论超出了本书的范围。)

实现链码实例化的主要函数在instantiate-chaincode.js中作为instantiateOrUpgradeChaincode实现。此函数既可用于实例化新部署的链码,也可用于更新已在通道上运行的链码(见第九章在区块链网络中的生活)与之前的阶段一样,我们必须创建客户端和通道句柄,并将通道句柄与客户端关联。此外,必须将网络中的所有背书对等方添加到通道中,然后必须使用与通道关联的通道对象来初始化通道的与通道相关联的 MSP(来自四个组织中的每一个):

channel.initialize();

这设置了通道以验证证书和签名,例如,来自对等方收到的背书。接下来,我们构建了一个实例化提案并将其提交给通道上的所有背书对等方(从buildChaincodeProposal函数的片段):

var tx_id = client.newTransactionID();
var request = {
  chaincodePath: chaincode_path,
  chaincodeId: Constants.CHAINCODE_ID,
  chaincodeVersion: version,
  fcn: funcName,
  args: argList,
  txId: tx_id,
  'endorsement-policy': Constants.TRANSACTION_ENDORSEMENT_POLICY
};
channel.sendInstantiateProposal(request, 300000);

链码的路径、ID 和版本必须与安装提案中提供的内容匹配。此外,我们必须提供将发送到链码并执行的函数名和参数列表(在我们的链码中,这将执行 Init 函数)。还请注意,提案中包含了我们之前设置的背书策略(Constants.TRANSACTION_ENDORSEMENT_POLICY),该策略要求来自四个组织的成员对链码调用进行背书。顺序服务返回的提案响应(每个背书对等方一个)必须像在安装阶段一样进行验证。利用前面channel.sendInstantiateProposal调用的结果,我们现在必须构建一个实例化交易请求,并将其提交给顺序服务:

var proposalResponses = results[0];
var proposal = results[1];
var request = {
  proposalResponses: proposalResponses,
  proposal: proposal
};
channel.sendTransaction(request);

channel.sendTransaction的成功响应将使我们的中间件在实例化成功提交的基础上继续进行。然而,这并不意味着实例化将成功地在共享分类账上提交;为此,我们的代码将必须订阅事件,我们将在本节后面看到如何做到这一点。

我们的脚本createTradeApp.js触发链码实例化如下:

var instantiateCC = require('./instantiate-chaincode.js');
instantiateCC.instantiateOrUpgradeChaincode(
  Constants.IMPORTER_ORG,
  Constants.CHAINCODE_PATH,
  Constants.CHAINCODE_VERSION,
  'init',
  ['LumberInc', 'LumberBank', '100000', 'WoodenToys', 'ToyBank', '200000', 'UniversalFrieght', 'ForestryDepartment'],
  false
);

最后一个参数设置为false,表示必须执行实例化而不是升级。第一个参数(Constants.IMPORTER_ORG)表示实例化请求必须由进口商组织的成员(在这个上下文中是管理员)提交。

如果实例化成功,链码将在 Docker 容器中构建,每个背书对等方对应一个容器,并部署以代表它们接收请求。如果你运行 docker ps -a,你应该会看到除了在启动网络时创建的容器外,还有类似以下的内容:

CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES
b5fb71241f6d     dev-peer0.regulatororg.trade.com-tradecc-v0-cbbb0581fb2b9f86d1fbd159e90f7448b256d2f7cc0e8ee68f90813b59d81bf5    "chaincode -peer.add..."    About a minute ago    Up About a minute        dev-peer0.regulatororg.trade.com-tradecc-v0
077304fc60d8    dev-peer0.importerorg.trade.com-tradecc-v0-49020d3db2f1c0e3c00cf16d623eb1dddf7b649fee2e305c4d2c3eb5603a2a9f    "chaincode -peer.add..."    About a minute ago    Up About a minute        dev-peer0.importerorg.trade.com-tradecc-v0
8793002062d7    dev-peer0.carrierorg.trade.com-tradecc-v0-ec83c1904f90a76404e9218742a0fc3985f74e8961976c1898e0ea9a7a640ed2    "chaincode -peer.add..."    About a minute ago    Up About a minute        dev-peer0.carrierorg.trade.com-tradecc-v0
9e5164bd8da1    dev-peer0.exporterorg.trade.com-tradecc-v0-dc2ed9ea732a90d6c5ffb0cd578dfb614e1ba14c2936b0ae785f30ea0f37da56    "chaincode -peer.add..."    About a minute ago    Up About a minute        dev-peer0.exporterorg.trade.com-tradecc-v0

调用链码

现在我们已经完成了频道的设置,并为交易安装了链码,我们需要实现函数来执行链码调用。我们的代码在invoke-chaincode.jsinvokeChaincode函数中。

调用chaincode的过程与我们进行实例化时相同,代码也类似。调用者必须构建一个包含要调用的chaincode函数的事务提议,以及要传递给它的参数。仅提供chaincodeID(在我们的实现中为tradecc)就足以识别chaincode进程以引导请求:

tx_id = client.newTransactionID();
var request = {
  chaincodeId : Constants.CHAINCODE_ID,
  fcn: funcName,
  args: argList,
  txId: tx_id,
};
channel.sendTransactionProposal(request);

与实例化提议的一个区别是,这个操作通常不需要组织中的管理用户;任何普通成员可能就足够了。这个提议必须发送给足够的背书对等体以收集满足我们的背书策略的正确签名集。这是通过将我们网络中的所有四个对等体添加到频道对象中来完成的(必须与之前的阶段一样创建和初始化频道对象)。一旦提议响应被收集并得到验证,与实例化提议一样的方式,必须构建一个交易请求并发送给订购方:

var request = {
  proposalResponses: proposalResponses,
  proposal: proposal
};
channel.sendTransaction(request);

我们在createTradeApp.js中的测试脚本中调用invokeChaincode。我们希望执行的链码函数是requestTrade,按时间顺序来说,这是应该由进口商角色的用户调用的第一个函数(请回忆我们在chaincode中构建了访问控制逻辑,以确保只有进口商组织的成员可以提交requestTrade):

var invokeCC = require('./invoke-chaincode.js');
invokeCC.invokeChaincode(Constants.IMPORTER_ORG, Constants.CHAINCODE_VERSION, 'requestTrade', ['2ks89j9', '50000','Wood for Toys', 'Importer']);

最后一个参数('Importer')只是表明进口商组织中要提交此交易请求的用户的 ID。在代码中,如果用户已经在 CA 进行了注册,那么将加载该用户的凭证,否则将使用clientUtils.getUserMember函数注册一个具有该 ID 的新用户。

与实例化案例一样,成功的channel.sendTransaction调用只表示订购方接受了交易。只有订阅事件才能告诉我们交易是否成功提交到总账簿。

查询链码

查询链码的实现相对简单一些,因为它涉及到整个网络,但只需从客户端到对等体的通信。

客户端和频道句柄应该如同之前的阶段一样创建,但这次,我们将选择来自调用者(或客户端)组织的一个或多个对等体与频道对象关联。然后,我们必须创建一个查询请求(与调用提议请求相同)并将其提交给所选的对等体:

var request = {
  chaincodeId : Constants.CHAINCODE_ID,
  fcn: funcName,
  args: argList
};
channel.queryByChaincode(request);

在将响应返回给调用者之前,可以收集和比较查询的响应。完整的实现可以在 query-chaincode.js 中的 queryChaincode 函数中找到。我们通过在 createTradeApp.js 脚本中运行 getTradeStatus 链码查询来测试此函数:

var queryCC = require('./query-chaincode.js');
queryCC.queryChaincode(Constants.EXPORTER_ORG, Constants.CHAINCODE_VERSION, 'getTradeStatus', ['2ks89j9'], 'Exporter');

与调用一样,我们指定了一个用户 ID(‘Exporter’)和组织:这里我们希望出口商组织的成员检查交易请求的状态。

由于查询是对客户端及其相关节点的本地查询,因此响应会立即返回给客户端,无需订阅(如调用的情况)。

完成循环 - 订阅区块链事件

正如我们在前几章中所见,权限区块链上的共享账本承诺需要网络节点之间的共识。Hyperledger Fabric 在其 v1 版本中有一个更独特的过程来提交到账本:交易执行、排序和提交过程都是彼此解耦的,并被构建为管道中的阶段,在其中背书者、排序者和提交者独立执行其任务。因此,在 Fabric 方案中,导致区块提交到账本的任何操作都是异步的。我们在中间件中实现的三个操作都属于这一类别:

  • 通道加入

  • 链码实例化

  • 链码调用

在我们对这些操作的描述中,我们停在成功发送请求到排序者的地方。但是,为了完成操作循环,任何使用我们中间件的应用程序都需要知道请求的最终结果,以推动应用程序逻辑向前发展。幸运的是,Fabric 提供了用于异步操作结果通信的发布/订阅机制。这包括用于提交块、完成交易(成功或否)以及可以由链码定义和发出的自定义事件的事件。在这里,我们将检查区块和交易事件,它们涵盖了我们感兴趣的操作。

Fabric 在 SDK 中通过 EventHub 类提供了一个事件订阅机制,相关的订阅方法分别是 registerBlockEventregisterTxEventregisterChaincodeEvent,可以传递回调函数以在中间件层(或更高层)执行操作,每当事件可用时。

让我们看看如何在我们的中间件代码中捕获成功加入的事件。回到 join-channel.js 中的 joinChannel 函数,以下代码为给定的节点实例化了一个 EventHub 对象,其配置从 config.json 中加载。例如,要订阅来自出口商组织唯一节点的事件,我们 fabric-client 实例将监听的 URL(在内部)是 grpcs://localhost:7053

let eh = client.newEventHub();
eh.setPeerAddr(
  ORGS[org][key].events,
  {
    pem: Buffer.from(data).toString(),
    'ssl-target-name-override': ORGS[org][key]['server-hostname']
  }
);
eh.connect();
eventhubs.push(eh);

对于每个块事件,监听器或回调被定义如下:

var eventPromises = [];
eventhubs.forEach((eh) => {
  let txPromise = new Promise((resolve, reject) => {
    let handle = setTimeout(reject, 40000);
    eh.registerBlockEvent((block) => {
      clearTimeout(handle);
      if(block.data.data.length === 1) {
        var channel_header = block.data.data[0].payload.header.channel_header;
        if (channel_header.channel_id === channel_name) {
          console.log('The new channel has been successfully joined on peer '+ eh.getPeerAddr());
          resolve();
        }
        else {
          console.log('The new channel has not been succesfully joined');
          reject();
        }
      }
    });
  });
  eventPromises.push(txPromise);
});

每当接收到区块事件时,代码会将预期的通道名称(在我们的场景中为tradechannel)与从区块中提取的通道名称进行匹配。(区块有效载荷是使用 Fabric 源代码中的标准模式构造的,在protos文件夹中可用。理解和玩弄这些格式留给读者作为练习。)我们将在代码中设置超时时间(这里为 40 秒),以防止我们的事件订阅逻辑无限等待并阻塞应用程序。最后,通道加入的结果不仅取决于channel.joinChannel调用的成功,还取决于区块事件的可用性,如下所示:

let sendPromise = channel.joinChannel(request, 40000);
return Promise.all([sendPromise].concat(eventPromises));

对于实例化和调用,我们不是为区块注册回调,而是为特定事务注册回调,这些事务由事务建议创建时设置的 ID 标识。订阅代码可以在instantiate-chaincode.jsinvoke-chaincode.js中找到的instantiateChaincodeinvokeChaincode函数中找到。后者的代码片段说明了事务事件处理的基本工作原理:

eh.registerTxEvent(deployId.toString(),
  (tx, code) => {
    eh.unregisterTxEvent(deployId);
    if (code !== 'VALID') {
      console.log('The transaction was invalid, code = ' + code);
      reject();
    } else {
      console.log('The transaction has been committed on peer '+ eh.getPeerAddr());
      resolve();
    }
  }
);

传递给回调的参数包括事务句柄和状态码,可以检查链码调用结果是否成功提交到分类帐。一旦收到事件,事件监听器就会被注销以释放系统资源(我们的代码也可能会监听区块事件而不是特定的事务事件,但那样就必须解析区块有效载荷并查找和解释提交的事务的信息)。

将所有内容整合在一起

之前描述的步骤序列可以通过适当编码的脚本一次性运行。如前所述,createTradeApp.js包含这样一个脚本,该脚本导致创建tradechannel,将四个对等体加入该通道,将trade_workflow链码安装在所有四个对等体上,并在通道上进行实例化,最终以进口商向出口商创建贸易请求并跟踪查询请求状态而结束。您可以运行以下命令,然后在控制台上查看各种步骤的执行:

node createTradeApp.js

作为一个练习,也是为了测试中间件库函数和链码,您可以通过开始接受出口商的贸易请求,并最终由进口商向出口商支付完整的货物运输费用来完成createTradeApp.js脚本开始的贸易场景。要查看这个操作,请运行以下命令:

node runTradeScenarioApp.js

用户应用程序 - 导出服务和 API

为我们的中间件创建一组函数的练习为我们可以在其上构建的面向用户的应用程序奠定了基础。虽然我们可以以不同的方式设计应用程序,但它应该提供的功能集合将保持不变。在演示为区块链用户构建应用程序的方法之前,我们将讨论这样一个应用程序应该具备的显著特征。

应用程序

参考图 5.2:Hyperledger Fabric 应用程序的典型三层架构,以及本章的 应用模型和架构 部分的讨论,Hyperledger Fabric 应用程序的不同用户可能需要不同且独特的应用程序。我们的交易场景就是一个例子:代表交易方、银行、承运人和政府部门的用户可能需要我们的应用程序中的不同内容,即使他们共同参与交易网络并认可智能合约操作。

不同组织的管理员必须具备执行的共同操作。这包括从创建通道到实例化链码的各个阶段。因此,如果我们需要为每个网络参与者构建不同的应用程序,我们应该向这些应用程序的每个实例提供这些功能。一旦我们进入应用程序本身,它由链码提供的一组调用和查询功能组成,我们必须为区分留出空间。为交易方及其银行设计的应用程序必须向用户公开交易和信用证操作。然而,并不需要将这些操作暴露给承运人,因此,为后者设计的应用程序可以并且应该限制提供给其的功能,仅限于影响承运人角色的功能,例如创建提单和记录货物位置的功能。

在这里,为了简单起见,我们将所有应用程序汇集成一个,并演示如何使其工作。基于用户角色和要求的多样化留给读者作为一项练习。我们的汇编应用程序将作为一个 Web 服务器实现,松散地连接智能合约和中间件,从最终用户那里发出声音。

用户和会话管理

任何面向服务的应用程序的设计都需要确定可以访问应用程序并执行各种操作的用户。对于 Hyperledger Fabric 应用程序,应该特别考虑用户类别之间的区别。每个 Fabric 网络都有一组特权用户(我们一直称之为组织的管理员)和普通成员。这种角色的区分必须反映在面向用户的应用程序的设计中。

应用程序必须具有身份验证层以及会话管理机制,允许已经通过身份验证的用户行使应用程序,其权限受其角色的限制。 在我们的示例应用程序中,我们将使用JSON Web TokensJWT)来实现这一目的。

设计 API

在构建我们的应用程序之前,我们必须设计一个服务 API,以涵盖我们中间件暴露的功能。 我们将设计我们的 API 为 RESTful,如下所示:

  1. POST/login: 注册一个新用户(管理或普通用户)或登录为现有用户

  2. POST/channel/create: 创建一个通道

  3. POST/channel/join: 将网络对等体加入到此用户会话中创建的通道中

  4. POST/chaincode/install: 在对等体上安装 chaincode

  5. POST/chaincode/instantiate: 在通道上实例化 chaincode

  6. POST/chaincode/:fcn: 调用传递参数的 chaincode 函数 fcn(在请求体中); fcn 的示例包括 requestTradeacceptLC

  7. GET/chaincode/:fcn: 查询传递参数的 chaincode 函数 fcn(在请求体中); fcn 的示例包括 getTradeStatusgetLCStatus

这些 API 函数共同覆盖了 图 5.3:区块链应用程序创建和操作阶段 中的事务阶段。

创建和启动服务

我们将在 Node.js 中实现一个 express(expressjs.com/)web 应用程序,以暴露前述 API。 代码位于我们仓库的应用程序文件夹中,源代码在 app.js 中,依赖项在 package.json 中定义。 作为运行 web 应用程序的先决条件,必须安装依赖项,可以通过运行 npm installmake(参见Chapter 8区块链网络中的灵活性) 在该文件夹中。

以下代码片段显示了如何实例化和运行 express 服务器:

var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var port = process.env.PORT || 4000;
app.options('*', cors());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));
var server = http.createServer(app).listen(port, function() {});

总结一下,启动一个 web 服务器以在端口 4000 上监听 HTTP 请求。 配置中间件以启用 CORS,自动解析 JSON 负载并在 POST 请求中形成数据。

我们的 web 服务器通过不安全的 HTTP 监听请求。 在生产应用中,我们将启动一个 HTTPS 服务器,以与客户端进行安全、机密的通信。

现在,让我们看看如何配置各种 express 路由来实现我们的服务 API 功能。

用户和会话管理

在执行网络(通道)或链码操作之前,用户必须建立经过身份验证的会话。 我们将实现 /login API 函数如下:

  1. 为用户创建一个具有 60 秒过期时间的 JWT 令牌

  2. 注册或登录用户

  3. 如果成功,将令牌返回给客户端

服务器期望提供请求体中的表单数据,以提供注册或登录的用户名和组织名。 管理用户仅由管理员用户名标识。 请求体格式为:

username=<username>&orgName=<orgname>[&password=<password>]

必须提供密码,但只有当 <username>admin 时才需要。在这种情况下,中间件将简单地检查提供的密码是否与用于启动组织 MSP 的 fabric-ca-server 的密码匹配。正如本章前面提到的,我们的 MSP 管理员密码设置为默认的 adminpw

这是一个天真的实现,但是由于 Web 应用安全不是本教程的重点,这足以展示如何在智能合约和中间件上实现服务器和前端。

JWT 令牌创建和用户注册/登录的代码可以在以下 express 路由中找到,在 app.js 中配置:

app.post('/login', async function(req, res) { ... });

读者可以尝试使用会话管理的其他机制,例如会话 cookie,而不是 JWT 令牌。

我们的 Web 应用现在可以进行测试了。首先,使用 docker-compose(或 trade.sh)启动 Fabric 网络,就像本章前面所示的那样。

如果您使用 cryptogen(或 trade.sh 脚本)为组织创建了新的加密密钥和证书,则必须清除中间件用于保存世界状态和用户信息的临时文件夹,否则如果尝试注册在应用程序的上一次运行中使用过的 ID,则可能会看到错误。例如:如果临时文件夹在您的机器上是 network/client-certs,您只需从 network 文件夹运行 rm -rf client-certs 来清除内容。

在不同的终端窗口中,通过运行以下命令启动 Web 应用程序:

node app.js

在第三个终端窗口中,使用 curl 命令向 Web 服务器发送请求,以创建名为 Jim 的普通用户在 importerorg 组织中(这是在 middleware/config.json 中指定的组织名称):

curl -s -X POST http://localhost:4000/login -H "content-type: application/x-www-form-urlencoded" -d 'username=Jim&orgName=importerorg'

你应该看到类似以下的输出:

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4NTQsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6ImltcG9ydGVyb3JnIiwiaWF0IjoxNTI1MDAxNzE0fQ.yDX1PyKnpQAFC0mbo1uT1Vxgig0gXN9WNCwgp-1vj2g","success":true,"secret":"LNHaVEXHuwUf","message":"Registration successful"}

在中间件中,在这里执行的函数是 clientUtils.js 中的 getUserMember,这在本章前面已经讨论过。

要在同一组织中创建一个管理用户,请运行:

curl -s -X POST http://localhost:4000/login -H "content-type: application/x-www-form-urlencoded" -d 'username=admin&orgName=importerorg&password=adminpw'

你应该看到以下输出(管理员用户已经注册,所以这实际上是一个登录调用):

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4OTEsInVzZXJuYW1lIjoiYWRtaW4iLCJvcmdOYW1lIjoiaW1wb3J0ZXJvcmciLCJpYXQiOjE1MjUwMDE3NTF9.BYIEBO_MZzQa52_LW2AKVhLVag9OpSiZsI3cYHI9_oA","success":true,"message":"Login successful"}

在中间件中,在这里执行的函数是 clientUtils.js 中的 getMember

网络管理

正如你在 app.js 中所看到的,从通道创建到链码实例化的 API 函数都被实现为 express 路由:

app.post('/channel/create', async function(req, res) { ... });
app.post('/channel/join', async function(req, res) { ... });
app.post('/chaincode/install', async function(req, res) { ... });
app.post('/chaincode/instantiate', async function(req, res) { ... });

要使用这些路由,最终用户必须以管理员身份登录并使用返回的令牌。根据上一次调用的输出,我们可以如下请求通道创建:

curl -s -X POST http://localhost:4000/channel/create -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4OTEsInVzZXJuYW1lIjoiYWRtaW4iLCJvcmdOYW1lIjoiaW1wb3J0ZXJvcmciLCJpYXQiOjE1MjUwMDE3NTF9.BYIEBO_MZzQa52_LW2AKVhLVag9OpSiZsI3cYHI9_oA"

注意,授权头的格式是 Bearer <JWT 令牌值>。Web 服务器隐式假定通道名称为 tradechannel,这在 middleware/constants.js 中设置。(如果愿意,您可以扩展服务器 API 以接受请求体中的通道名称。)如果一切顺利,输出应该如下:

{"success":true,"message":"Channel created"}

管理员可以运行类似的查询以加入通道、安装链码和实例化链码。例如,实例化 API 端点预期的链码路径、链码版本和链码参数列表如下:

curl -s -X POST http://localhost:4000/chaincode/instantiate -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4OTEsInVzZXJuYW1lIjoiYWRtaW4iLCJvcmdOYW1lIjoiaW1wb3J0ZXJvcmciLCJpYXQiOjE1MjUwMDE3NTF9.BYIEBO_MZzQa52_LW2AKVhLVag9OpSiZsI3cYHI9_oA" -H "content-type: application/json" -d '{ "ccpath": "github.com/trade_workflow", "ccversion": "v0", "args": ["LumberInc", "LumberBank", "100000", "WoodenToys", "ToyBank", "200000", "UniversalFreight", "ForestryDepartment"] }'

如果一切顺利,输出将是:

{"success":true,"message":"Chaincode instantiated"}

在每个路由的实现中,都会检查用户(由 JWT 令牌标识)是否是管理员用户,如下所示:

if (req.username !== 'admin') {
  res.statusCode = 403;
  res.send('Not an admin user: ' + req.username);
  return;
}

如果我们要使用 Jim 注册的用户令牌,Web 服务器将向客户端返回403错误代码。

运行应用程序

一旦由管理员用户初始化了链码,我们的应用程序就开放了。现在,任何普通用户(例如进口商组织中的 Jim)都可以请求链码调用或查询。例如,可以发出交易请求如下:

curl -s -X POST http://localhost:4000/chaincode/requestTrade -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4NTQsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6ImltcG9ydGVyb3JnIiwiaWF0IjoxNTI1MDAxNzE0fQ.yDX1PyKnpQAFC0mbo1uT1Vxgig0gXN9WNCwgp-1vj2g" -H "content-type: application/json" -d '{ "ccversion": "v0", "args": ["2ks89j9", "50000","Wood for Toys"] }'

注意,请求体中必须提供链码版本。如果一切顺利,输出将是:

{"success":true,"message":"Chaincode invoked"}

随后,可以查询交易的状态(再次由Jim):

curl -s -X GET http://localhost:4000/chaincode/getTradeStatus -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4NTQsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6ImltcG9ydGVyb3JnIiwiaWF0IjoxNTI1MDAxNzE0fQ.yDX1PyKnpQAFC0mbo1uT1Vxgig0gXN9WNCwgp-1vj2g" -H "content-type: application/json" -d '{ "ccversion": "v0", "args": ["2ks89j9"] }'

现在,输出应包含链码响应:

{"success":true,"message":"{\"Status\":\"REQUESTED\"}"}

用户/客户端交互模式

虽然运行 curl 命令足以测试我们的 Web 应用程序,但向用户公开应用程序的适当方式是通过一个或多个网页,其中包含用户触发这些命令的小部件。

正如我们在中间件实现部分看到的,包括链码调用在内的各种操作都是异步的。在我们的实现中,我们通过使包装函数返回给调用者来遮蔽了这种异步行为,但仅当请求已成功发送到订购者并且已收到并验证了已订阅的事件时才返回。我们还可以选择将此异步行为暴露给 Web 应用程序客户端。使用 Web Sockets (developer.mozilla.org/en-US/docs/Web/API/WebSockets_API),当事件通知到达与事件中心注册的回调时,向最终用户呈现的 Web 界面内容可以动态更新。

设计良好的 Web 界面超出了本书的范围,读者可以利用其他知识源来构建适合其应用程序的界面。

测试中间件和应用程序

我们已经展示了如何使用示例脚本和curl命令来执行基于 Node JS 的中间件和应用程序功能。通过观察控制台输出,您可以了解应用程序是否按预期工作。对于生产应用程序,您将需要一种更健壮和可维护的测试方法,以持续评估库函数和 API 端点的正确性。单元测试和集成测试都应该是您评估过程的一部分。这样的测试的实际演示超出了本章的范围,编写单元测试和集成测试留给读者练习。Mocha 是一个功能丰富的 JavaScript 框架,用于异步测试(mochajs.org/),可以用于此目的。

与现有系统和流程的集成

在与客户讨论端到端解决方案时,我们经常解释说,与整体足迹相比,与区块链相关的组件所占比例很小。这仍然是一组非常重要的组件,但它们代表的足迹很小。

本节将重点介绍我们传统系统与 Hyperledger Fabric 和 Composer API 之间的接触点。

我们将探讨我们已经利用的各种集成模式,并查看一些非功能性要求如何影响集成部署。最后,我们将探讨一些在设计集成层时集成者需要牢记的其他考虑因素。

简而言之,在本节中,您将:

  • 了解集成层的设计考虑

  • 查看集成设计模式

  • 探索非功能性要求对集成的影响

设计考虑

到目前为止,您已经有了使用 Fabric SDK 的经验,到第七章结束时,一个商业网络示例,您将体验使用Composer REST网关。虽然这些确实是集成的主要工具,但它们是生态系统的一部分,并且企业的业务流程需要进行调整,以确保集成是有意义的。

根据设计考虑,我们将考虑以下几个方面:

  • 去中心化的影响

  • 流程调整

  • 消息关联

  • 服务发现

  • 身份映射

去中心化

曾经有很多尝试标准化 IT 功能和能力,但现实是没有两个组织拥有相同的 IT 景观。即使对于那些选择了相同的 ERP 供应商的组织来说,系统也会被定制以满足组织的流程和需求。

这意味着在规划集成设计时,您应该记住每个组织可能都有自己调用智能合约的方式,可能没有相同的 IT 能力或政策。

例如,通过 Web Socket 公开事件可能对熟悉基于云的技术的组织有意义,但其他组织可能没有这方面的技能,或者其 IT 安全策略可能不允许他们使用该协议。

尽管对一些人来说可能令人惊讶,但请记住,一个网络可以是财富 500 强组织和初创企业的混合体。想象一下供应链行业;您会发现一些几乎没有 IT 基础设施的卡车公司,一直到行业巨头。显然,一刀切可能不适用于所有情况。

话虽如此,从网络的角度来看,您应考虑网络希望为加入组织提供的支持程度。有两种可能的方法:

  • 网络提供集成资产:这可以采用每个参与者在其自己的基础设施中部署的网关形式。网关对每个人都是标准的,并以一致的方式管理智能合约的调用。

    这可以提供加速入职流程的好处,但需要考虑谁拥有、管理和支持这个 IT 组件。此外,由于信任问题,一些组织可能不希望部署此基础设施。

  • 每个参与者构建自己的集成层:这种方法的明显缺点是所有参与者都要重新发明轮子,但它减少了通过在每个组织中部署一个公共组件所创建的潜在支持问题。

    对于需要深度系统集成以实现流程优化效益的用例,这也可能是首选方法。

流程对齐

集成层将不得不处理两种不同的观点:

  • 组织 IT 系统和业务流程视角:组织业务流程可能托管在诸如 SAP 等 ERP 中。在这种情况下,当特定的业务事件需要调用智能合约时,可能会通过 SAP 系统的 业务 APIBAPI)调用来发出。来自 ERP 的 API 调用可能包含各种数据结构,其中一些与区块链网络完全无关。

  • 智能合约视角:这个视角具有数据表示是应用程序不可知的特点。这意味着网络的所有参与者都将理解正在处理的数据的性质。

它取决于集成层来协调两者,并确保在两个系统中保持正确的交易语义。这可能意味着:

  • 映射:将数据从一个字段名移动到另一个字段名

  • 转换:基于输入聚合、拆分或计算新值

  • 交叉引用:利用参考表将应用程序特定代码映射到网络识别的值

这里的观点是,即使你的网络同意使用第七章中介绍的 Hyperledger Composer REST 网关,《业务网络示例》,每个参与者仍然需要做一些工作,以确保集成适应组织的整体业务流程。

消息亲和性

虽然这通常不是一个讨论的问题,但忽视它可能会导致严重的问题,通常会在集成或性能测试过程中出现。

当系统发出一系列相互依赖的交易,并且这些交易在短时间内发出时,我们将其称为消息亲和性。因为每个交易是分开发出的,所以它们可能以与客户端发出时不同的顺序进行处理。

结果可能是不可预测的,就像下面的例子所示。为了更具体,让我们看一个将发出三个单独事务的订单过程,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5.4:按顺序处理服务请求

由于服务提供者是多线程的,所以处理顺序可能因当时的负载而有所变化。一个潜在的结果如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5.5:潜在的服务处理结果

如果第一个正在处理的项目顺序错误将被拒绝,因为订单对象还没有被创建。然而,后续的两个对象将成功,并且将系统留在订单记录为单个项目而不是两个项目的状态。

面临的挑战是很难进行故障排除。一个不知情的开发人员可能无法在他/她的开发平台上重现这种行为。

现在,你可能会想,这与区块链和 Hyperledger Fabric 有什么关系?考虑到 Fabric 交易是异步处理的,并且它们根据每个世界状态进行验证,这种情况可能会发生。客户端将发出事务,并且可能会异步收到一条消息,说事务无效,因为它与世界状态不相符。

这里的故事教训是,在设计 API 时,确保它们在完全描述业务事件的粒度级别上。过多的细粒度交易只会导致消息亲和性、增加的延迟和可能出现的问题,就像这里描述的那样。

服务发现

在集成到 Hyperledger Fabric 和 Composer 的上下文中,服务发现的概念着重于记录和向调用应用程序暴露来自 Fabric 的工件:CA、对等体和排序器。

正如我们现在所经历的那样,为了让应用程序获得认可的交易并添加到分类账中,需要与这些类型的众多组件进行交互。有一种方法将此信息作为服务配置元素维护将使团队能够快速适应网络的不断发展。

目前,在使用 Fabric SDK 开发客户端应用程序时,开发人员负责管理和应用此服务配置。

Hyperledger Fabric 路线图的一部分是促进这种配置。

依赖于Composer REST网关等组件的一个好处是网关提供了服务发现。具体来说,正如您很快会发现的,它提供了一个包含身份信息和连接配置文件的商业卡,其中列出了可用于执行交易的 Hyperledger Fabric 服务。

身份映射

身份映射是将个人或组织的身份转换为网络上可识别的身份的过程。

从业务网络视角看待解决方案时,需要识别的身份的粒度是多少?其他组织是否会在乎 ACME 的 Bob 或 Ann 发起了交易?在大多数情况下,答案会是否定的。知道交易是由 ACME 发起的将是足够的。

你可能会想为什么。这直接与信任的概念有关。还记得第一章中介绍的概念,区块链-企业和行业视角;区块链解决了时间和信任的问题。了解信任问题的根源有助于我们理性地确定应该用于在网络上交易的身份。在大多数情况下,我们的经验是信任问题发生在组织之间。

如果想象一个银行客户通过其银行门户进行交易的用例,客户不会关心后端系统;他们信任他们银行的安全系统。

虽然如此,有些情况下需要进行身份映射:

  • 通过组织的集成层进行交易的商业合作伙伴

  • 具有不同权限级别的不同部门

  • 具有不同角色的用户具有不同的访问权限

在这种情况下,集成层将需要将传入的凭据(API 密钥,用户 ID 和密码,JTW 令牌等)转换为 Hyperledger Fabric 身份。

在使用 Hyperledger Composer REST网关时,可以配置它来支持多个用户。服务器利用节点密码框架来管理此身份验证。这提供了支持不同模型(例如用户 ID/密码,JWT 和 OAUTH)的灵活性。

客户端一经验证通过服务器,还有一个额外的步骤,即将 Hyperledger Composer 商业卡加载到服务器的用户存储库中。客户端与服务器之间需要有隐式信任,因为商业卡包含私钥。

集成设计模式

现在我们将看一些我们在行业中见过的可行集成模式。这个列表并不是详尽无遗的,考虑到我们仍处于 Hyperledger Fabric 和 Composer 解决方案的早期阶段,我们预计随着人们和组织对技术变得更加熟悉,将会出现新的模式。

企业系统集成

在这个类别中,我们考虑任何组织在加入网络之前存在的现有系统。因此,这些系统有着自己的概念和范式,我们将需要一种形式的抽象来协调这两个世界。

与现有记录系统集成

以下是一张图来说明将区块链网络集成到现有记录系统中的过程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5.6:将区块链网络集成到现有记录系统中

大多数寻求加入业务网络的大型企业最终将目标定在集成其记录系统,以确保他们从实时透明的交易分配中受益。在这种情况下,我们之前提到的流程对齐将非常重要。

如前图所示,该方法将利用适配器模式来充当两个世界之间的数据映射器。适配器将采用企业系统应用程序协议和数据结构来接收交易请求。可选地,它还可以利用现有基础设施,如消息服务,来传播分类帐事件。

这里需要注意的重要事项是,这种类型的集成将针对一个组织,很少有可能重用。

作为此模式的一种变体,一些组织将适配器分解为两部分:

  • REST 网关:暴露与 Fabric 智能合约对齐的 REST 接口

  • 集成总线:映射字段并连接企业系统

虽然在这种变体中,重用率更高,但相同的考虑只是向下移动了一层。

与运营数据存储集成

这里有一张图,展示了将区块链网络集成到运营数据存储中的过程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5.7:将区块链网络集成到运营数据存储中

通常,组织正在寻找方法对其分类帐中的信息进行分析。然而,对组织的对等方发出多个/大型查询将仅影响系统的在线性能。通常,在企业系统设计中公认的方法是将数据移动到运行数据存储中。然后可以轻松查询数据。可以通过使用不同的数据来源使数据进行丰富处理,从而创建数据的附加视图。

在这种模式下,事件监听器订阅了 Fabric 组织的事件。因此,它可以接收组织有权访问的所有通道的交易。如果数据的完整性保护很重要,事件监听器可以计算每条记录的哈希值,并将其与记录一起存储。

你会注意到该模式还考虑了一个名为syncAll的函数,它将允许事件监听器与世界状态的最新视图重新同步数据存储。请记住,这个syncAll函数的实现需要谨慎进行,并且很可能需要支持结果集的分页。

微服务和事件驱动架构

下图说明了区块链应用的微服务和事件驱动架构:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5.8:区块链应用的微服务和事件驱动架构

我们将此模式标记为微服务和事件驱动,因为这是这些类型架构中最常见的模式。然而,该模式的特殊之处来自于网关。这样的系统不会执行任何数据映射;它将利用共同的通信协议(HTTP)和数据格式(通常为 JSON,但也可以是 XML)。此外,服务已经设计为理解所进行交易数据的语义也是一个期望。事件也通过相同的协议和数据格式传播。

再次强调,微服务应用往往是较新的应用程序,并且会从更细粒度的接口受益。因此,它们 tend to evolve more quickly and be in a position to adapt and adhere to the transactions from the network. Similarly, event-driven applications will benefit from their low coupling to the other components of the system, and so are good candidates for this pattern.

考虑可靠性、可用性和可维护性

软件或硬件组件的故障对于任何工业应用来说都是不可避免的事实,因此您必须设计您的应用程序能够抵御故障并最大程度地减少停机的可能性。我们将讨论业界广泛使用的三个关键指导方针,以构建和维护系统,并简要审视它们如何适用于使用 Fabric 或 Composer 工具构建的应用程序。

可靠性

可靠的系统是在面对故障时确保正确运行的系统,概率很高。这涉及以下几点:

  • 系统的持续自我监控

  • 组件中故障或损坏的检测

  • 修复问题和/或切换到正常工作的组件

虽然行业中已经发展出了各种实践以确保可靠性,但冗余和故障转移通常(甚至普遍)被使用。

在我们在第一部分构建的 Fabric 应用程序的背景下,这具有某些含义。请回想一下,Fabric 有许多不同的组件,它们必须协同工作(虽然是以松散耦合的方式)以确保成功运行。排序服务是其中一个关键组件,如果它失败了,将会完全停滞事务流水线。因此,当构建生产版本的比如说我们的交易应用程序时,您必须确保排序器具有足够的冗余性。在实践中,如果您的排序器是一个 Kafka 集群,这意味着必须确保有足够的 Kafka 节点(broker)来接管应付一个或多个失败的情况。

同样,对于背书和承诺的对等体的可靠性对于确保交易完整性至关重要。虽然区块链作为共享复制账本被设计为在对等体故障时具有一定的健壮性,但其易受攻击的程度可能因应用程序而异。如果一个背书对等体失败,并且其签名是满足交易背书政策所必需的,那么就无法创建交易请求。如果一个背书对等体行为不端,并且产生了错误的执行结果,那么交易将无法提交。在任何一种情况下,系统的吞吐量都会降低或降为零。为了防止这种情况发生,您应确保在每个组织内建有足够的冗余,特别是对于满足背书政策的关键对等体。以下图示了一种可能的机制,通过该机制,交易提案将被提供给多个对等体,并且使用多数规则丢弃缺席或错误的响应:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5.9 可靠交易背书的冗余对等体

从系统获得的可靠性水平取决于投入到监控和故障转移的资源量。例如,前述图中的五个对等体足以应对两个对等体的失败,但现在这需要比我们在示例网络中使用的对等体多四个对等体。为了确定并确保您的网络产生预期的可靠性水平,您需要在一段时间内对您的完整系统进行集成测试。

可用性

可用性标准与可靠性密切相关,但更多关乎确保系统的高概率运行时间,或作为推论,最小化系统停机的概率。与可靠性一样,检测故障节点并确保充足的故障转移是确保您的应用仍将正常运行的关键。确定所需的可用性水平,分配足够数量的冗余和自我纠正组件资源,并在生产环境中进行测试,是确保您的应用能够获得期望性能的必要条件。

可维护性

可维护性易维护性是指您可以在不影响整个系统的情况下更换或升级系统部件的便捷程度。

考虑这样一种情况:您必须在一个或多个订购服务节点上升级操作系统,或者需要在一个组织内替换一个有故障的节点。与可靠性或可用性一样,拥有冗余(或并行)资源,可以让应用操作无缝切换至这些资源,是处理工业规模系统中这类情况的方法。所谓的蓝绿部署是用于这个目的的一种流行机制之一。简而言之,您有两个平行环境(比如,对于订购服务),一个称为蓝色,一个称为绿色,其中蓝色环境正在接收实时流量。您可以升级绿色机器上的操作系统,充分测试它们,然后切换流量从蓝色到绿色。此时,绿色正在提供服务,您可以以同样的方式升级蓝色。

在具有松散耦合组件的区块链应用中,建议为每个组件(订购者、节点和 MSP)都提供蓝色和绿色环境,并分阶段进行升级和测试,或者一次只升级一个组件集群,以减小出错的机会。

摘要

构建一个完整的区块链应用是一个雄心勃勃且具有挑战性的项目,不仅因为它需要各种技能——系统、网络、安全和 web 应用开发等,而且因为它需要跨越多个安全域的多个组织共同进行开发、测试和部署。

在本章中,我们从一个简单的智能合约开始,并以一个准备好驱动贸易场景并在一个防篡改、共享、复制账本中存储记录的四节点区块链网络结束。在此过程中,我们学会了如何设计组织结构并配置 Fabric 网络。我们学会了如何构建一个通道,或者说一个 Fabric 区块链的实例,让网络中的节点加入该通道,并在该通道上安装和实例化一个智能合约,使用 Fabric SDK。我们学会了如何通过 Web 应用程序向最终用户公开我们网络和智能合约的功能,公开服务 API。我们还学会了超级账本 Fabric 交易流水线的工作原理,以及区块提交操作的异步性质如何影响端到端应用程序的实现。

在本章的后半部分,我们学习了可以用来构建行业规模的区块链应用程序的各种设计模式和最佳实践。我们还了解了在将这些应用程序与现有系统和流程集成时需要考虑的因素。最后,我们探讨了运行操作性 Fabric 网络的性能方面,并学习了 CAP 定理以及 Fabric 如何在分布式环境中实现数据一致性。

超级账本平台和工具毫无疑问会随着时间的推移而发展,以满足行业和开发者的需求,但我们在应用构建过程中描述的架构和方法论,以及设计和集成模式,应该会长期作为教育指南。

我们迄今为止的旅程已经带领我们到了超级账本 Fabric 框架的基础。我们已经使用链码并使用 Fabric SDK API 集成了一个应用程序。这些都是必不可少的技能。

在接下来的两章中,我们将探讨一种不同的建模和实现业务网络的方法。

第六章:商业网络

本章介绍并探讨了一个新概念——商业网络。通过理解什么是商业网络以及它们如何运作,您将能够理解区块链技术如何彻底改善它们。特别是,区块链,尤其是超级账本 Fabric 区块链,对商业网络提供了重大好处,因为它彻底简化了将企业联系在一起的信息和流程,既降低了成本,又为网络内的企业创造了新机遇。

我们将看到商业网络的概念允许您通过查看其与之交互的交易对手来分析企业。虽然商业网络是特定于行业的,但单个网络可以用于支持多个用例,并链接到其他商业网络以形成网络之间的网络。

我们将花一些时间介绍商业网络的词汇,介绍关键术语如参与者资产交易事件。然后将这些元素组合起来来定义正在分析的商业问题的行为。我们能够使用业务需求来创建一个技术蓝图,该蓝图可用于实施解决方案。通过本章的学习,您将准备好使用超级账本 Fabric 和 Hyperledger Composer 来实施这些想法,而您将在接下来的章节中做到这一点。

尽管在实施区块链网络之前理解商业网络的概念是必要的,但您会发现它对更广泛的问题很有帮助,例如执行区块链分析、与现有系统集成以及如何构建应用程序和企业架构。从这个意义上说,本章可以单独阅读,而不必在之后实施网络。

在本章中,我们将涵盖以下主题:

  • 商业网络的语言

  • 商业网络的概念

  • 定义商业网络

  • 介绍参与者

  • 介绍资产

  • 介绍交易

  • 介绍事件

  • 实施商业网络

一个充满有意义的活动的繁忙世界

想象一下我们正在一架飞机上飞越一个大城市。我们可以看到工厂、银行、学校、医院、零售店、汽车展厅、港口的船只等等。这些是定义城市的结构。

如果我们仔细观察,就会看到在这些结构内部和之间发生的事情。卡车可能正在向工厂运送铁矿石,客户可能正在从银行提取资金,学生可能正在参加考试——这是一个繁忙的世界!

而且,如果我们能再仔细一点看,我们会看到所有这些人和组织都在与彼此进行有意义的活动。学生从老师那里接受评估,随后这将帮助他们进入大学。银行向可以搬家的客户提供贷款。工厂从原材料中制造组件,然后客户将其组装成复杂的物体。人们从经销商那里购买二手车,他们用来每天上班或度假!

我们可能会对所有这些结构和它们之间的过程的多样性感到惊叹。我们甚至可能会想知道它是如何如此轻松地共同运作的!

然后我们可能会反思所有这些多样化的活动,并想知道它们是否都有共同之处?是否有可重复的模式可以让我们理解所有这些复杂性?是否有一种分辨率,在这种活动看起来都一样?所有这些人和组织,在某种意义上,是在做同样的事情吗?

答案当然是肯定的!接下来的一节将会给我们一个更好的解释。

为什么要有一种用于商业网络的语言?

商业网络是一种思维方式,它允许我们审视所有这些活动,并用非常简单的语言描述它。而且,由于我们试图用一种对区块链有意义的语言来描述世界,而区块链是一种简单的技术,我们期望那种语言的词汇是简单的。在下一节中,你将会看到它就是这样!

但在我们深入探讨之前,让我们问一下为什么我们想要创造一种区块链能够理解的语言?嗯,如果我们可以的话,我们就可以将区块链的所有好处带到用那种语言描述的世界。而且,我们可以简洁地总结这些好处——增加的信任。

增加的信任意味着学生可以向他们的大学展示他们的高中证书,大学可以对资格的真实性感到放心。它意味着银行可以以最低的利率向客户提供贷款,因为它可以对客户的财务状况感到放心。它意味着组件制造商可以为他们的产品收取更高的价格,因为他们的客户可以确信原材料的质量,知道它们的来源。最后,购买二手车的买家可以对他们的购买感到放心,因为他们可以证明它以前只有一个细心的车主!

定义商业网络

我们可以用商业网络的概念总结所有这些想法:

商业网络是一组参与者和资产,经历由交易描述的生命周期。当交易完成时,事件发生。

你可能会想知道这意味着什么。经过所有这些铺垫之后,我们告诉你几个看似简单的句子描述了所有这些复杂性?

简单的答案是肯定的——我们很快会通过更详细地描述参与者资产交易事件来解释。然后,你会发现所有这些丰富的行为都可以用相对简单的语言词汇来描述。

更深层次的想法

实际上,在业务网络背后有一个更深层次的想法——即技术的语言和词汇应与业务领域的语言和词汇紧密匹配,消除业务概念和技术概念之间的重大翻译需求。业务网络通过用与业务相同的语言描述基础技术,摆脱了断开连接的技术概念。这使得更容易思考世界,并更准确地将思想转化为一个完全运作的系统。

在实践上,这意味着虽然我们对业务网络的初始词汇很简单,但它是一个可以随着时间逐渐丰富的语言的开端,只要它描述了发生在现实世界中的细节和细微差别。我们以后会回到这个想法,但现在让我们从了解参与者开始。

介绍参与者

威廉·莎士比亚说,世界是一个舞台,在这个舞台上男人和女人是演员。以类似的方式,业务网络有一个演员阵容——一组相互交互以获得某种形式互惠的演员。我们称这些演员为业务网络中的参与者。例如,教育网络中的参与者可能是学生、教师、学校、大学、考官或政府检查员。保险网络中的参与者可能是投保人、经纪人、承保人、保险公司、保险联合体、监管机构或银行。

参与者的概念对于理解业务网络至关重要。起初,你可能会觉得这个术语有点令人生畏,但实际上没有什么可担心的。理解的关键在于名字——参与者参与了业务网络。我们感兴趣的是他们的行动。用不同形式的词来强调他们相互作用的不同方面:参与者、当事人和交易对手,例如。所有这些形式都源自行动的概念。和往常一样,我们发现诗人对世界运作的方式了解一二!

学会喜欢这个词,因为它是一个开启大门的词!这是你理解业务的创立原则的简称——与谁做生意至关重要。但它比这更重要;确定业务网络中的参与者是确定是否有机会从区块链的使用中获益的第一件事情。你需要先了解人员构成,才能真正了解发生了什么。而且,随着你了解参与者之间的互动,你将能够提高对成为特定参与者意味着什么的理解。

参与者类型

商业网络中有不同类型的参与者,我们将它们分为三大类。令人惊讶的是,我们不会首先描述最重要的类别!

个人参与者

希望这个类别是一个相当明显的类别——教师、学生或银行客户都是个人参与者的例子。无论你把它们称为个体、人员,甚至是人类,这个第一个类别是我们直觉上会认为是参与者的,因为我们将它们与自己联系起来。

你可能认为个人是网络中最重要的参与者。毕竟,企业的存在是为了服务个人,不是吗?是的,他们是,但情况比较微妙。虽然商业网络通常存在是为了满足个体最终消费者的需求,但区块链是一种对于网络中的企业更有价值的技术。这是因为它使它们能够更好地协调彼此的活动,从而降低成本,并为最终消费者提供新的商品和服务的机会。这就是为什么你会听到人们说出类似区块链对于 B2B 比 B2C 或 C2C 更重要的句子——他们试图传达的是商业网络的重大成功是将区块链用作高效和创新的企业间交互的普遍基础。

当然,个人参与者也很重要。企业需要了解他们的最终消费者,而最终消费者通常使用商业网络提供的服务与彼此互动。例如,如果我希望通过银行网络向你转账,我们各自的银行就需要知道我们是谁,以便能够正确验证和路由交易。

最后,一个合理的经验法则是,商业网络中认识的个人要比网络中的企业多。这并不令人太惊讶——只是值得指出这一点,以便你对什么是个人参与者的理解是完整的!

组织参与者

组织参与者是商业网络中最重要的角色。汽车经销商、银行、学校和保险公司都是组织参与者的例子。当我们首次思考特定的商业网络时,我们会识别这些参与者,然后是它们互相以及最终消费者提供的商品和服务。这些组织参与者为商业网络提供基础设施——使其运作的人员、流程和技术。

虽然组织由个体构成,但在概念上与它们是完全分开的。一个组织有着自己的身份和目的。它以非常实际的方式存在,独立于属于它的个体。组织为商业网络提供了一种永恒感。虽然组织内的个体可能随着时间变化,组织内的个体数量可能增加或减少,甚至组织内的不同角色可能来来去去,但组织保持不变;它是一个比任何个人成员的寿命都要长得多的结构。

关于个体与他们的组织之间关系性质的最后一点需要注意的是,是个体执行组织的功能,如个体的组织角色所定义的。当银行向客户提供贷款时,是银行员工代表银行执行的。通过这种方式,个体是组织的代理人,并且个体的角色决定了它可以执行的任务集。例如,学校教师可以给学生布置家庭作业,但需要学校校长来聘请新教师。简而言之,个体代表组织行事,并且代表该组织的权威。

系统或设备参与者

系统或设备参与者代表商业网络中的技术组件。实际上,它们是一种特殊类型的个体参与者,如果你觉得有用,可以这样认为。然而,我们之所以单独将它们列出来,有两个原因。

首先,如今的商业网络中有很多技术组件!例如,有 ERP 系统、支付引擎、预订系统、交易处理器等等。事实上,今天商业网络中的大部分重活都是由这些系统完成的。这些系统与拥有它们的组织相关联,就像我们之前讨论过的个体一样,这些系统代表它们的拥有组织行事——它们也是其代理人。

将区块链纳入商业网络将增加更多的系统参与者,其他参与者(个体、组织和系统/设备)可以与之互动。了解这些区块链系统参与者是很重要的,因为它们将为商业网络提供非常有用的服务!

其次,设备正变得成为业务世界中更为重要的一部分。而且,尽管今天许多设备相对简单,毫无疑问,设备正在获得更多自主特性。我们都听说过预期出现自动驾驶车辆,正是出于这种精神,我们引入设备参与者的概念。逐渐重要的可能是将这些设备视为业务网络中更为积极的角色。因此,虽然我们不指望汽车很快就变得智能(不管那意味着什么!),但将这些越来越自主的设备称为网络中的活跃实体而不是被动实体是有帮助的。

参与者是代理

我们对参与者类型的考察告诉我们一个共同点——它们都拥有一定程度的主动性——它们积极地做事情。虽然系统和设备的自主程度受其程序和算法的限制,但将它们这样地看待仍然是有帮助的。而且,这些相对自主的参与者之间的交互作用成为业务网络中下一个概念的引导,即资产。我们稍后会看到,在参与者之间移动的实体——资产——没有这种自主性。这些都受到参与者施加在它们身上的力量的影响。稍后再详述此点。

参与者和身份

最后,但非常重要的是,参与者有身份。例如,一个学生有学生证,一个司机有驾驶执照,一个公民有社会保障号码。显而易见,参与者和用于识别参与者的东西之间有区别。而且,将这两个概念紧密联系但分开是非常重要的。

例如,参与者可能有不同的身份参与不同的业务网络——可能是同样的银行参与了保险网络和抵押网络,但它在这两个网络中有不同的身份。此外,即使在一个单一网络中,参与者的当前身份可能会被 compromise,允许他们被冒充。在这种情况下,他们被 compromise 的身份将被吊销,并发放一个替代身份供真正的参与者使用,拒绝冒名顶替者,恢复信任。不同身份,但是同一参与者——这就是要传达的信息。

正是因为对冒充的担忧,某些身份会定期被故意过期。例如,X.509 数字证书有一个到期日期,过了这个日期,它们将不再有效。然而,仅仅因为证书已经过期,并不意味着参与者不再存在。

实际上,情况恰恰相反。参与者相对于它的身份的相对永久性意味着它可以用来提供业务网络中谁做了什么的长期历史参考。参与者随时间提供的身份的一致性帮助我们推理业务网络中的交互历史。我们可以不使用参与者的概念就做到这一点——只使用身份,并清楚地了解它们如何以及何时相互之间改变,但这将不那么直观。

关于参与者的内容就到这儿了;你现在是专家了!正如你所看到的,参与者可能是业务网络中最重要的事情,这就是为什么我们花了相当多的时间讨论它们。现在让我们把注意力转向在参与者之间流动的对象,即资产。

介绍资产

我们已经看到业务网络是由在其中运作的参与者定义的。这些参与者是在网络中进行有意义交互的积极主体,他们的交易至关重要。现在我们要问自己一个问题,*参与者之间流动的是什么?*答案很简单,是资产。

要理解我们所说的资产是什么,让我们看一些例子。我们注意到,学生从导师那里接收课程作业。同一个学生随后可能向大学展示他们的教育证书。汽车经销商向买家出售一辆汽车。保险公司为投保人提供汽车保险,签发一份保单。投保人提出索赔。这些例子都包含了资产:课程作业、教育证书、汽车、保单和索赔。

资产在参与者之间流动

我们可以看到,资产是在参与者之间流动的对象。而参与者有相当大的自治度,资产则相当被动。这种资产的性质是基础的——资产往往对于交换它们的交易对手来说意义重大。这并不是说其他参与者对这些资产不感兴趣,但这确实强调了资产的被动性质。那么,是什么让资产如此重要呢?我们为什么要讨论这些被动的对象呢?

答案就在我们选择的词——资产。资产是一个有价值的东西。尽管资产相对被动,但它们代表着在参与者之间交换的价值。再用这种基于价值的视角看一下这些示例资产:课程作业、教育证书、汽车、保单和索赔。课程作业对老师和学生有价值;教育证书对学生和大学有价值;汽车对经销商和买家有价值;保单对保险公司和投保人有价值;索赔对申请人和保险公司有价值。希望现在清楚了为什么资产很重要,以及为什么它们被称为资产!

作为一个小注,不要认为因为我们有资产,我们就必须有负债——我们并不是完全按照这种方式使用这个术语。完全正确的是,如果我们要将对象作为为我们服务或反对我们的对象进行计量,我们将把它们称为资产或负债,但这不完全是这里发生的情况——我们使用资产作为具体名词,而不是作为质量或抽象名词。

有形资产和无形资产

让我们通过考虑有形和无形资产来继续了解资产。有形资产是我们可以触摸和感受的东西——汽车、纸币或课程作业。无形资产是诸如抵押贷款、知识产权、保险单和音乐文件等东西。在一个日益数字化的世界中,我们将会看到更多的无形资产。你会听到人们说物体正在变得非物质化,而无形资产的概念很好地捕捉了这个概念。

几个小点需要注意,以避免混淆我们对无形一词的使用。首先,由于我们正在处理数字分类帐,在某种微不足道的意义上,区块链上的一切都是无形的。有趣的是对象本身的性质——使用无形这个词可以帮助你记住要注意在物理世界中看不见的东西。

其次,使用无形一词并不意味着对价值的陈述。在会计系统中,当我们难以定义某物时,例如商誉,我们经常使用这个术语。同样,我们并不是用这个词的这个意义;我们的无形资产比这更具有具体、明确和可交换的形式,因为它们是有价值的东西,即使你不能触摸它们。

资产的结构

现在让我们重新聚焦,看看资产的结构。资产具有一组称为属性的属性集和一组称为关系的属性集。属性属性易于理解——它们是对象的特征。例如,汽车有制造日期、颜色和发动机尺寸。或者,抵押贷款有价值、寿命和还款计划。特定资产由特定的一组属性标识。例如,我的汽车可能是 2015 年生产的,是白色的,有 1.8 升的发动机。另一个例子——你的抵押贷款可能价值 10 万美元,寿命 25 年,并且每月付款。区分这种差异很重要——在资产的结构一般类型和资产的特定 实例之间。

第二,资产还有一组被称为关系的属性。关系是一种特殊类型的属性—它是对另一个资产的引用!你可以立即看出这为什么很重要。例如,一辆汽车有一个保险文件。汽车是一个有价值的对象,保险文件也是一个有价值的对象。此外,保险文件命名了一个保单持有人。在我们的例子中,主体和客体都是资产,并且它们以提供基本含义的方式相互关联。

后面我们会看到,描述或建模这些关系是一项极其重要的活动,因为我们在描述世界是如何运作的。在前面的例子中,我们故意犯了一个错误—是的,真的!这是因为在现实世界中,实际上是政策文件是核心,因为它命名了汽车和保单持有人。在建模中,我们称之为关联关系,并且我们将看到为什么正确理解这种事情非常重要。例如,在汽车的性质中,你不会找到保险文件—汽车是在有效的保单文件中命名而被保险的。此外,如果我想要让更多的人驾驶汽车,我会将他们的名字添加到保单文件中,而不是汽车!关于这一点,以后会有更多的讨论—现在,记住资产具有属性和引用,特定对象对这些属性具有具体的值就足够了。

也值得简要提及使资产属性成为属性而不是对另一个资产的引用的性质。一个简单的答案是:当属性变得太时,将它们拆分成资产引用!当然,这是一个非常不满意的答案!为什么呢?因为我没有告诉你什么定义了大!一个更好的答案是,当属性满足单独的关注时,需要引用。这个原则—关注分离—是任何系统中的关键设计原则。例如,对于保险政策,政策的有效日期不是一个单独的关注点,但汽车和命名的驾驶员是单独的关注点。这个原则帮助我们独立地思考保险政策、汽车和驾驶员,从而更真实地建模现实世界。最后,在资产的这个方面,属性和关系属性是领域特定的—它们与手头问题的性质有关。所以,对于汽车制造商,颜色可能是汽车的属性—但对于油漆制造商,颜色绝对是一种资产类型!

所有权是一种特殊的关系。

有一种特定的关系在商业网络中尤为重要,那就是所有权的概念。所有权是一种关联关系,例如我们之前讨论过的保险单文件。让我们考虑一个具体的例子——一个人拥有一辆车。车主是车的属性吗?车是人的属性吗?经过一点思考,我们可能意识到,这两种说法都没有捕捉到拥有某物的含义。所有权是人和车之间的映射。所有权是一个与车和车主完全不同的概念。

了解这种关于所有权的思考方式很重要,因为在许多情况下,我们通过车或车主来建模所有权关系,而这对许多目的来说已经足够了。但是,所有权关系的本质是一种关联关系,意识到这一点很重要——因为区块链经常用于记录商业网络中的所有权和所有权转移。例如,政府经常持有土地或车辆的所有权记录。在这些情况下,考虑的主要资产是所有权关系。当车辆或土地在参与者之间转移时,改变的是这些所有权记录,而不是资产本身。这很重要,因为我们经常对车辆或土地的历史感兴趣,而车辆或土地本身可能不会改变,但它的所有权肯定会改变。因此,清楚地知道我们是在谈论资产的历史,还是在谈论所有权的历史很重要。这些历史通常被称为溯源——它们告诉我们谁拥有了一个资产,以及它如何随着时间的推移而改变。这两个方面都很重要,因为了解资产的溯源可以增加我们对其的信心。

资产生命周期

这种出处的想法自然地将我们带到了资产生命周期的概念。如果我们考虑一下资产的历史,那么从某种非常有意义的意义上讲,资产是被创建的,随着时间的推移而发生变化,并最终停止存在的。例如,考虑一笔抵押贷款。当银行同意向客户借出一笔钱时,它就产生了。它在抵押贷款的期限内存在。随着利率的变化,它根据固定利率或浮动利率确定每月偿还金额。抵押贷款的期限可以在银行和抵押贷款持有人的同意下更改。最后,在抵押贷款到期时,它停止存在,尽管可能会保留其历史记录。如果客户希望提前支付抵押贷款(也许他们搬家了),或者不幸地,如果他们违约了,抵押贷款可能会被提前终止。在某种意义上,我们看到抵押贷款被创建了,期限定期更改了,然后抵押贷款以正常或意外的方式完成了。这种生命周期的概念在业务网络中非常重要,我们将在后面详细讨论它,当我们讨论交易时。

回到资产,我们可以看到在它们的生命周期中,资产也可以被转化。这是一个非常重要的想法,我们考虑资产转化的两个方面——即转化是否涉及分割聚合,以及它是否是均匀不均匀的转化。这些术语听起来有点吓人,但它们非常容易理解,并且最好使用每个方面的一个示例来描述。

在第一个例子中,我们考虑了一颗被开采出来的珍贵宝石。一般来说,一颗开采出来的宝石对于任何珠宝商来说都太大了,无法用于制作单件珠宝。它必须被分割成更小的石头,每个石头可以用于制作单件珠宝。如果我们看一下一颗大的被开采出来的宝石的历史,我们会发现它经历了一个分割的过程。最初的资产是一颗宝石,它被转化为一组更小的宝石,每个宝石都与原始宝石相关。我们可以看到资产转化是均匀的,因为尽管更小的宝石绝对是不同的资产,但它们与原始资产是相同类型的。类似的均匀转化过程经常发生在无形资产中,例如,当一笔大型商业贷款或保险请求被分散到几家公司中以分散风险时,或者当股票被拆分时。

在我们的下一个例子中,我们考虑使用较小的宝石的珠宝商。我们想象他们使用宝石为客户制作精美的戒指。为了制作戒指,他们运用所有的技能将宝石镶嵌在一个通过肩连接到环上的镶座上。珠宝商的手艺值得赞美——他们将一小块银和一颗宝石变成了一件有价值的珠宝。让我们再思考一下正在考虑的资产。我们可以看到金属块和宝石已经被结合或聚合成戒指。我们还注意到戒指是一种不同于作为输入的宝石或银块的资产。我们可以看到这些输入经历了异质转换,因为输出资产是不同类型的。

这些聚合和分割过程在许多资产的生命周期中都可以看到。在制造业生命周期中非常流行,但使用无形资产。例如,我们在并购中看到它,其中公司可以合并在一起,或者收购,其中一家公司通过并入另一家公司而停止存在。分拆分立的反向过程被清晰地描述为资产的分割。

详细描述交易中资产的生命周期

让我们考虑资产如何在它们的生命周期中移动。我们已经学到资产是如何创建、转化和最终停止存在的。尽管生命周期是一个非常有用的概念,但这些步骤似乎有些受限。毫无疑问,对资产在其生命周期中经历的一系列步骤进行更丰富的描述是有可能的。答案是肯定的!交易为描述资产随时间演变提供了丰富的、特定领域的词汇。例如,保险单被要求、完善、签署、交付、申报索赔、支付索赔、失效或续约。这个生命周期的每一步都是一个交易——我们将在下一节中更详细地讨论交易。

最后,与资产一样,参与者可以通过交易来描述生命周期。那么,你可能会想,资产和参与者之间有什么区别呢?嗯,这实际上归结为思考形式与功能。仅仅因为资产和参与者都可以通过交易来描述生命周期,并且同样如此,这并不意味着它们是相同的东西。就像鸟类、昆虫和蝙蝠都能飞行一样,它们绝对不是相关的。在一般意义上,我们认为参与者和资产是资源——它们仅在最一般的意义上相关。

这就结束了我们对资产的讨论!正如我们在话题末尾看到的那样,交易对于描述资产和参与者的生命周期至关重要,所以现在让我们转向这个主题吧!

介绍交易

到目前为止,我们的旅程涉及了理解商业网络的基本特性——即它由参与者组成,这些参与者参与资产的有意义交换。现在让我们专注于商业网络中最重要的概念——交换。

变化作为一个基本概念

为什么交换是最重要的理念呢?嗯,没有它,参与者和资产就没有了目的!

这似乎是一个过度夸张的说法!然而,如果你稍微思考一下,参与者只有在彼此交换商品和服务(统称为资产)的意义上才有意义存在。如果一个参与者不与另一个参与者交换,那么他们在任何有意义的方式下都不存在。资产也是一样——如果它们不在参与者之间交换,那么它们也不存在任何有意义的方式。如果资产在不同参与者之间不移动,那么资产具有的生命周期就没有意义,因为该资产对参与者来说是私有的,在参与者的私人背景之外对商业网络没有任何意义。

变化,因此,是商业网络中的基本原则。当我们考虑交换、转移、商业、买卖、协议和合同时,所有这些激励性的想法都与商业及其变化的影响有关。变化赋予了商业世界动力和方向。我们捕捉变化的方式是通过交易。这就是为什么交易是商业网络中最重要的概念——它定义并记录了变化——资产的变化;资产所有权的变化;参与者的变化。每当商业网络中发生任何变化时,都会有一笔交易来记录。

交易定义和实例

术语“交易”通常有两种密切相关但不同的用法,意识到这种差异很重要。我们有时使用术语“交易”来一般性地描述交易中发生的事情。例如,我们可能定义房地产交易涉及买方支付一定金额给房产所有者,以换取房产的所有权和产权的转移。(几乎总是,买方还获得了随后出售房产的权利。)在这种意义上,术语“交易”用于一般性地描述交换过程,描述参与者和资产涉及的交换过程。

“交易”一词的另一个用法是描述特定交易。例如,我们可能会说,2018 年 5 月 10 日,黛西从温彻斯特自行车店以 300 英镑的价格购买了一辆自行车。我们在这里使用术语“交易”来描述特定交易的一个实例。这两种用法非常密切相关,上下文几乎总是清楚表明我们在谈论哪一种。

两种用法之间的基本区别在于前者定义了交易的含义,而后者捕捉了交易的特定实例。在现实世界中,我们经常看到交易实例的例子——每当我们进入商店购买商品时,我们都会收到收据!在我们之前的例子中,黛西可能为她的自行车收到了一张收据。收据可能是纸质的,尽管现在它经常发送到我们的手机或电子邮件地址。这张收据是交易的副本——它是黛西对发生的事情的个人记录。自行车店也会保存交易记录的副本,以供他们自己的会计目的使用。

隐式和显式交易

请注意,像这样的交易通常不会看到显式的交易定义;定义被编码在您与之互动的人、流程和技术中。对于像黛西这样的低风险交易,交易定义是隐式的。只有在存在争议时,我们才能了解交易是如何定义的。例如,如果黛西的自行车链条在几天后断裂,她可能合理地期望链条会免费修复,或者自行车会被更换,或者她会退款。这是黛西确定与温彻斯特自行车店的交易的真实性质的时刻。

看起来这种隐式交易定义只有缺点,但事实上并非如此。首先,每个国家的法律都有关于公平交易的显式概念,这将使黛西在进入交易时有合理的期望。在大多数国家,这被称为诸如《货物销售法》之类的东西,它规定了任何商业交易中涉及的所有交易对手的权利和责任。其次,缺乏显式合同简化了黛西和自行车店之间的互动。鉴于在大多数情况下,自行车在购买后都能表现良好一段时间,收据对于大多数实际目的来说已经足够了。每次进行简单购买时重新陈述每个人都知道的真相既费钱又费时。这种简化是人们经常称之为减少摩擦的一个例子。

对于高风险交易或具有特殊条件的交易,情况大不相同——事先明确交易定义至关重要。如果我们再次看 Daisy 的交易,我们会发现,如果存在争议,就会有其他跟进交易—例如,自行车可能已经更换了链条,或在极端情况下,她可能已经拿回了她的钱。我们可以看到,一般来说,我们需要多个条件交易来描述参与者之间对这种交易的满意互动。这意味着如果 Daisy 得到的是一笔抵押贷款,而不是一辆自行车,就有必要指定若干交易和可执行条件。你可能已经听说过一个用来描述这种交易和条件集合的术语——合同

合同的重要性

对于高价值资产,拥有一份合同是很重要的。它定义了一组相关的交易和它们发生的条件。合同通常围绕着特定资产类型展开,并涉及一组明确定义的参与者类型。如果你看一份现实世界的合同,它包括了一系列关于实例和定义的陈述。在合同的顶部,所有的资产和参与者将被列出,并注明了特定的价值—比如 Daisy(买方)、Winchester 自行车(卖方)、300 英镑(价格)、2018 年 5 月 10 日(购买日期)等等。只有在所有这些类型到实例的映射都被列出之后,合同才会在这些类型、交易和在这些类型发生的条件下定义,而不涉及特定实例值。这就是使合同在一开始看起来有点奇怪的地方—但一旦你能看到参与者、资产、交易及其相应价值的结构,它们实际上是相当容易理解的,这种结构使它们更加强大。

签名

在合同中我们最后看到的是其底部——签名!签名在许多方面是合同中最重要的部分,因为它代表了所有交易对手对其中包含的信息达成一致的事实。当然,在现实世界中我们会看到许多签名。Daisy 的商店收据通常上面有她的签名—可以是实物的,或者是通过私钥的数字签名。在简单交易中,商店的签名实际上是隐含的—他们会在品牌收据上放置交易代码,并保留一份副本用于自己的目的—这满足签名的目的。

然而,对于高风险交易,所有交易方都需要明确签署一份合同。更为明确地,为了确保每个交易方都是睁大眼睛参与合同,可能需要一个独立的第三方,如律师、公证人或监管机构,签署合同以验证明交易明确涉及的各方自愿而自由地参与其中。

多方交易处理的智能合约

彻底理解这些概念是绝对至关重要的。它们并不特别复杂,特别是如果你将它们与你每天做的事情联系起来!当涉及理解区块链如何帮助多个交易方创建并就高价值资产相关低摩擦交易达成一致时,我们需要理解这些术语及其重要性,无论是作为独立存在还是相互关联。

现在,当我们看一个商业网络时,我们可以看到它充满了由合同管理的多方交易! 这就是为什么交易是商业网络中最重要的概念;它们定义并捕捉了不同交易方之间有价值资产的约定交换。

现在,让我们来使用一个你在谈论区块链时可能听过很多次的术语——智能合约。它们只是这些概念的数字化表现。智能合约是合同的数字形式——意味着它们可以被计算机系统轻松解释和执行。实际上,所有实现高风险或低风险交易的计算机系统都实现了合同。但是,不同于区块链,这些系统没有内建具有使这些概念转化为技术平台的词汇表。

数字交易处理

正如我们在本章的开头所提到的,这就是基于区块链实现的商业网络的大思想。它们使得从现实世界到计算机系统的转换尽可能简单。尤其是 Hyperledger Fabric 将所有这些想法都表现得非常明确,这样我们就可以轻松地对商业网络进行建模和实现。它保持所有现有概念完整,但是以基本上数字的方式实现它们——使用计算机进程、网络和存储。

交易是商业网络的核心,因为它们作用于资产和参与者。然而,它不仅仅如此。即使我们向商业网络添加更多概念,它们仍然必须始终受到交易的约束。交易性是涉及商业网络所有方面的通用属性。这就像我们在本章前面提到过的能够飞行的能力——商业网络中的每个对象都受到交易的约束,并且必须是交易的主体。

发起交易

暂时停下来,我们可以看到交易通常是由业务网络中的一个参与者发起的。这个参与者通常是从特定服务提供商那里获得服务的消费者。例如,黛西希望在购买自行车时消费 Winchester bicycles 提供的服务。

大多数参与者发起的交易涉及资产状态的变化,但在某些情况下,交易可以涉及参与者状态的变化。例如,如果我通过正式文件更改我的姓名,那么从某种意义上说,被转变的资产就是我——参与者。这强调了交易的核心特性——无论对象如何,它们都捕捉到变化。

交易历史

当我们之前讨论资产的来源时,我们看到资产的历史很重要——它为网络中的参与者提供了信心,并增加了信任。同样,交易历史也很重要,因为它也增加了信任。为什么?嗯,这归结于那些签名。任何变化都必须得到涉及交易的所有参与者的同意,而每个交易中的签名提供了信心,即每个交易方都同意了交换。交易历史更好——它显示了在所有时间点,网络中的每个参与者都同意了每笔交易描述的每个变化!

区块链历史包含一系列交易的顺序。尽管顺序似乎意味着交易按时间顺序发生,但这只是部分正确的。例如,如果我在上午 11 点向我的银行账户存钱,然后在上午 11 点 30 分从我的银行账户支付款项,那么第一笔交易发生在第二笔交易之前是非常真实的。

同样,如果你在上午 11 点向你的银行账户存钱,然后在上午 11 点 30 分支付款项,你的交易就有了明确的顺序。但是,现在让我们问一下,我们的上午 11 点的交易是先发生还是后发生的?或者我们的上午 11 点 30 分的交易呢?即使在某种意义上,我的上午 11 点的交易在你的上午 11 点 30 分的交易之后记录,这是否重要?

交易流

这个例子告诉我们,在讨论交易历史时,交易的依赖性是重要的;依赖于先前交易的交易在其之后记录。对于独立交易流,这种排序就不那么重要了。我们必须小心一点,因为交易有一个讨厌的习惯,就是它们会彼此纠缠在一起。例如,如果你的上午 11 点 30 分的交易将款项支付到我的银行账户,那么两个看似独立的交易流就开始相互干扰了。这意味着我们不能随意延迟交易的记录。

请注意,我们谈论的不是交易的实际发生——在特定时间或特定地点——而是该交易记录在交易历史中。这有点像一本奇怪但全面的历史书,记录了拿破仑在 1800 年前往意大利的行程,同时也记录了 1800 年美国国会图书馆的成立,以及日本人物本居宣长完成的文学作品《古事记传》。重要的是这些事件被记录下来——它们在书中相对于彼此的顺序并不是至关重要的,只要它们大致同时出现即可。

将交易分开到不同的商业网络

这个看似刻意的交易历史例子实际上为我们提供了对商业网络设计的深刻见解——复杂交互网络中所有互动的一条记录并不是一个好主意。这个例子开始说明,将商业网络与特定关注点相关联可能是更好的设计,而不是试图将所有历史记录合并到一个网络中。在我们的类比中,最好为法国历史、美国历史和日本历史各有不同的历史书,并相互交叉参考!

这个想法对于如何处理区块链网络有具体且重要的影响。将商业网络分开成不同的关注点,并将它们链接在一起不仅是良好的设计,而且是必要的设计。这将导致更简单、更易理解、更可扩展、更可扩展和更具韧性的系统。你将能够从小处开始,然后逐步发展,并且有信心无论事物如何演变,你都能应对变化。你将看到 Hyperledger Fabric 明确支持使用称为网络和通道的概念的多个商业网络的想法,我们稍后会更详细地讨论这些内容。

交易历史与资产状态

更详细地审视商业网络历史,我们可以看到一个资产(或参与者)历史的两个元素,即其当前值和导致该值的一系列序列化交易。如果我们按照影响它的所有交易依次从任意时间点进行应用,我们可以生成资产在所有时间点的价值。事实上,我们将交易历史视为一系列在商业网络中不同时间和地点发生的交易事件,从而确定其在任何给定时间点的状态。

我们将在 Hyperledger Fabric 中通过账本世界状态和账本区块链的概念明确表达商业网络的这两个方面。世界状态保存了商业网络中资产的最新值,而区块链则保存了商业网络中所有交易的记录。这使得 Hyperledger Fabric 比其他区块链更强大一些——像它们一样,它记录了区块链中的所有交易。此外,它还计算了资产的当前值,使得非常容易确信你正在处理最新的状态。这些最新的值往往是最重要的,因为它们代表了世界的当前状态。而且,当涉及到发起新交易时,大多数参与者都对此感兴趣。

商业网络作为交易历史

从一个非常真实的意义上来说,我们可以将商业网络视为交易的历史。我们这样说是什么意思呢?好吧,我们已经看到,商业网络由参与多方交易资产交换的参与者组成,这些交易由合同定义。然而,如果我们稍微重新调整一下自己的思路,我们会发现,网络是其交易历史的产物,而这又与发起交易的资产和参与者无法分开。

所有这些概念都是一个整体的组成部分,彼此支持和加强。参与者只是我们理解的第一步——进入商业网络世界的入口。通过学习更多,我们意识到交易实际上是中心,同时也是毫无意义的,除非它们涉及到网络内部的资产和参与者,它们既创造又改变并描述了!正是交易历史将一切联系在一起形成一个连贯的整体,从这个意义上说,它就是商业网络。

监管机构和商业网络

关于一种特殊类型的参与者的最后一句话,这种参与者几乎在每一种商业网络中都很常见——监管机构。大多数商业网络的性质是存在一种角色的参与者,其职责是确保交易遵守某些规则。例如,在美国,证券交易委员会SEC)确保执行涉及证券资产的交易的参与者按照约定的法律和规则进行,使投资者对股票市场充满信心。或者,在英国,驾驶员和车辆许可局DVLA)确保车辆按照英国法律进行适当的保险、税收和交换。另一个例子是在南非,南非食品科学与技术协会SAAFoST)确保涉及农业、食品分销、食品加工和食品零售的交易符合适当的南非法律。

每个商业网络都有某种形式的监管机构来确保适当的监督。简单来说,监管机构确保每个人都按照商业网络的规则来玩游戏。我们可以看到,在所有交易都以数字形式记录在区块链上的商业网络中,监管机构实际上可以更有效地和及时地执行他们的工作。

当然,人们可能会问,如果所有交易对适当授权的参与者都可见,并且能够证明行为的正确性或错误性,那么我们为什么需要监管机构呢?答案是监管机构有权制裁网络中的某些参与者——特别是将他们排除在网络之外,并没收他们的资产或非法交易的资产。这些制裁是网络中最强大的交易,因为它们提供了最终的权力,并且必须仅在极端情况下使用。

祝贺!既然你已经走到了这一步,你真正理解了商业网络的基本性质。更好的是,我们在讨论商业网络时真正需要涵盖的概念只剩下一个:事件。让我们继续讨论你会发现非常有力的商业网络的最后一个方面。

从使用 Composer 设计商业网络的角度讨论事件

到目前为止,我们已经看到商业网络的词汇包含一组紧密联系的概念——参与者、资产和交易。尽管数量不多,但这些概念非常具有表现力——它们包含着重要的想法,有许多方面支持和加强彼此。

并不是说有什么缺失,而是通过添加一个额外的概念,我们将显著增加这个词汇的描述和设计能力。这个最后的概念是事件——混合物中的最后一种成分!好消息是,你可能以前听过这个术语,并且它支持的许多想法是相当明显的。但不要误解,事件是一个非常强大的概念,值得花点时间来掌握——你对这个话题的投资将得到丰厚的回报。

一个通用的概念

我们将事件视为表示特定事实发生或发生的事件。例如,总统抵达澳大利亚,或者股市今天收盘上涨 100 点,或者卡车抵达配送中心都是事件的示例。基本想法似乎相当简单——事件是发生重要事情的时刻。事件代表某种过渡——将世界从一个状态转移到完全不同的状态。这就是事件的本质——历史从一条平滑的线变成了一组连接的点——其中每个点代表一个重要事件。

在商业网络领域,我们可以随处看到事件。发起交易的参与者算作事件。资产经历一系列变化也算作事件。同样地,资产在参与者之间交换也算作事件。资产的生命周期不过是一系列事件的历程!我们现在看到参与者加入和离开业务网络也算作事件。考虑一下交易历史,我们把它看作关于参与者和资产的一系列事件。天哪,一旦我们睁开眼睛,事件真的无处不在!如果我们不小心,这些小空间入侵者会让我们不知所措!

消息携带着事件通知

我们将消息视为事件通知的载体。在现实世界中,我们通过短信、电子邮件或可能是新闻源接收到事件的通知。因此,我们区分事件和其通信。这是一个非常重要的区分,因为它说明我们通过媒介与事件耦合在一起。

让我们现在种下这个想法——我们之后会回到这个想法——即使有一个单独的事件,多个参与者可以通过单独的消息通知。我们看到事件生成者和事件消费者之间存在松散耦合。这一切意味着事件具有稍微难以捉摸的特质——它们稍微抽象的本质使它们难以捉摸,除非通过感知它们的消息。

现在可能需要有点小心了——如果我们过于沉迷于事件,就会失去对重要事情的关注。首先,显而易见的是,我们只需要考虑重大事件——可能会导致某种行动的事件。除了事件之外的一切都只是噪音——我们不需要考虑它。当然,什么算作重大事件将是与领域和问题特定相关的——在金融网络中,股市价格的上涨是重大的,但在教育网络中不是。所以现在,让我们把事件作为一种工具,用于在商业网络中发生重大事情时,当我们需要了解是什么促使参与者行动时。让我们看看如何利用这个工具。

一个例子阐述事件结构

以股票市场事件为例。每当一支股票的价格上涨或下跌,我们可以将其表示为一个事件。例如:

**在 UTC 时间: 2018-11-05T13:15:30:34.123456+09:00 ** 股票 MZK 从 13000 日元上涨了 800 日元

我们可以看到,这是一个关于股票 ABC 在 2018 年 11 月 5 日某个非常特定的时间上涨了 800 日元的事件描述。

就像资产和参与者一样,我们可以看到术语“事件”既可以指事件的类型,也可以指事件的实例。在我们的示例中,我们将类型和实例信息合并在一起。该事件具有类型股票市场 标记,结构包括时间:2018-11-05T13:15:30:34.123456+09:00,符号:MZK,货币:JPY,前值:13000,变化:+800。对于结构中的每个元素,我们显示了此事件的特定实例。从这个事件中,我们可以清楚地看到以结构化形式发生了什么。

事件和交易

我们可以看到,事件与交易密切相关。事实上,因为事件通常描述一笔交易,因此常见到这些术语互换使用。然而,事件描述的活动类别比交易更广泛。具体来说,事件描述了一种变化,而交易则捕获了变化的记录要素。交易往往是外部事件的结果——这是不由特定参与者或资产行动引起的事件。在这种情况下,所产生的交易使用外部事件的信息子集作为输入。但是,事件本身不是交易的一部分,除了在这种有限意义上。这需要一点思考——我们真的在剖析一些微妙但重要的差异。

在可能看起来是矛盾的情况下,交易也可以生成事件!天哪,这似乎变得很复杂!但是想一想——事件只是描述某事发生的情况,有时事件是由交易显式创建的,而不是由任何交易之外的力量引起的。在我们的股票标记示例中,交易可能会生成一个事件,以表示 MZK 股票在单个标记中增长超过 5%!这个事件可能是快速股票上涨,其结构为符号:MZK,增益6.1% ——它是由交易显式生成的。交易体现了业务流程的一部分,即识别和传达高百分比股票变动。这个事件在很大程度上实际上是交易的一部分。

外部事件与显式事件

因此,我们可以看到,事件分为两类——外部事件显式事件。我们不经常将这两个术语视为对立的,但它们完美地描述了业务网络中的两种不同类型的事件。我们的第一种事件类型是外部事件——它是在业务网络外部生成的。此事件将由参与者处理,因此可能会导致交易——不要忘记,只考虑重要事件——会导致行动的事件。对于外部事件,大部分事件内容都被捕获为交易输入,但除此之外的事件内容都不会被记住。如果我们想要保存一个外部事件,我们会生成一个显式交易来执行此操作。

显式事件有所不同。因为它们在交易中生成,所以它们自动成为交易历史的一部分。当交易提交到账本时,这些事件将被释放到网络中 - 在那里它们将被任何对它们感兴趣的参与者消耗。在显式事件的情况下,总账本本身就是事件的产生者。

事件引起参与者采取行动

因此,我们可以看到事件之所以重要,是因为它们标识了导致参与者采取行动的变化!就像在现实世界中一样,当事件发生时,人们和组织会得知它,处理其中的信息,并因此产生行动。我们可以看到,事件为参与者采取行动提供了主要的激励刺激之一 - 通常是通过发起新交易,有时是通过生成新事件。

松耦合设计

现在让我们回到松耦合的想法。事件生产者和事件消费者并不直接了解彼此 - 它们被称为松耦合。例如,当一个参与者被添加到业务网络时,现有参与者不需要联系新加入者介绍自己。相反,现有参与者如果感兴趣,就会监听新的参与者事件。同样,如果一个参与者加入一个网络,它不需要联系每个它感兴趣的人和事物,它只是监听它认为重要的事件 - 这些事件可能会导致它采取行动。我们可以看到事件生产者和事件消费者并不明确地了解彼此 - 他们只知道事件 - 因此,通信可以非常容易地增减 - 它更具可扩展性。

现在我们看到,松耦合是事件和交易之间的一个主要区别。交易明确将参与者绑定在一起 - 在交易中,我们命名所有的交易对手。在事件中,我们根本不知道生产者和消费者之间是否以及如何相关。从设计的角度来看,这意味着我们可以创建一个非常灵活的系统。通过事件,参与者可以以几乎无限灵活的方式相互耦合,这确实反映了我们在现实世界中看到的丰富性。

事件的效用

现在我们知道为什么我们将事件添加到了业务网络的定义中。事件使业务网络几乎具有无限的灵活性。欣赏这一点混沌 - 在某种意义上,这可能是不太可分析的,但没关系。现实世界本来就不可分析 - 事件为参与者之间提供了高效的协调机制,以便重要的变化通过多方交易得到协商和记录。

恭喜!记得业务网络的定义吗?

业务网络是参与者和资产的集合,经历由交易描述的生命周期。当交易完成时,事件发生。

我们意识到这几句话可能比起初显得更加强大——它们描述了一个非常丰富的世界。让我们做一个实例来看看这些想法是如何发挥作用的!

实施商业网络

我们已经在商业网络的世界里游览过了,并且看到了多方参与者之间资产的多方交易处理的重要性——这正是这些网络的生命线。事实上,由于当今商业网络的重要性,已经有大量技术被应用于它们的追求。如果你在 IT 领域工作过一段时间,你可能已经听说过企业对企业B2B),甚至可能已经听说过电子数据交换EDI)[协议]。这些术语描述了企业之间如何交换信息的想法和技术。你甚至可能听说过,或者有过,如 AS1、AS2、AS3 和 AS4 这样的网络协议。这些定义了两个组织之间如何交换业务数据的标准机制。如果你没有听说过这些术语也不要担心——关键的要点是,商业网络在今天在一个非常实质的意义上存在,并且有很多技术应用于它们。

实施商业网络意味着什么呢?嗯,当涉及到诸如汽车或设备或重要文件等有形资产的交换时,区块链会在商业网络中捕获资产、参与者、交易和事件的表示。但是,对于无形资产来说,情况有所不同——在某种意义上,资产的不断去物质化意味着它们在计算机系统内的表示与资产本身一样真实。

去物质化的重要性

考虑音乐的情况。一百年前,音乐可能会记录在黄麻胶上,然后通过一系列技术创新,转移到黑胶唱片、CD、数字迷你光盘。每一步都比上一步便宜,质量更高。但是,大约 25 年前,发生了一些不同的事情!第一个 MP3 格式被引入以支持高保真音频捕获。

这是去物质化的一步,与其他步骤完全不同。是的,它更便宜,质量更高,但至关重要的是,它停止了音乐的物理表现形式。这种去物质化模式越来越普遍——金融产品如债券、证券、互换、抵押贷款等主要是以数字形式表示的。越来越多的文件和表格正在数字化——从飞机和火车票等琐碎的例子,到更重要的教育证书、就业和健康记录。这种向数字化的转变意味着区块链比我们可能想象的更具相关性。

所以,当我们在区块链上实施商业网络时,通常会接近处理商业网络中的实际资产。而且,可以说即使在有形资产的情况下,关于资产的信息和资产本身一样重要!这似乎有些夸张,但请稍作思考。假设你拥有一辆车。这辆车需要加油,需要交税,需要维修和投保。它需要每年检测以确保上路安全性。你的车周围有许多经济活动!这意味着关于车辆的信息非常宝贵—事实上,在汽车的寿命内,总运行成本通常是汽车成本的两倍。也许关于车辆的信息比车本身更有价值!?

区块链在 B2B 和 EDI 方面的益处

区块链可以为跨多个组织的企业对企业B2B)信息处理提供更简单、更全面的方法。而电子数据交换EDI)协议仅关注信息的交换,区块链可以在分类帐中存储数据,通过智能合同处理数据,以及通过共识通信和交换数据。区块链提供了对多方交易处理的整体方法。在区块链中,业务网络中的所有处理、数据和通信都可以从一个连贯的系统中访问。这与传统的 B2B 方法形成对比,传统方法中数据、处理和交换由不同的系统管理。这种分离直接导致了在这些系统之间加入信息的重大处理量,以及整体透明度的缺乏。这个过程被描述为协调—确保业务网络的不同部分之间没有重大差异—这是及时和昂贵的。

我们现在看到了在区块链上实施企业网络的好处。与记录资产的一组不同系统以及在其上运行的不同程序相比,现在有了资产及其完整交易生命周期的共享视图。区块链为资产及其生命周期,参与者、交易和事件提供了明确的共享理解。区块链的这种共享性通过增加透明度而增加了信任,并且彻底简化和加快了处理。例如,组织不必定期与其他对手进行协调,以确保他们的系统吻合—因为在区块链中一切始终吻合。

那么,假设我们希望获得区块链对多方交易处理的益处—我们该如何做呢?这将是我们在本章剩余部分关心的事情—基本的架构方法,但主要是设计工具,您可以使用这些工具在商业网络中实现区块链技术平台。

与区块链进行交互的参与者

首先,哪些参与者与区块链进行交互?首先要说的是,在企业网络中,区块链的主要受益者是持有最多数据的参与者,通常是组织。这并不是说个人不能保存区块链账本的实例,但更有可能的是他们将与管理部分区块链的组织进行交互。实际上,他们甚至可能都不知道自己在使用区块链。在组织内部,虽然是个体使用应用程序与区块链进行交互,但至关重要的是,他们是代表组织行事的——他们是组织的代理人。

同样,当涉及到系统和设备参与者时,设备很少会保存区块链账本的副本。这样,设备更像是个体参与者一样。相反,网络中的系统可以代表组织行事,或者在某些情况下,实际上代表组织。那么,系统代表组织意味着什么呢?如果我们想象一个 B2B 系统,那么组织在网络中看起来真的就像是它的 B2B 网关,实质上,网关就是这个组织。这样看来,一个大型系统与区块链账本实例之间有很密切的关联是有道理的。

通过 API 访问业务网络

组织、个人、系统和设备通过一组业务网络 API 与区块链进行交互。我们马上就会看到这些 API 如何创建,但现在知道区块链网络就像普通的 IT 系统一样被消费。差异在于内部——这些 API 是在区块链基础设施上实现的,最终提供了比实际上可能实现的更简单和更丰富的 API 集。然而,区块链 API 的使用者不需要担心这些——他们只需发出 API,并且所需的服务就会自动执行。正在发生的折衷是,区块链基础设施需要业务网络中的组织之间进行更多的协调。他们必须事先就参与者、资产、交易和事件达成一致,以及它们的发展方向。虽然他们在区块链外部可以唯一地处理、存储和传递信息,但他们必须在区块链上达成一致。这就是折衷:前期一致性换取正常运行时业务流程的极端简化的承诺。

从高层次来看,业务网络 API 很容易理解。在车辆网络中,我们可能有诸如 buyVehicle()insureVehicle()transferVehicle()registerVehicle() 等 API。这些 API 是领域特定的——刚刚提到的 API 与商业票据网络中的 API issuePaper()movePaper()redeemPaper() 完全不同。API 必须是领域特定的,因为这样可以使它们对使用它们的网络中的参与者有意义——这些 API 讲述了参与者的语言。

一个 3 层系统架构

这些 API 在一个非常标准的系统架构内运作。通常,最终用户将在其 Web 浏览器或移动设备上运行一个展示层。这将与应用程序服务器层进行通信,使用根据正在开发的整体解决方案定义的 API。这个应用程序层可能在云中运行,也可能在本地系统上运行。这是应用程序的所有应用逻辑所在的地方,它是由区块链提供的业务网络 API 的消费者。该应用程序可能会执行其他工作,比如访问数据库或执行分析,但从我们的角度来看,它是与区块链网络的交互点。它使用区块链 API,而不是最终设备。总的来说,这些 API 在典型的 3 层系统架构结构中运作,包括展示层、应用程序层和资源管理层。

或者,如果我们有一个设备或系统与区块链进行交互,那么它将没有展示层——它将直接使用应用程序 API 或区块链 API。从某种意义上说,设备是展示层,而系统是应用程序层。同样,这是非常标准的。

Hyperledger Fabric 和 Hyperledger Composer

基本的设计方法同样非常简单。我们使用 Hyperledger Composer 对特定的业务网络中的参与者、资产、交易和事件进行建模。然后,我们使用该模型生成区块链智能合约和账本,实现这些元素,并将其部署到使用 Hyperledger Fabric 创建的区块链网络上。我们还使用 Hyperledger Composer 模型生成一组领域特定的 API,以访问在 Hyperledger Fabric 区块链中操纵它们的交易。正如我们所见,这些 API 将由应用程序代表个人、组织、系统和设备使用。

摘要

在本章中,我们已经介绍了业务网络并对其进行了详细的探讨。通过理解参与者、资产、交易和事件的关键组成部分,我们已经看到,在某种意义上,所有的业务网络都共享相同的关注点。

通过对不同类型的参与者进行分类——个人、组织、系统和设备,我们能够正确描述谁发起捕捉业务网络变化的交易。通过理解资产的概念——有形或无形的价值物品,我们能够描述和理解在参与者之间流动的资源,以及它们表达参与者相互交互的原因。理解参与者和资产使我们能够理解如何在交易中捕捉这些变化。最后,事件的概念使我们能够理解网络发生重大变化的时候,并对其采取行动。

我们花了一些时间讨论这些概念是如何使用 API 消耗的,在接下来的章节中,我们将更加专注于这个方面——如何在一个商业网络的真实示例中展示所有这些想法。我们将特别使用 Hyperledger Fabric 和 Hyperledger Composer,这样你就可以看到如何将这些想法应用于实践。

第七章:一个业务网络示例

在本章中,我们将使用一个真实的示例业务网络,结合我们讨论过的所有概念。具体来说,我们将详细介绍 Hyperledger Composer 信用证示例,以便您了解参与者、资产、交易和事件如何在代码中实现。我们将展示业务网络如何被使用、分析、定义,以及如何使用该定义生成 API、测试它们,并将它们集成到示例应用程序中。这将是一个全面的导览,将让您从概念直接进入实施。我们将使用信用证示例,因为它代表了一个经常在区块链相关讨论中被讨论的众所周知的过程。让我们先讨论一下这个流程,然后看看为什么它被用作宣传样例。

信用证示例

因此,我们来到了我们的示例。Alice,意大利 QuickFix IT 的所有者,希望从在美国经营 Conga Computers 的 Bob 那里购买计算机。Alice 将从她的银行 Dinero Bank 申请信用证,这将被 Bob 的银行 Eastwood Banks 接受作为支付形式。

我们将使用在github.com/hyperledger/composer-sample-applications找到的信用证示例应用程序尝试整个流程。该存储库包含多个业务网络的示例应用程序-我们将使用信用证示例。

安装示例

如果你遵循了第三章中的步骤,使用业务场景做准备,你应该已经完成了所有先决条件。现在,在你的 GitHub 账户中 fork 一个样例应用程序的存储库(github.com/hyperledger/composer-sample-applications),然后使用以下命令将其克隆到你的本地计算机:

cd <your local git directory>
git clone git@github.com:<your github name>/composer-sample-applications.git

导航到相应的目录,并使用以下命令安装信用证示例应用程序。应用程序下载和安装需要几分钟时间:

cd composer-sample-applications

cd packages/letter-of-credit

./install.sh

安装脚本还会在你的浏览器中启动应用程序的演示层。让我们来调查一下。

运行示例

你会看到你的浏览器打开了与网络中不同参与者相对应的选项卡。点击不同的选项卡查看网络中的不同参与者。当我们通过示例工作时,我们将扮演这些角色中的每一个。让我们通过尝试应用程序来走一遍这个过程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第 1 步 - 准备请求信用证

我们首先准备我们的请求:

  1. 在浏览器上选择第一个选项卡-你将看到以下页面:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 您现在是 Alice!您可以看到您的银行和您的账户细节。您可以通过单击“申请”按钮申请一个信用证。试试看!

  2. 您将看到一个页面,您可以在该页面上请求信用证书:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第 2 步 - 请求信用证

这是流程的第一个阶段,您将要求从 Bob 购买计算机的信用证!在每个屏幕的顶部,您都能看到您在流程中的确切位置,例如:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在页面的左侧,您将看到商家的详情 - 阿里斯和 Bob 的联系方式。注意公司名称和账户详情:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们假装作为 Alice 提交一个申请。在屏幕的右侧,您可以输入贸易的细节。假设 Alice 向 Bob 请求 1,250 台计算机,每台价格为 1,500 欧元。该申请的总价值为 1.875M EUR:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

还要注意,Alice 可以根据(她银行的许可)在申请书上选择一些条款和条件。这些是与 Bob 签订合同的重要条款和条件 - 除非这些条件得到满足,否则双方都不会收到货物或付款:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你可以根据需要编辑这些内容,尽管这些过程不会受到影响。

当您准备好进入流程的下一个阶段时,请点击“开始批准流程”按钮:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

恭喜,您刚刚申请了信用证!

第 3 步 - 进口银行批准

这是流程的下一个阶段。在浏览器的下一个标签页上单击。现在您是阿里斯银行 Dinero 的员工 Matias,需要处理她的申请!以下是 Matias 看到的页面:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它显示了来自 Alice 的申请,目前正等待 Matias 的批准。他代表 Dinero 银行行事,并申请批准或拒绝的信用证。我们可以设想,在一个复杂的过程中,Matias 只需要批准无法自动批准的特殊信函。

如果 Matias 点击申请,他将看到与 Alice 请求的内容基本相同的详细信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在我们的场景中,Matias 将批准信用证,流程将继续!选择“接受”按钮,我们将进入下一步:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第 4 步 - 出口银行批准

在浏览器的下一个标签页上单击。现在您是 Bob 银行 Eastwood 的员工 Ella,已被告知阿里斯希望与 Bob 做生意:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个示例对流程稍作创意性处理 - 通常情况下,信函将由 Alice 提交给 Bob。然后 Bob 将其提交给 Ella。然而,我们可以看到,因为每个人都可以预先查看信函,所以流程创新是可能的。我们稍后会详细说明这一点。

我们可以看到,Ella 授权了流程的下一个阶段 - 我们可以看到信函在流程图中的位置。当 Ella 选择了这封信时,她可以看到以下详情:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

请注意货币已经被更改。Alice 必须用美元支付,因为那是 Bob 想要的,但是 Ella 和 Matias 已经就 Alice 和 Bob 的汇率达成了一致,所以每个人都可以使用自己的货币。Alice 将以欧元计价,而 Bob 将以美元支付。

在屏幕顶部,您将看到与流程相关的以下信息。我们可以看到我们在流程中的位置;由于区块链的唯一性,增加的透明度是可能的,尽管不同的组织每个都通过自己的系统托管和批准了流程的各个阶段:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们再次推进流程。Ella 可以通过点击接受按钮来批准该信函:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第 5 步 - 出口商收到信函

在你的浏览器中点击下一个选项卡。你现在是 Bob,你可以看到 Alice 的信用证:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个流程示例中,Bob 可以相当确信 Alice 是值得信赖的,因为他的银行事先告诉了他。如果 Bob 选择这封信,他将被显示其详细信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

希望你现在开始理解这个流程了 - 所以让我们不要再一次详细说明所有细节了!只需注意 Bob 如何因为他可以获得的透明度而增加信任。Bob 接受信函作为支付(点击接受),现在必须将货物发给 Alice!

第 6 步 - 装运

你将会回到 Bob 的初始界面,但是请注意,现在有一个选项将货物运送给 Alice:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

点击发货订单,表示货物已经运送给 Alice:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Bob 现在可以看到,就信用证流程而言,他已经完成了 - 订单已经发货。

但是 Bob 还没有收到付款 - Alice 必须先收到货物才能发生这种情况。请注意 Bob 网页右下角的历史记录。Bob 可以看到他在整个流程中的位置,以及在他收到付款之前需要完成的一些步骤:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们返回到 Alice 的选项卡,继续流程的下一步。

第 7 步 - 收到货物

返回到你浏览器中 Alice 的选项卡:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当爱丽丝从鲍勃那里收到电脑时,她可以点击“接收订单”来表示这一点,并查看信用证。此时,两家银行都能释放付款。让我们转到马蒂亚斯的网页,看看这个过程的下一步。

第八步 - 付款

马蒂亚斯可以看到爱丽丝和鲍勃很高兴,因此付款可以进行。点击马蒂亚斯的初始页面,查看当前信函的详细信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

马蒂亚斯可以看到爱丽丝已经收到货物,马蒂亚斯可以点击“准备付款”来进行下一步。

第九步 - 关闭信函

爱拉现在可以关闭信函并向鲍勃付款:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作为艾拉,点击“关闭”以进入流程的最后一步。

第十步 - 鲍勃收到付款

如果我们返回鲍勃的网页并刷新它,我们可以看到鲍勃有一些好消息!看看他的余额增加了:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

鲍勃现在已经收到了他向爱丽丝出售的电脑的付款。业务流程已经完成。

概括流程

爱丽丝想从鲍勃那里购买电脑,并使用信用证流程来促成这次交易。她以美元购买商品,但以欧元支付。在支付之前,她能够确信货物符合她的条款和条件。

鲍勃向爱丽丝出售了电脑,一个他之前不认识的海外客户。信用证流程使他有信心以他本地货币美元的形式收到他货物的付款,只要爱丽丝对货物满意即可。

马蒂亚斯和艾拉,分别是 Dinero Bank 和 Eastwood Bank 的代表,提供了一个系统,让爱丽丝和鲍勃能够相信彼此将履行相互同意的条件以便收到付款。他们能够为他们的服务向爱丽丝和鲍勃收取公平的价格。他们几乎实时了解业务流程的每一个步骤。

现在让我们看看如何使用 Hyperledger Composer 和 Hyperledger Fabric 实现这个过程。

分析信用证流程

业务网络的核心是一个业务网络定义,其中包含资产、参与者、交易和事件的正式描述。我们将为信用证申请检查这一点。在本章结束时,您将能够了解网络是如何实现和被应用程序访问的。此外,您将具备构建自己的网络和消费应用程序的知识。

操场

如果您转到演示中的下一个选项卡,您会发现 Hyperledger Composer Playground 已经为您打开:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

游乐场是一个工具,可以让您调查商业网络。游乐场的初始视图包含一个充满 商业网络卡钱包。就像一个真实的钱包一样,这些卡可以让您连接到不同的网络。当您使用特定的卡连接到网络时,您就会作为不同的参与者进行操作。这对于测试网络非常有用。让我们作为管理员连接到网络,看看里面有什么!(稍后我们将创建我们自己的网络卡。)

查看商业网络

在标记为 admin@letters-credit-network 的商业网络卡上,点击立即连接。您将看到一个网页:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

商业网络定义视图

这是商业网络定义的视图。它包含我们在信用证网络中讨论的参与者、资产、交易和事件的定义。页面的左侧是一组文件,其中包含与我们连接的网络相关的这些概念的信息。我们选择了 关于,在右侧,我们可以看到商业网络的描述。让我们稍微详细地研究一下这个描述——理解这一点非常重要。

商业网络描述

README 文件包含了对网络的自然语言描述,涉及其资产、参与者、交易和事件。

参与者描述

参与者列在商业网络描述中:

Participants
 Customer, BankEmployee

在我们的示例中,有四个参与者 实例 ——Alice、Bob、Matias 和 Ella。但请注意,这里只有两种参与者 类型,即 CustomerEmployee。在我们的网络中,Alice 和 Bob 是 Customer 类型的参与者,而 Matias 和 Ella 是 BankEmployee 类型的参与者。我们可以看到,这些类型是从银行的角度命名的——因为 Dinero 和 Eastwood 银行提供网络服务,并由 Alice 和 Bob 使用。

我们很快会看到有关这些参与者类型及其网络中特定实例的更多细节。但现在,只需考虑一下我们如何将网络中的参与者简化为两种非常简单的表示。尽管我们在应用程序中看到了丰富的行为,但在参与者方面,网络却非常简单。你将在商业网络中看到这一点——尽管参与者可以有很多实例,但类型的数量通常非常有限,并且很少超过 10 个。当然,规则是用来打破的,但你会发现以这种方式思考网络会让分析变得更加可管理。

资产描述

如果你对这个商业网络中参与者类型数量很少感到惊讶,那么当你看到资产类型的数量时,你会感到惊讶:

Assets
 LetterOfCredit

现在,这是一个示例网络 - 这里是为了教我们关于业务网络概念,而不是对信用证世界的详尽表示。 但是,如果你考虑我们的例子,整个流程主要关注的是一个资产类型:信函

公平地说,我们没有关注正在转移的货物 - 电脑或付款。 在真实的系统中,这些将被描述为资产。 即便如此,请注意资产类型的数量仍然相对较少。 我们可以创建无限数量的信用证实例,计算机和付款,但类型仍然只有几种。

我们稍后将详细了解此资产类型的细节。

交易描述

现在,让我们转移到业务网络中的交易类型:

Transactions
 InitialApplication, Approve, Reject, SuggestChanges, ShipProduct, ReceiveProduct, ReadyForPayment, Close, CreateDemoParticipants

最后,我们可以看到相当多种类型! 这是典型的情况 - 尽管参与者和资产类型的数量相当有限,但资产的生命周期丰富多彩。 如果你考虑我们的应用程序,信用证与网络中的不同参与者交互时会经历许多状态。 这些交易直接对应于这些交互。(忽略CreateDemoParticipants,这是设置演示的交易!)

交易名称相当容易理解 - 这些与信函的生命周期密切相关。 这是您作为不同参与者使用应用程序时经历的步骤。 Alice 进行了InitialApplication,并有可能对信函的条款和条件进行SuggestChanges。 Mattias 和 Ella 可以ApproveReject 信函。 Bob 调用ShipProduct 表示他已完成了他的交易,并且 Alice 使用ReceiveProduct 也是这样表示她已经收到了计算机。 最后,Matias 表示该信函已准备好进行支付,并且 Ella 发出了Close 交易以结束流程并触发向 Bob 的付款。

没有理由交易类型的数量必须大于资产类型的数量。 人们很容易想象许多不同类型的资产,其生命周期相同且相对简单。 想象一下零售商的产品库存 - 商品可以被采购,交付,出售和退货。 这是一个相对简单的生命周期,但是不同类型的商品数量可能相当大。 然而,我们可能期望这些不同的商品通过某种行为的共性共享这种生命周期; 毕竟,它们都是产品。 关于继承的这种想法以后会有更多内容。

我们将更详细地研究这些交易的实现,但目前,最重要的是理解网络中参与者之间资产流动的概念图片,如交易所描述的,而不是担心这些交易变化背后的确切逻辑。

事件描述

最后,让我们看一下业务网络中事件的列表:

Events
 InitialApplicationEvent, ApproveEvent, RejectEvent, SuggestChangesEvent, ShipProductEvent, ReceiveProductEvent, ReadyForPaymentEvent, CloseEvent

我们可以看到事件的名称与交易类型匹配,这是典型的。这些是显式事件,由交易生成,以指示业务网络中发生某些事件的时间。在我们的场景中,它们被用户界面用于保持网页的最新状态,但当然也可以用于更复杂的通知处理,例如,CloseEvent 可以用于触发向 Bob 的支付。

当您首次定义业务网络时,您会发现事件与交易密切相关。但是,随着时间的推移,您会发现更复杂的显式事件被添加,例如,Matias 或 Ella 可能想要为 HighValue 信函或 LowRisk 申请生成特定事件。

我们稍后会详细讨论这些事件的细节。

一个业务网络的模型

现在我们已经理解了自然语言中业务网络中的类型,让我们看看它们在技术上是如何定义的。在 Playground 的左侧,选择模型文件。

在此业务网络中,只有一个模型文件定义了参与者、资产、交易和事件。在更大的应用程序中,我们将保留来自不同组织的信息在其自己的文件中,并且通常在其自己的命名空间中。它允许它们保持分离,但在必要时将它们汇集在一起。让我们看看命名空间是如何工作的。

命名空间

我们的示例使用单个命名空间:

namespace org.acme.loc

此命名空间表示此文件中的类型定义已由 Acme 组织的信用证流程定义。这都是一个简短的名称!使用命名空间——它们将帮助您清楚地分离,更重要的是传达,您的想法。建议使用分层名称,以便清楚地知道网络中哪些组织正在定义网络使用的相关类型。

枚举

接下来,我们看到一组枚举类型:

enum LetterStatus {
  o AWAITING_APPROVAL
  o APPROVED
  o SHIPPED
  o RECEIVED
  o READY_FOR_PAYMENT
  o CLOSED
  o REJECTED
}

这些是信函要经过的状态。当我们访问一封信时,我们将能够使用此枚举标识业务流程的位置。所有的名称都相当自我解释。

资产定义

现在我们来到了第一个真正重要的定义——信用证资产:


 asset LetterOfCredit identified by letterId {
   o String letterId
   --> Customer applicant
   --> Customer beneficiary
   --> Bank issuingBank
   --> Bank exportingBank
   o Rule[] rules
   o ProductDetails productDetails
   o String [] evidence
   --> Person [] approval
   o LetterStatus status
   o String closeReason optional
 }

让我们花点时间来理解这个定义,因为它既是理解业务网络的中心,也是特别关注 Hyperledger Composer 的。

首先,请注意asset关键字。它表示接下来的内容是描述资产的数据结构。这就像正常编程语言中的类型定义,但具有一些特殊特征,我们稍后会看到。

我们可以看到资产是LetterOfCredit类型。在此示例中,我们只有一种资产类型——在更复杂的示例中,我们将拥有更多类型的资产。例如,我们可以扩展此模型以包括 Shipment 资产和 Payment 资产:

asset Shipment
asset Payment 

现在,让我们跳过identified by子句,转到资产定义中的第一个元素:

o String letterId

字母o表示这个字段是资产的简单属性。这是一种略显奇怪的表示方法,所以可以将它看作是一种装饰。第一个属性是letterId。回想一下,当在商业网络中创建一封信时,将为其分配一个唯一 ID。如果回想一下,在我们的例子中,我们有letterId分别是L64516AML74812PM。这是通过字段具有String类型来表示的——有很多类型可用,我们很快就会看到。我们可以看到,这个定义允许我们将一个可读的标识符与资产关联起来。请注意,这个标识符必须是唯一的!

现在让我们回到identified by子句:

identified by letterId

现在我们可以理解,这表明letterId属性是唯一标识资产的属性。这是一个简单但强大的想法,与现实世界密切相关。例如,一辆汽车可能有一个用于唯一标识它的车辆识别号VIN)。

让我们继续下一个属性:

--> Customer applicant

我们注意到的第一件事是-->装饰符!(在您的键盘上键入两个破折号和一个大于符号)。这是一个引用属性——它指向某物!就一封信而言,它指向一个不同的类型,Customer,而这个元素的名称是applicant。看到引用的概念比我们之前看到的简单属性复杂一些——因为它做的工作更多。这个字段表示这封信有一个申请者,是Customer类型,而且你需要通过这个引用查找它。

在我们的示例中,一封信的实例将指向Alice,因为她是 Dinero 银行的客户,她提出了申请。请注意,这个引用属性指向商业网络中的不同对象。这种引用的概念非常强大——它允许资产指向其他资产,以及参与者,参与者也是如此。有了引用,我们能够表示我们在世界中看到的丰富结构。这意味着我们可以创建可以组合和分割的资产,对参与者也是如此。在我们的示例中,我们使用引用来查看谁申请了一封信,通过导航引用。再次看到,这个模型非常以银行为中心。稍后我们将看到Customer实际上是一个参与者,并且我们将看到像 Alice 这样的参与者是如何定义的。但现在,让我们先留在资产定义上。

正如我们在商业网络中所讨论的,我们的应用程序使用了一种简单的方式来建模所有权——在现实世界中,这经常是一种联想参考。我们可以将这种更复杂的联想关系最容易地建模为一个OwnershipRecord,它指向一个资产,并且如果需要的话,指向一个参与者:

asset OwnershipRecord identified by recordId {
   o String recordId
   --> LetterOfCredit letter
   --> Customer letterOwner

我们可以立即看到这种方法的强大之处。我们能够建模现实世界中存在的关系,使我们的应用程序更加现实和易于使用。对于我们的目的,我们当前的模型完全够用。

让我们转向下一个字段:

--> Customer beneficiary

这是与上一个领域非常相似的领域,在我们的示例中,这个元素的一个实例将是鲍勃。没有必要花时间来定义这个概念。当然很重要,但它只是指向鲍勃的信。如果你回忆一下,我们的应用程序总是将两个交易对方与一个信件关联起来。

接下来的两个字段具有类似的结构,但我们将花更多时间讨论它们:

--> Bank issuingBank
--> Bank exportingBank

我们可以看到这些字段也是对其他对象的引用,根据它们的名称- issuingBankexportingBank,我们可能会怀疑它们是参与者!这些类型的示例实例是Dinero 银行Eastwood 银行,它们代表着 Alice 和 Bob。

通过这四个参考字段,我们已经建模了资产的非常丰富的结构。我们表明信用证确实有四个参与者参与其中。我们给它们起了符号化的名称和类型,并展示了它们如何与资产相关联。此外,我们没有编写任何代码就完成了这一点。稍后我们需要做一些这样的事情,但现在,请注意我们在模型中捕捉到了信用证的基本本质。值得花一点时间真正理解这一点。

我们只会考虑资产定义中的另一个字段,因为希望你现在已经掌握了这个!这是一个重要的字段:

o LetterStatus status

还记得文件顶部定义的 ENUMs 吗?很好!这个字段将包含那些不同值,例如AWAITING_APPROVALREADY_FOR_PAYMENT。您在您的业务网络中经常,如果不是总是,会有这样的字段和枚举,因为它们以一种非常简单的形式捕捉了您模拟的业务流程中的位置。如果您熟悉工作流程或有限状态机,您可能想把这些看作状态-这是一个非常重要的概念。

参与者定义

现在我们转向模型文件中的下一组定义:参与者!

让我们看看第一个参与者定义:

participant Bank identified by bankID {
  o String bankID
  o String name
}

这是我们的第一个participant类型定义,一个银行。在示例应用程序中,我们有两个这种类型的实例:Dinero 银行Eastwood 银行

我们可以看到,参与者是通过participant关键字identified by的,随后是类型名称-银行。在这种情况下,参与者类型是一个组织,而不是一个个人。与资产一样,每个参与者都有用于识别的唯一 ID,我们可以看到对于银行来说,它是bankID字段:

participant Bank identified by bankID

对于我们的示例,银行的建模非常简单-只有一个bankID和一个name,它们都是字符串:

String bankID
String name

我们可以看到银行实际上比信件简单得多。不仅仅是因为它们具有较少的字段和更简单的类型。更重要的是,它们不引用任何其他参与者或资产——这就是使它们简单的原因——缺乏引用,简单的结构。你的模型也将是如此——一些资产和参与者将具有相对简单的结构,而其他人将具有更多,包括对其他资产和参与者的引用。

记住这些类型是从资产定义中调用的。如果需要,再次查看信件类型定义,看看引用:

--> Bank issuingBank
--> Bank exportingBank

现在你能看到信件 资产银行 参与者是如何相关联的了吗?太好了!

现在让我们看看下一个参与者类型。它和我们之前看到的有点不一样,现在先忽略抽象关键词:

abstract participant Person identified by personId {
  o String personId
  o String name
  o String lastName optional
  --> Bank bank
}

感觉我们的应用程序中有Person类型的四个实例——Alice 和 Bob,Matias 和 Ella!让我们来看看个体参与者是如何定义的:

abstract participant Person identified by personId

再次,忽略抽象关键词。该语句定义了 Person 类型的参与者,由其类型定义中的唯一字段identified by。这些类型将是我们应用程序中的个体参与者,而不是之前我们定义的组织(即银行)。(我们可能期望BankPerson在结构上相关,我们以后会看到!)

如果我们更详细地看一下定义,就会发现他们的结构比 bank 有趣多了:

o String personId
o String name
o String lastName optional
--> Bank bank

我们可以看到Person也有名字和姓氏。但注意姓氏是optional的:

o String lastName optional

我们可以看到 optional 关键字表示 lastName 可能存在也可能不存在。你可能还记得我们的示例中,Alice 和 Bob 提供了姓氏(Hamilton 和 Appleton),但银行的员工 Matias 和 Ella 没有。这种选择性已经被建模了——看看它是如何帮助我们使应用程序更接近现实世界的。

然而,最重要的字段是接下来的一个:

 --> Bank bank

为什么?它揭示了结构。我们可以看到一个人与银行有关。对于 Alice 和 Bob 来说,这是他们有账户的银行。对于 Matias 和 Bob 来说,这是他们的雇主。我们将再考虑一下,这是否真的是正确的建模关系的地方,但目前重要的是,我们有一个个体参与者与组织参与者有关。你可以看到复杂结构不仅仅适用于资产——参与者也可以有复杂结构!

但等等,这并不是那么简单。在定义中我们跳过了一些东西,是吗?看看下面的内容:

abstract participant Person identified by personId { 

抽象 关键字几乎完全摧毁了我们刚说的关于Person类型的一切!抽象类型很特殊,因为无法实例化。真的吗?鉴于我们可以看到 Alice 和 Bob,Matias 和 Ella,这似乎有违直觉。

要理解发生了什么,我们需要转向下一个参与者定义:

participant Customer extends Person {
   o String companyName
}

仔细看看这个定义的第一行:

participant Customer extends Person {

我们可以看到我们定义了一个称为Customer的特殊Person类型!这比以前更好,因为 Alice 和 Bob 都是Customers。实际上,在我们的应用程序中,我们没有Person参与者的实例 - 我们有Customer类型的实例。

我们现在可以看到Customer类型定义中的extends关键字与Person类型定义中的abstract关键字相配对。它们是我们之前提到的类型特化和继承这一更大概念的一部分:

abstract participant Person
participant Customer extends Person 

abstract关键字阻止我们定义Person的实例!这很重要,因为在我们的例子中,实际上是正确的 - 没有Person类型的实例,只有Customer类型的实例。

我们可以看到一个Customer在扩展Person类型时有一个额外的属性,即他们的公司名称:

o String companyName

对于 Alice,这将是 QuickFix IT,对于 Bob,将是 Conga Computers。

最后,让我们看看最后一个参与者类型,BankEmployee

participant BankEmployee extends Person {
}

我们不需要详细描述这个 - 你可以看到,例如CustomerBankEmployee扩展了Person类型,但不同于它,它没有添加任何额外的属性。这没关系!在我们的应用中,Matias 和 Ella 是这种类型的实例。

我们现在可以看到为什么Person类型是有用的。不仅仅是因为它不能被实例化,它还捕捉到了CustomerBankEmployee之间的共同之处。它不仅仅是节省了打字的工作 - 它展示了一个内部结构,这提高并反映了我们对业务网络的理解。

牢记这一点,你可能会考虑是否以以下方式对模型进行略微更加现实的建模:

abstract participant Person identified by personId {
   o String personId
   o String name
   o String lastName optional
}

participant Customer extends Person {
   o String companyName
   --> Bank customerBank
}

participant BankEmployee extends Person {
   --> Bank employeeBank
}

在现实情境中,实际参与者的身份将被存储在模型之外。这是因为个人身份和不可更改的账本不是一个好的组合。在账本上存储 Alice 的个人信息意味着它将永远存在。

你能看出这个模型如何展示了CustomerBankEmployee之间的银行关系性质有所不同吗?

这里有一个重要点 - 没有所谓的正确模型。模型只是为了服务于一个目的 - 它们要么足够,要么不足够。对于我们的目的来说,我们的两个模型完全足够,因为我们不需要在他们与银行的关系方面区分CustomersBankEmployees

好了,参与者的部分就到这里。让我们继续下一个模型定义中的元素。

概念定义

观察ProductDetail而不是Rule,因为它一开始会稍微容易理解:

concept ProductDetails {
   o String productType
   o Integer quantity
   o Double pricePerUnit
}

概念在模型中是较小但有用的元素。它们既不是资产也不是参与者 - 它们只是定义其中包含的结构元素。

这个前置概念定义了ProductDetail。我们可能会认为这实际上是一个资产 - 对于我们的应用程序来说,它不是参与者之间转移的东西!当我们看到Rule概念时,可能会更清楚一些,它捕捉了信用证的条款和条件:

concept Rule {
   o String ruleId
   o String ruleText
}

这与资产或参与者不太相似,但将其作为单独的类型很有帮助,因为它揭示了一个重要的结构。

交易定义

让我们继续吧!下一节非常重要 - 交易!让我们先从看第一个交易定义开始:

transaction InitialApplication {
   o String letterId
   --> Customer applicant
   --> Customer beneficiary
   o Rule[] rules
   o ProductDetails productDetails
}

我们可以看到,像资产和参与者一样,交易是用自己的关键字定义的:

transaction InitialApplication {

transaction关键字标识接下来的内容是一个交易类型的定义。就像assetparticipant关键字一样。请注意,交易定义中没有identified by子句。

此交易定义代表了 Alice 为信用证所做的初始申请。这实在是显而易见,不是吗?Alice 使用的应用程序会创建交易的特定实例,并且我们可以看到其中包含的信息:

o String letterId
--> Customer applicant
--> Customer beneficiary
o Rule[] rules
o ProductDetails productDetails

如果你回顾一下 Alice 的网页,你会看到所有这些信息:申请人Alice,受益人Bob,条款和条件规则)和产品详细信息。请注意,申请人和受益人是参与者的引用,而规则和产品详细信息则是概念。

我们可以看到,交易的结构相对简单,但却强大地捕捉了一个申请人(例如,Alice)申请与受益人(例如,Bob)做生意的意图。

事件定义

看一下模型文件中的下一个定义:

event InitialApplicationEvent {
   --> LetterOfCredit loc
}

这是一个事件!你经常会看到这种情况 - 事件定义紧邻同名的交易。这是因为这真的是一个外部事件 - 它只是捕捉申请人申请信用证。它只是指向生成事件的信件。在申请中,它只是用于保持用户界面最新,但通常情况下,这个初始申请可能引发各种处理。

继续浏览模型文件,你会看到为流程的每个步骤定义的交易和事件,有时还有与该交易步骤相关的额外属性。花点时间看看这些 - 它们很有趣!

正如我们所见,还可以声明更明确的事件,比如高价值信件或低风险申请。想象我们的应用程序用以下事件来做到这一点:

event highValueLetterEvent {
   --> LetterOfCredit loc
}

 event lowRiskLetterEvent {
   --> LetterOfCredit loc
}

你认为模型文件中的哪些交易会与这些相关联?

要确定这一点,我们需要考虑这个过程 - 一个高价值的信件在申请后立即被知晓,因此它将与 InitialApplication 交易相关联。然而,直到交易被两家银行首次处理,并且申请人和受益人都被评估之前,很难说这封信是低风险的。这意味着这个事件更可能与 Approve 交易密切相关。

此外,在这种更高分辨率的情况下,我们会考虑为进口银行批准和出口银行批准创建单独的交易,ImportBankApprovalExportBankApproval

检查实时网络

很好 - 现在我们已经看到了业务网络中参与者、资产、交易和事件类型是如何定义的,让我们看看如何创建这些类型的实例。Playground 工具还有另一个非常好的功能 - 它允许我们在业务网络运行时查看网络内部,以查看这些类型的实例,并在 Playground 页面顶部选择测试选项卡:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你会发现视图有点变化。在左侧,我们可以看到为这个业务网络定义的参与者、资产和交易:银行银行员工客户LetterOfCredit,以及交易。你可以选择这些选项,当你这样做时,右侧的窗格会变化。试试看!

选择 LetterOfCredit 资产,在右侧窗格上,你将看到以下内容(使用显示所有展开视图):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

哇 - 这很有趣!这是我们申请的一封实际信用证。让我们仔细看看信件,以及它如何映射到我们之前检查的类型结构。

检查信用证实例

我们可以看到 ID,L73021 AM,和实例信息。它显示为 JSON 文档,你可以看到结构与 LetterOfCredit 定义中的结构相似,但其中包含了真实的实例数据。

你可以看到信件中包含的每个资产和参与者都有一个类($class),它由命名空间与类型名称连接而成。例如:

"$class": "org.example.loc.LetterOfCredit"
"$class": "org.example.loc.ProductDetails"

还要注意这封信的信息是如何被捕获的:

"letterId": "L73021 AM"
"productType": "Computer"
"quantity": "1250"

最后,请注意信件处于最终状态:

"status": "CLOSED"
"closeReason": "Letter has been completed."

所有这些数据都非常强大。为什么呢?因为类型和实例信息被保留在一起,就像在真实合同中一样,它在编写后可以被正确解释。你可以想象这对喜欢在数据中寻找模式的分析工具有多么有帮助!

对于参考属性,我们可以看到结构略有不同:

"applicant": "resource:org.example.loc.Customer#alice"
"beneficiary": "resource:org.example.loc.Customer#bob"
"issuingBank": "resource:org.example.loc.Bank#BOD"
"exportingBank": "resource:org.example.loc.Bank#ED"

我们可以看到这些属性是参与者的引用,如果我们点击参与者选项卡,我们就能看到它们!点击银行选项卡:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

检查参与者实例

你可以看到我们网络中的两家银行,它们的类型和实例信息!点击不同的参与者和资产选项卡,并检查数据,看看类型如何在场景中被实例化。花些时间来做这个——重要的是你理解这些信息,将它们与类型联系起来,并真正思考它们与业务网络的关系。不要被欺骗——信息看起来很简单——但其中有一些强大的想法需要一些时间去联系。然而,我们鼓励你这样做——真的值得理解一切是如何联系在一起的,这样你也可以做到同样的事情!

检查交易实例

现在点击“所有交易”选项卡:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你可以看到我们应用程序运行过程中完整的交易生命周期。(你的时间可能会有点不同!)如果你滚动查看交易,你可以看到在我们的场景中确切发生了什么——Alice 申请了一封信,Matias 批准了,等等。如果你点击查看记录,你将能够看到单个交易的细节。

例如,让我们来看看 Alice 提出的InitialApplication

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以看到交易细节(我们稍微编辑了它们以适应页面):

"$class": "org.example.loc.InitialApplication",
"letterId": "L73021 AM",
"applicant": "resource:org.example.loc.Customer#alice",
"beneficiary": "resource:org.example.loc.Customer#bob",
"transactionId": "c79247f7f713006a3b4bc762e262a916fa836d9f59740b5c28d9896de7ccd1bd",
"timestamp": "2018-06-02T06:30:21.544Z"

注意我们如何可以看到这笔交易的确切细节!再次,非常强大!花一些时间查看此视图中的交易记录。

向网络提交新交易

我们可以在 Playground 中做更多的事情;现在我们将动态地与业务网络进行交互!

确保你已经在测试视图中选择了LetterOfCredit资产类型。注意左侧窗格上的“提交交易”按钮:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们将通过提交新的LetterOfCredit申请与业务网络进行交互。如果你按下提交交易,你将看到以下输入框:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在交易类型下拉菜单中,你会看到列出了所有可能的交易。选择InitialApplication,并用以下数据替换 JSON 数据预览:

{
   "$class": "org.example.loc.InitialApplication",
   "letterId": "LPLAYGROUND",
   "applicant": "resource:org.example.loc.Customer#alice",
   "beneficiary": "resource:org.example.loc.Customer#bob",
   "rules": [
     {
       "$class": "org.example.loc.Rule",
       "ruleId": "rule1",
       "ruleText": "This is a test rule."
     }
   ],
   "productDetails": {
     "$class": "org.example.loc.ProductDetails",
     "productType": "Monitor",
     "quantity": 42,
     "pricePerUnit": 500
   }
 }

你能看到这个交易描述了什么吗?你能看到 Alice 和 Bob 之间的新LetterId作为CustomerBeneficiary吗?你能看到ProductDetailsQuantityPrice吗?

如果你按下提交,你会看到你被返回到主视图,并且一个新的信函已被创建:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

恭喜,你刚刚提交了一份新的信用证申请!

但等等!如果我们已经与实时网络交互,那么当我们返回到应用视图时会发生什么。如果你回到了 Alice 的视图,你会注意到她有一封新的信:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Hyperledger Composer Playground 已经让我们与实时业务网络互动!此外,如果我们选择 Matias 的页面,我们可以看到该信件正在等待批准:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意所有属性都是您在样本交易中输入的属性!您现在可以使用 Playground 将此信件移动到其完整生命周期。我们建议您花一些时间这样做——这将帮助您巩固您的知识。

理解事务如何实现

这一切都非常令人印象深刻,但它是如何工作的——实现这些操作的逻辑在哪里,操作参与者和资产,并创建事件?要理解这一点,我们需要查看事务程序——在提交到引用这些资产、参与者和事件的网络的事务时运行的代码。

事务代码保存在一个脚本文件中,如果您在 Define 选项卡上选择 Script File,您将看到以下内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这就是实现事务的代码!今天,Hyperledger Composer 使用 JavaScript 来实现这些函数,这就是您在此页面上看到的内容——JavaScript。如果您浏览脚本文件,您将看到模型文件中定义的每个事务都有一个函数。

让我们检查到目前为止我们一直在玩的事务之一——InitialApplication事务。注意函数的起始方式:

/**
  * Create the LOC asset
  * @param {org.example.loc.InitialApplication} initalAppliation - the InitialApplication transaction
  * @transaction
  */
 async function initialApplication(application) {

注释和程序代码的第一行有效地说明了以下函数实现了InitialApplication事务,该事务接受org.example.loc.InitialApplication类型,并将其赋给了本地作用域的application变量。简而言之,它将程序逻辑与模型文件中看到的事务定义连接起来。

代码的第一行是以下内容:

const letter = factory.newResource(namespace, 'LetterOfCredit', application.letterId);

factory.newResource()org.example.loc命名空间中使用由函数调用者在输入application.letterId事务变量中提供的标识符创建了一个新的本地LetterOfCredit。此语句将此函数的结果赋给了本地letter变量。

重要的是要理解,此语句并没有在业务网络中创建一封信件;factory.newResource()只是创建了一个正确形状的 JavaScript 对象,现在可以由以下后续逻辑来操作,并且在使用调用者提供的输入正确形成之后(例如,由 Alice 使用的申请),它可以被添加到业务网络中!

注意applicantbeneficiary是如何赋值的:

letter.applicant = factory.newRelationship(namespace, 'Customer', application.applicant.getIdentifier());
letter.beneficiary = factory.newRelationship(namespace, 'Customer', application.beneficiary.getIdentifier());

交易确保 Alice 和 Bob 的标识符正确地放置在信函中。在我们的网络中,application.applicant.getIdentifier()会解析为resource:org.example.loc.Customer#aliceresource:org.example.loc.Customer#bob。交易逻辑有条不紊地使用提供的输入和已经存储在业务网络中的信息构建信用证。

接下来,请注意issuingBankexportingBank如何通过参与者导航到其银行。程序逻辑会浏览参与者和资产定义中的引用来做到这一点:

letter.issuingBank = factory.newRelationship(namespace, 'Bank', application.applicant.bank.getIdentifier());
letter.exportingBank = factory.newRelationship(namespace, 'Bank', application.beneficiary.bank.getIdentifier());

我们可以看到这些声明中,交易必须使用在模型中定义的结构。它可以添加任何专有的业务逻辑,但必须符合此结构。检查每行分配给letter的内容,看看您是否能理解在这些术语中发生了什么。需要花点时间来适应,但理解这一点非常重要 - 交易通过这种逻辑将业务网络从一个状态转变为另一个状态。

注意信函分配的最后一条声明:

letter.status = 'AWAITING_APPROVAL';

看看枚举类型如何被用于设置信函的初始状态。

在函数中下一个非常重要的声明是以下内容:

 await assetRegistry.add(letter);

现在这封信已经被添加到了业务网络中!此时,我们在业务网络中为信用证创建了一个新的应用程序。我们在本地存储中创建的信函已被发送到网络,并且现在是一个指向网络中参与者和资产的实时资产。

最后,我们发出一个事件来表示交易已经发生:

const applicationEvent = factory.newEvent(namespace, 'InitialApplicationEvent');
applicationEvent.loc = letter;
emit(applicationEvent);

就像处理信函一样,我们创建了一个正确形状的本地事件 - 一个InitialApplicationEvent,完善其细节,然后emit()它。通过检查不同的交易及其逻辑,您将对每个交易的精确处理变得更加熟悉 - 这个努力将给您带来丰厚的回报。

创建业务网络 API

在本章的最后部分,我们将展示您的应用程序如何使用 API 与业务网络中的交易功能进行交互。示例应用程序和 Playground 都使用 API 与业务网络进行交互。

实际上,您可以看到从服务消费者的角度来看,无论是 Alice、Bob、Matias 还是 Ella 都不知道区块链 - 他们仅仅与一些用户界面交互,最终导致这些交易功能(或类似的功能)被执行,以根据这些交易处理函数中编码的业务逻辑来操作业务网络。

正是这些用户界面和应用程序使用 API 与业务网络进行交互。如果您对 API 是新手,您可以在这里阅读相关信息。尽管更加技术上准确,但很少有人使用术语Web API - 它就是API

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们来看看我们业务网络的 API!如果您选择演示的最终选项卡,您将看到以下页面:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是 Hyperledger Composer REST 服务器。它是一个服务器,正在公开我们业务网络中的 API。这些 API 使用标准 SWAGGER 格式进行描述。

SWAGGER API 定义

SWAGGER 是一种描述 API 的开放标准 – swagger.io/specification/ 这些 API 是由 Hyperledger Composer 生成的,使用与模型中定义的相同词汇来描述为此业务网络定义的参与者、应用程序和交易!这意味着 SWAGGER API 对业务用户和技术用户都具有明显的意义。

对于业务网络中的每种参与者、资产和交易类型,都有相应的 API。

使用 SWAGGER 查询网络

选择其中一个 API LetterOfCredit

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意此 API 的 GETPOST 动词。大多数现代 API 使用 REST 和 JSON 进行定义,这就是您在此处看到的。练习展开和折叠视图,以查看所有不同的选项。

当您满意时,选择 InitialApplication GET

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就像使用 Playground 一样,您可以使用相同的 API 与业务网络交互。作为程序员,您应该对这个视图感到满意,尽管它要技术得多。

我们选择的 API 允许程序查询(GET)业务网络中的所有信件。如果您选择“试一试**!**”,您将看到以下响应:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些详细信息向您展示了发出的确切 API。这是一个在

http://localhost:3000/api/LetterOfCredit URL,响应体显示返回的数据。您应该能够看到它的结构与 Playground 数据非常相似,如果您滚动浏览响应,您将看到网络中的两封信。

从命令行测试网络

您还可以使用 curl 命令从终端与网络交互,并为您显示语法:

curl -X GET --header 'Accept: application/json' 'http://localhost:3000/api/LetterOfCredit'

在终端中尝试此操作,您将在命令行上看到数据:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它比 Playground 或 SWAGGER 视图要不那么美观,但如果您是程序员,您知道这有多强大!想想这如何帮助自动化测试,例如。

使用 SWAGGER 创建新信件

我们还可以从 SWAGGER 视图为信用证创建一个新的应用程序。选择 InitialApplication API。

我们将使用 POST 动词为 Alice 创建另一个申请:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

value 框中,粘贴以下数据:

{
  "$class": "org.example.loc.InitialApplication",
  "letterId": "LPLAYGROUND2",
  "applicant": "resource:org.example.loc.Customer#alice",
  "beneficiary": "resource:org.example.loc.Customer#bob",
  "rules": [
   {
    "$class": "org.example.loc.Rule",
    "ruleId": "rule1",
    "ruleText": "This is a test rule."
   }
  ],
  "productDetails": {
   "$class": "org.example.loc.ProductDetails",
   "productType": "Mouse Mat",
   "quantity": 40000,
   "pricePerUnit": 5
  }
 }

你能看出这个应用的作用吗?你能看到 Alice 想要向 Bob 以每件5美元的价格购买40000块鼠标垫的信件申请吗?

如果你点击“尝试一下!”按钮,将会创建一个新的信件!你现在可以使用 SWAGGER 控制台、应用程序或 Playground 查看这封新的信件。让我们试一下:

这是使用 SWAGGER 的视图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是使用 Playground 的视图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是使用该应用的视图(Matias 的视图):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

网络卡和钱包

最后,在结束本章之前,我们将把添加到这个业务网络,这样你就可以提交交易了!为了做到这一点,我们将返回到最初允许我们连接到网络的业务网络卡钱包。请记住,所有应用程序,包括 Playground,都有一个包含业务网络卡的钱包,可以用来连接不同的网络。当应用程序使用特定卡来连接网络时,它被标识为网络中特定的参与者实例。

  1. 让我们创建一个新的参与者!在测试标签页上,选择客户参与者:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 你将看到 Alice 和 Bob 的参与者信息。点击“创建新参与者”:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个页面将让你发出 API 并创建一个新的参与者。我们为一个名为Anthony的新参与者输入了以下详细信息,他在 BlockIT 工作:

{
   "$class": "org.example.loc.Customer",
   "companyName": "BlockIT",
   "personId": "Customer003",
   "name": "Anthony",
   "bank": "resource:org.example.loc.Bank#BOD"
}

注意他的标识符和对 Dinero 银行的引用。点击“创建新的”,注意参与者注册表已更新:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们在网络中创建了一个新的参与者。(也可以自己输入详细信息,只要确保你的参与者有有效的数据,特别是参考现有的银行。)

点击ID 注册表下的管理员。现在你将看到与 Playground 相关的身份列表。

而 Alice 和 Bob 的数字证书是私有的,只在他们的应用程序中可见,这里我们可以看到当前 Playground 用户(业务网络管理员)相关的身份信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

点击“发出新的 ID”:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输入ID003为 ID 名称,并与我们创建的新参与者关联,org.example.loc.Customer#Customer003,然后点击“创建新的”:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为业务网络卡命名,并点击“添加到钱包”。

你将看到 ID 列表已更新,关联了ID003,与Customer003相关:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

点击管理员标签下的我的业务网络用户,返回到 Composer Playground 的初始页面:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以看到 Playgrond 钱包现在包含了一个新的业务网络卡,允许您连接到我们的网络。点击“现在连接”以使用 Cusotmer003Card。您现在作为 Customer003 而不是 Admin 连接到了网络。

访问控制列表

所有应用程序,包括 Composer Playground,都使用来自其钱包的业务网络卡(本地文件系统上的文件)连接到网络。该卡包含网络的 IP 地址、参与者的名称和他们的 X.509 公钥。网络使用这些信息来确保他们只能对网络中的某些资源执行特定操作的权限。例如,只有特定的银行员工才能授权信用证。

通过检查网络的访问控制列表(ACL),您可以看到这些权限是如何为业务网络定义的。在“定义”选项卡上选择“访问控制”:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

滚动查看列表,查看不同用户对网络中不同资源拥有的权限。这些规则可能与类型或实例相关,尽管前者更常见。花一点时间研究此文件中的 ACL 规则。

摘要

您已经学会了如何使用超级账本技术建立真实的业务网络。您知道如何作为用户、设计师和应用程序开发人员与业务网络进行交互。您知道如何定义参与者、资产、交易和事件,并如何在代码中实现它们的创建。您知道如何将这些暴露为 API,以便外部应用程序可以使用它们!您可以通过查阅产品文档来了解更多关于超级账本 Composer 和超级账本 Fabric 的信息。拥有这些信息以及本章的知识,您已经可以开始构建自己的业务网络了!

现在让我们转向如何在区块链网络中管理开发生命周期 - 如何在区块链网络中实现敏捷性。我们将研究帮助我们设置和管理日常操作以开发区块链软件的过程和工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值