cssmagic / blog

CSS魔法 - 博客
http://blog.cssmagic.net/
2.81k stars 274 forks source link

QCon 演讲:为什么前端工程师更应该掌握区块链 DApp 开发(下) #101

Open cssmagic opened 5 years ago

cssmagic commented 5 years ago

摘要

魔法哥在 “QCon 全球软件开发大会” 2018 上海站的演讲广受好评。为让这个演讲发挥更大的价值,本文尝试以图文配合的方式还原现场体验。

在实战环节中,我们将和小明一起做出自己的第一款 DApp。实战案例清晰简洁,覆盖完整的开发流程,带你轻松跨入区块链应用开发的大门。


(本文是演讲的下半部分,建议您先读完 上半部分 再阅读本文。)

slide-p42

在做好准备工作之后,小明终于开始动手了!

slide-p43

我们回顾一下传统 Web App 和 DApp 的架构图。

在开发传统 Web 应用时,我们一般会先确定后端接口。同理,在开发 DApp 时,我们通常也会先把合约端的接口准备好。

首先,我们需要了解合约代码的编写模式。

slide-p44

讲到这里,终于可以给大家看前面那段 “被打码” 的代码了。这是一段最小化的星云链智能合约代码。我们来看一看它有哪些要素:

slide-p45

只要满足以上条件,就是一个合法的智能合约了。也就是说,你可以按照自己的开发习惯来组织合约代码,只要满足上述条件就可以了。

其次,另一项重要的基础知识是 “合约存储区”。

slide-p46

前面提到过,每个合约都有自己的独立存储区,那么智能合约自然也提供了操作存储区的 API。星云链合约存储区提供了多种操作方式,本文为方便讲解,只介绍其中最基础的用法。

在智能合约的运行环境中,有一个 LocalContractStorage 全局对象,它提供了三个最基本的 API(参见上图)。

前端工程师看到这里应该会会心一笑,因为这跟浏览器里的本地存储 API 几乎一致。我们在浏览器端要实现持久化存储,最常用的就是本地存储(localStorage);而在智能合约中,实现持久化存储也是通过类似的 API 和类似的思路来实现的。

👉 值得一提的是,.set() 方法接受的 value 参数可以是复合结构,比如数组或对象等。也就是说,相对于浏览器端的 localStorage.setItem() API,合约存储区的 .set() 方法更方便,不需要手动处理 value 的序列化和反序列化问题。

接下来,我们就要开始实现合约接口了。

slide-p47

既然智能合约要提供与传统后端功能相当的接口,那不妨先来看盾,传统的后端接口是怎样的。

slide-p48

在小明的网站中,后端至少要提供两个接口,分别对应以下两项功能:

如何在合约端实现这两个接口的功能?别着急,慢慢来。小明先把合约的大致结构写好:

slide-p49

这个合约似乎并不需要在构造器里做什么事儿,留空就行。

然后需要做一些初始化工作。小明需要做的就是在合约存储区里建一个 key,取名 'items',用来保存所有预言。在初始化阶段,显然啥预言也没有,就存一个空数组进去。

slide-p50

随后就要进入重点了,小明开始写两个合约接口。

slide-p51

第一个接口是 “获取预言列表”,即 getAllItems() 方法。

slide-p52

实现这个方法其实非常简单——从存储区里读出 'items' 这个 key 的值,返回就行了。写出的代码也是一目了然。

👉 请留意:这个方法里只有读操作,没有写操作。

第二个接口是 “发布预言”,即 create() 方法。

slide-p53

这个接口稍稍复杂一些,我们一步一步来。

slide-p54

我们先把这条新预言的数据准备好,存到 newItem 变量中备用。

slide-p55

接下来更新存储区:我们先取出所有预言的列表,然后把新预言 push 进去,最后把预言列表写回存储区。

slide-p56

在这个方法的最后,我们把这一条新预言的数据作为函数返回值,以便客户端在拿到调用结果后立即把新预言渲染出来——这与传统后端接口 /api/create 的设计保持一致。

👉 请留意:这个方法需要更新存储区,也就是说,有写操作。

好,两个接口已经全部实现,合约代码就写好了。

slide-p57

合约需要部署到链上才能真正发挥作用。这里请出 “星云 Web 钱包” 来完成合约的部署和测试。

部署合约的操作比较简单,这里就不赘述了。

slide-p58

部署成功之后,还可以测试一下合约行为是否符合预期。(如果发现合约代码有 bug,则需要重新部署。)

slide-p59

准备好合约端之后,小明接下来开始实现客户端。

网页形态的客户端开发大家都很熟悉了,这里不赘述,只着重讲一下 DApp 客户端独有的行为——合约调用。

slide-p60

调用合约有两种情况,或者说,有两种方式:

在实现这两种类型的合约调用时,需要用到客户端 SDK。它的作用是帮助我们与链交互,我们不用操心区块链网络的具体地址、接口和参数等细节,直接使用 SDK 提供的接口就可以完成调用合约、转账、查询网络状态等操作了。

slide-p61

星云链官方的客户端 SDK 功能完备,但设计风格偏底层,在实际开发中略显繁琐。因此这里推荐一款第三方的 SDK——Nasa.js。

Nasa.js 的安装方式很简单,直接用 npm 就可以。

加载方式也很简单,在页面中引入 nasa.js 文件,随后就可以使用它提供的各种 API 了。

slide-p62

Nasa.js 提供了近 30 个 API,在日常开发中,最常用的是以下三个:

这三者都与合约调用相关。第一项用于 “不需要写数据” 类型的合约调用,后两项涉及 “需要写数据” 的情况。具体用法会在下面的代码中详细讲解。

小明开始写网页端代码了!他先实现 “展示预言列表” 这个功能。

slide-p63

第一行很简单,就是把合约地址保存好。

接下来,小明需要调用合约的 getAllItems 接口,以获得所有的预言数据,并在页面中渲染出来。这个接口并不需要写数据,因此使用 Nasa.query() API 来调用它。

这个 API 接受三个参数,分别是合约地址、合约接口的函数名、传给函数的参数。

👉 提示:关于第三个参数 “传给函数的参数”,如果合约接口不需要参数,这里就传一个空数组进去;如果合约接口需要参数,就把若干个参数放在一个数组里传进去。由于合约的 getAllItems 接口不需要参数,小明传了空数组进去。

通过 Nasa.query() 发生的合约调用,可以很快得到调用结果,这个过程就好像是在调用一个 Ajax 接口。Nasa.query() 的返回值是一个 Promise,Promise 包裹的值就是合约调用结果——这个设计和常见的 Ajax 库也是十分类似的。

slide-p64

小明在 Promise 的 .then() 回调中拿到合约接口的调用结果。调用结果有一个 execResult 字段,用来保存合约接口函数的返回值。小明通过它可以得到一个包含所有预言数据的数组,然后在网页上就可以渲染出预言列表了。

小明接着实现第二个功能——“发布预言”。

发布页面有一个表单,小明需要给表单的提交事件绑定事件处理函数(代码略),并在这个事件处理函数中实现发布预言的操作(以下代码均写在事件处理函数中)。

slide-p65

前两行是准备工作,把合约地址和已经输入的文字保存好。

接下来,小明需要调用合约的 create 接口,以完成新预言的发布。这个接口需要通过发起交易来完成上链,于是小明使用 Nasa.call() API 来调用它。

这个 API 同样接受三个参数,参数含义与 Nasa.query() 的三个参数完全一致。小明依次传入合适的值。

这个 API 会唤起钱包插件,引导用户完成交易。

slide-p66

这个 API 也返回一个 Promise,但 Promise 包裹的值与 Nasa.query() 不同。因为通过交易来调用合约,无法直接得到调用结果,只有当这一笔交易被矿工处理完成并打包上链,我们才能得到这次合约调用的结果。因此,这个 Promise 只能给我们一个交易流水号,我们随后可以拿着这个流水号向链查询交易的状态和调用结果。

当钱包插件把交易发出后,小明就可以在 .then() 回调中拿到交易流水号(payId)。接下来就轮到 Nasa.getTxResult() 这个 API 出场了。

这个 API 接受一个参数,就是交易流水号;返回一个 Promise,Promise 包裹的值就是本次交易的状态和本次调用的结果。(查询交易结果是一个轮询的过程,不过我们不用操心这些细节,Nasa.js 会自动完成这个过程,我们只需要关心这个 Promise 就可以了。)

slide-p67

.then() 回调中,小明把交易流水号传给 Nasa.getTxResult()。由于这个 API 也返回 Promise,小明可以把它 return 出去,让这个 Promise 链条继续往下走。

当查询发现交易被矿工处理完成之后,可以在下一个 .then() 回调中拿到本次交易的状态和本次调用的结果。同样还是在 execResult 字段中,我们可以获取合约接口函数的返回值。

slide-p68

当然保险起见,我们最好先判断一下交易是不是成功了。如果status 字段的值为 1,则表示交易已被正常处理。

小明在这里拿到合约接口函数 create 的返回值(也就是发布成功的这条新预言的数据),把它渲染并添加到页面中就可以了。

👉 提示:以上代码仅仅演示了最核心的逻辑。对于一个体验良好的 DApp 来说,还需要处理好各种交互反馈和错误提示。

至此,整个 DApp 从合约端到客户端,就全部开发完成了。小明把这两端的功能跑通,网站顺利上线。

slide-p69

👉 这里顺便插一句,对 DApp 来说,甚至可以没有 “上线” 这个环节。因为整个应用的业务逻辑已经以智能合约的形式部署上链了,如果小明只打算自己用的话,客户端代码保存为 HTML 页面,在本地打开照样可以用。

网站看起来还是那个网站,不过它的本质已经升级为基于区块链的 DApp 了。

万事具备,小明静静等待下一届世界杯的到来。

slide-p70

2018 年世界杯,小明又一次正确预测出了冠军得主。

slide-p71

小明终于成功地在女朋友面前秀了一把。

slide-p72

从此他们幸福地生活在一起……(全剧终)

slide-p73

故事到这里就讲完了。不过在听故事的过程中,大家头脑中可能会冒出一些问题。

首先,这个实战案例是不是太简单了?

slide-p74

时间关系,我们无法在短短一次演讲中实现更多的功能。不过,互联网产品都是迭代式开发的嘛,小明确实也打算升级一下这个 DApp。他设想的新功能有:

先说 “点赞”,这个功能相对比较好实现,合约端新增一个点赞接口,再给预言数据增加一个点赞数的字段,差不多就可以了。

再看 “多用户” 和 “打赏” 功能,乍一看很复杂,但其实在区块链上非常容易实现。

slide-p75

在开发传统 Web App 的时候,如果要识别不同用户,往往需要自己做一套账号系统,或者对接第三方社交账号。而在区块链上就不用这么麻烦,因为每个在链上的用户都有自己独一无二的身份标识——“地址”。DApp 通过地址就可以区分每位用户。

此外,在传统应用中如果要实现打赏功能,就需要接入支付平台,比如银联、支付宝或微信支付等。但这对个人开发者来说是极为困难的。别急,这里又体现出 DApp 的优势了——区块链的老本行就是记账,每条公链通常都有自己的原生货币系统,DApp 直接使用链的支付功能就可以了。

区块链 “原生的账号系统” 和 “原生的支付系统”,堪称 DApp 的两大天生神力!

slide-p76

这样一个 DApp 最终确实被做了出来,叫作 “我是预言帝”。这实际上也是魔法哥自己学习开发 DApp 的第一款作品。

另一个经常被问到的问题是:大家经常提到区块链 “不可篡改”,到底是指什么?小明的女朋友凭什么相信他?

slide-p77

在看完上面的实战案例之后,大家很可能会产生一个疑惑:我可以通过调用合约来更新合约存储区,这算不算 “篡改” 呢?

简单解释一下,区块链的 “不可篡改” 特性,主要有以下两个层面来构成:

说到这里,我们再来回顾一下 “修改合约存储区” 的过程:调用合约来更新存储区,本质上是一次交易,需要打包上链才能生效。因此,“修改合约存储区” 这个操作并不是直接修改已经在链上的历史记录,而是通过新记录在老记录上打补丁,我们在任一时刻得到的数据其实是所有修改记录叠加的结果。

因此,对于 DApp 来说,“不可篡改” 并不是说我们无法修改合约的存储区,而是说任何人都无法 “偷偷摸摸地修改”,因为每一次的修改记录都会明明白白地记录在链上。

slide-p78

讲完这个原理,我们可以把故事的结尾再补充一下。如果小明的女朋友想验证这个 DApp 上展示的预言是否真实可靠,她还需要做这两件事情:

slide-p79

第三个问题,开发者特别关心 DApp 的应用场景。目前已经明朗的应用场景有以下一些:

实际上区块链应用还处在早期,更多的落地场景还有待我们去探索和发现。

slide-p80

第四个问题,有兴趣尝试 DApp 开发的同学肯定希望获取演讲中提到的各种工具和资源。

由于 DApp 开发需要掌握和学习的资料非常多,魔法哥特别整理了一个资料库,并已在 GitHub 开源:
https://github.com/NasaTeam/Awesome-Nebulas

文中提到的第三方 SDK Nasa.js 实际上也是由魔法哥组队开发的,同样在 GitHub 开源,欢迎你一起来打磨它:
https://github.com/NasaTeam/Nasa.js

slide-p81

最后,说几句题外话吧,聊一聊我接触区块链这小半年来的感悟。

由于某些原因,区块链技术和区块链行业一直被广泛误解。一方面它被夸大为万能的银弹,一方面它的基础设施还极为简陋,这令很多人敬而远之,甚至 “路转黑”。区块链虽然已经诞生了近十年,但在应用层面仍然非常早期,它还有极大的不确定性,还有无数的未知等待我们去探索。

就像这张图,一片荒芜。探索者们独孤前行,然而他们相信光明的未来。

有人说,“区块链是风口、是潮流”。而我想说的是,“我们不跟随潮流,我们推动潮流——因为我们是开发者”。区块链是什么、能做什么,将由我们这些开发者来定义。期待有更多的开发者加入到这个新世界,我在这里等你!

今天的分享就到这里,谢谢!

slide-p82


相关阅读


© Creative Commons BY-NC-ND 4.0   |   我要订阅   |   我要打赏

icksky commented 5 years ago

感谢, 受教了, 有个疑问想请教一下. DAPP 有办法应用到普通用户身上么? 因为目前看来 一定要安装星云链的扩展程序, 还要有自己的钱包.

cssmagic commented 5 years ago

DApp 确实有这样的门槛存在。

有一些游戏的做法是在自己的后端帮用户管理钱包、帮用户与链交互。这也是一种解决方案。

icksky commented 5 years ago

如果由后端统一处理, 创建钱包的动作也是后端来做么? 所以引导用户氪金之前, 钱包内余额是0, 这之前所有产生的交易所用到的 Gas 都要游戏所有者进行垫付吧? 还是其实只有一个钱包, 所有用户用的都是这个钱包产生交易..... 这样好像用区块链没什么意义了呀.


另外, 关于不可篡改这个优点. 如果小明建立合约时设置了一个 update 方法, 不就可以修改之前的预言了?

刚刚接触这个, 一头雾水 问题比较多 还请前辈赐教

cssmagic commented 5 years ago

如果由后端统一处理, 创建钱包的动作也是后端来做么?……这样好像用区块链没什么意义了呀.

这确实很考验游戏的设计者,在便捷性和公信力之间找平衡。

关于不可篡改这个优点……

这个在文章末尾专门有讲,可以扫码阅读公众号里的完整版。


你问的都是好问题,欢迎多交流!

icksky commented 5 years ago

已关注. 感谢科普.

hkongm commented 5 years ago

再次手动点赞