使用GraphQL构建以太坊Dapp

使用GraphQL构建以太坊Dapp,用GraphQL作为智能合约的查询语言。

即使是最简单的去中心化应用程序dapp也涉及分布在许多不同来源的信息。最重要的是,除非我们非常小心,否则我们的组件很容易与我们应用程序的架构细节密切相关。在这篇博文中,我将向你展示如何使用GraphQL作为合约的强大抽象,最终简化dapp用户界面的构建和维护。

注意:这篇文章并不是对以太坊,智能合约或GraphQL的介绍。话虽这么说,但我会尽力解释一下,你不应该需要太多的概念,要么跟随它。为了更好地介绍去中心化应用程序的基础知识,我推荐这篇文章。有关GraphQL的介绍,我建议从这里开始。

在这篇文章中,我们将建立一个像eBay这样的拍卖平台,用户可以在这里发布待售物品。为简洁起见,我们的示例仅会触发一个列出我们平台上可用的拍卖的查询。由于我们可以向我们的架构发送任何类型的查询,因此你可以轻松插入你喜欢的GraphQL框架并按照你的需要工作。

以太坊和GraphQL

当你第一次听说GraphQL时,它很可能是作为远程服务器API的查询语言。在去中心化的世界中,没有像传统架构那样的中央服务器。但是,这并不意味着我们无法利用GraphQL社区生成的所有令人惊奇的工具。

通过定义仅存在于客户端中的模式,我们可以使用GraphQL作为我们的合约集合的抽象层。这允许我们查询我们平台的当前状态,就像它是传统服务器一样,并解决对区块链的查询。

我们的合约

这篇文章的代码存在于GitHub上。该平台由两个不同的合约组成。一个叫做AuctionHub,它维护一个地址列表,指向另一个名为Auction的合约的实例。第二份合约只有一个公共领域:待售物品的名称。

让我们从部署一些合约开始,这样我们就可以在UI中查询。克隆演示项目并安装项目依赖项。这可能需要一点时间。

1
2
3
git clone git@github.com:AlecAivazis/ethereum-graphql-demo.git \ 
&& cd ethereum-graphql-demo \
&& npm i

一旦完成,启动我们可以测试的本地以太坊区块链:

1
npm run testnet

请注意可用帐户下的帐户列表 Available Accounts。我们稍后会需要其中一个。在另一个终端,编译合约:

1
npm run build:contracts

这应该在contract/*下为每个合约生成两个文件。*.bin文件是每个合约的编译版本,*.abi是描述合约内容的json文件(属性,方法等)。

该演示项目附带一个脚本,该脚本将引导一些合约并打印路由器的地址。在运行它之前,你必须在此处更新要在创建测试合约时使用的钱包地址。更新后,你可以创建示例合约:

1
npm run init

如果一切按计划进行,你应该看到我们刚部署的集线器的地址。记下这个地址。我们稍后在定义模式时需要它。部署了我们的合约后,我们现在可以开始使用GraphQL查询它们了。

建立我们的客户

repo已经包含了一个非常基本的UI,我们可以使用它而不必担心它是如何工作的。只要知道如果更新组件触发的查询或GraphQL架构,则必须使用npm run relay重新运行编译器才能使更改生效。

定义架构

构建客户端GraphQL层的第一步与传统世界相同:定义代表我们域的模式。无论产品的持久性和执行细节如何,此表示都应适用。而在模式充当服务器和客户端之间的协议之前,在我们的去中心化世界中,此模式对UI和合约之间的内部API进行编码。它甚至可以作为负责区块链和UI开发的独立团队之间的交接(如果这种分离对你有意义)。

让我们看看它是如何工作的。首先在src/目录下添加一个名为schema.js的文件,其中包含以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { makeExecutableSchema } from 'graphql-tools'

const schema = `
type Auction {
itemName: String!
}
type Query {
allAuctions: [Auction!]!
}
`

export default makeExecutableSchema({
typeDefs: schema,
})

如果你有使用graphql-tools软件包的经验,那么这看起来应该很熟悉。我们在这里做的是定义一个GraphQL模式,让我们查询系统中的所有拍卖。

指定解析器

架构本身不足以解析查询。我们还需要告诉运行时如何解析请求的字段。这是通过定义一个对象来完成的,该对象的键是类型名称,其值是另一个具有解析每个字段的函数的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { makeExecutableSchema } from 'graphql-tools'
import { HubABI, AuctionABI, web3 } from '../contracts'

const schema = ...

const resolvers = {
Auction: {
// auction is an instance of the Auction contract wrapper
itemName: auction => auction.methods.itemName().call(),
},
Query: {
allAuctions: async () => {
// create a reference to the hub contract we created earlier
const hub = new web3.eth.Contract(HubABI, 'hub address from before')

// build the list of auctions
const auctions = []
for (let i = 0; i < await hub.methods.auctionCount().call(); i++) {
auctions.push(
new web3.eth.Contract(
AuctionABI,
await hub.methods.auctions(i).call()
)
)
}

return auctions
}
},
}

export default makeExecutableSchema({
typeDefs: schema,
resolvers,
})

如你所见,我们告诉运行时解析Auction对象类型上的itemName字段需要在拍卖合约的实例上调用itemName方法。这将返回一个带有常量方法值的promise,它将为我们提供我们想要的字符串。同样,我们通过查看我们之前创建的拍卖中心并为存储在路由器中的每个地址返回拍卖结构的实例来解析所有可用拍卖的列表。

通过这样做,我们完全从用户界面中抽象出了路由器的存在。如果我们确实有单独的团队在UI和区块链基础设施上工作,那么UI团队不必了解如何查找每个拍卖。他们所要做的就是触发像{allAuctions {itemName}}这样的查询。

查询我们的架构

有了我们的架构,我们现在准备开始连接用户界面和我们的合约。在Apollo(演示项目使用的graphql框架)中,你可以提供一个称为“链接”的东西,负责处理查询。如果在演示项目中打开src/client.js,你将看到它已经包含了定义自定义链接所需的许多样板。但是,缺少一个核心部分——解决查询的实际逻辑。我们现在加上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { ApolloLink } from 'apollo-link'
import { Observable } from 'apollo-link-core'
import { graphql } from 'graphql'
import { print } from 'graphql/language/printer'
import schema from './schema'

const blockChainLink = new ApolloLink(
(operation) => new Observable(observer => {
graphql(schema, print(operation.query), null, null, operation.variables)
.then(result => {
observer.next(result)
observer.complete()
})
})
)

// ... everything else...

正如你所看到的,解决我们的查询并不像在集中式情况下那样触发网络请求。相反,我们只是将查询字符串和变量传递给graphql函数,该函数使用我们之前定义的解析器来处理查询。

有了这两个文件,我们现在应该可以运行我们的客户端并查看我们平台中的拍卖列表。启动开发服务器:

1
npm run relay && npm run dev

并导航到http://localhost:8000。如果一切顺利,你现在应该看到一个项目列表——一个用于我们在init脚本中创建的三个拍卖。

为什么这样更好?

到现在为止,你可能会问自己是否值得这么做。如果你有使用web3的经验,那么你就会知道,如果没有某种特定于域的抽象层,命令式API可能会变得非常重复。例如,获取我们平台中每个拍卖列表所需的代码(从上面复制):

1
2
3
4
5
6
7
8
9
10
// create a reference to the hub contract we created earlier
const hub = new web3.eth.Contract(HubABI, 'hub address from earlier')

// build the list of auctions
const auctions = []
for (let i = 0; i < (await hub.methods.auctionCount().call()); i++) {
auctions.push(new web3.eth.Contract(AuctionABI, await hub.methods.auctions(i).call()))
}

return auctions

在试图找出将这种逻辑抽象为可重用的东西的最佳方法时,有很多问题需要回答。其中某些部分特定于我们的拍卖/中心故障。其他部分是样板文件,你可以在任何时候想要建立一个列表中找到它。一旦我们解决了它,我们如何以一种使UI容易消费的方式包装逻辑?虽然所有这些问题都没有灵丹妙药,但每种解决方案都有其优点和缺点。通过将GraphQL用作智能合约的数据层,你可以为UI组件提供强大的集成,从而抽象出声明API背后的检索逻辑的详细信息。但是,这会以应用程序中额外的逻辑层为代价。

一旦你将合约封装在单个模式中,就可以利用graphql社区生成的所有强大工具。最重要的是,这个模型还为半去中心化方法提供了一个非常流畅的故事,其中信息来自链上和链外的来源。我们可以使用远程服务来构建模式,并构建统一的抽象,使我们可以构建接口,而无需担心信息的来源。我在我的一个dapps中使用了类似的方法来与流行的oauth提供程序集成。

结论和后续步骤

在这篇文章中,我向你展示了如何构建一个客户端GraphQL模式,该模式解决了在以太坊区块链上运行的智能合约。然后,我们将架构连接到我们的用户界面,以便我们可以轻松查询合约的状态。这为我们的UI提供了一个很好的抽象来查询我们的合约而不用担心如何检索所请求的数据。

为了保持专注,我们只讨论了如何针对区块链解决单个查询。但是,有很多东西可以添加到顶部,以使开发人员体验和你的应用程序加上超级。

======================================================================

分享一些以太坊、比特币、EOS等区块链相关的交互式在线编程实战教程:

  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • ERC721以太坊通证实战,课程以一个数字艺术品创作与分享DApp的实战开发为主线,深入讲解以太坊非同质化通证的概念、标准与开发方案。内容包含ERC-721标准的自主实现,讲解OpenZeppelin合约代码库二次开发,实战项目采用Truffle,IPFS,实现了通证以及去中心化的通证交易所。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • c#比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在C#代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是C#工程师不可多得的比特币开发学习课程。
  • EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • 深入浅出玩转EOS钱包开发,本课程以手机EOS钱包的完整开发过程为主线,深入学习EOS区块链应用开发,课程内容即涵盖账户、计算资源、智能合约、动作与交易等EOS区块链的核心概念,同时也讲解如何使用eosjs和eosjs-ecc开发包访问EOS区块链,以及如何在React前端应用中集成对EOS区块链的支持。课程内容深入浅出,非常适合前端工程师深入学习EOS区块链应用开发。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是使用GraphQL构建以太坊Dapp