使用 Redwood.js 和 Fauna 构建以太坊应用程序

随着比特币价格最近突破 20,000 美元,并进一步突破 30,000 美元,我认为深入探讨创建以太坊应用程序的价值。以太坊,如你所知,是一个公共(意味着对所有人开放,无限制)区块链,它充当分布式共识和数据处理网络,数据以“交易”(txns)的规范形式存在。但是,以太坊的当前功能只能存储(受 gas 费限制)和处理(受区块大小或参与共识的各方大小限制)有限的交易和交易/秒。现在,由于这是一篇关于使用 Redwood 和 Fauna 进行构建的“如何”文章,而不是关于“如何[… ]”的文章,我将不再深入探讨以太坊的工作原理、其限制及其没有限制等等的技术细节。相反,我将假设你,作为读者,已经对以太坊以及如何构建或使用它有一定的了解。

我意识到,有些没有以太坊经验的新手会偶然发现这篇文章,而我会指导这些用户一些方向。值得庆幸的是,截至重写本文时,以太坊最近更新了其开发者页面,其中包含大量资源和教程。我强烈建议新手学习一下!

不过,在接下来的过程中,我会提供相关的具体细节,以便任何熟悉构建以太坊应用程序、Redwood.js 应用程序或依赖 Fauna 的应用程序的人都可以轻松地遵循本教程中的内容。言归正传,让我们深入了解吧!

准备工作

该项目是对Emanator 单体仓库的派生,该项目由应用程序的创建者之一 Patrick Gallagher 在其团队的 Superfluid 黑客松提交的博客文章中进行了很好的描述。Patrick 的应用程序使用 Heroku 作为数据库,而我将展示如何在这个相同的应用程序中使用 Fauna!

由于该项目是派生项目,请确保在继续之前已下载MetaMask 浏览器扩展

Fauna

Fauna 是一个 Web 原生 GraphQL 接口,支持自定义业务逻辑和与无服务器生态系统的集成,使开发人员能够简化代码并更快地发布。其底层的全球分布式存储和计算架构快速、一致且可靠,具有现代的安全基础设施。Fauna 非常易于上手,并提供 100% 的无服务器体验,无需管理。

Fauna 还为我们提供了一个高可用性解决方案,每个全球部署的服务器都包含我们数据库的一部分,并通过每个请求异步复制我们的数据,提供我们数据库或所执行交易的副本。

使用 Fauna 的一些好处可以概括如下:

  • 事务性
  • 多文档
  • 地理分布式

简而言之,Fauna 使开发人员不必担心单文档或多文档解决方案。它保证数据一致性,而无需开发人员承担如何对系统进行建模以避免一致性问题的负担。要更好地了解 Fauna 如何做到这一点,请参阅这篇关于 FaunaDB 分布式事务协议的博客文章。

除了使用 Fauna,还可以选择其他一些替代方案,例如:

  • Firebase
  • Cassandra
  • MongoDB

但这些选项无法像 Fauna 那样提供 ACID 保证,从而损害可扩展性。ACID 代表:

  • **原子性:**所有交易都是一个单一的真相单元,要么全部通过,要么全部失败。如果我们在同一个请求中有多个交易,那么要么两个都成功,要么两个都失败,不能一个成功而另一个失败。
  • **一致性:**交易只能将数据库从一个有效状态转换为另一个有效状态,即写入数据库的任何数据都必须符合数据库设定的规则,这确保所有交易都是合法的。
  • **隔离性:**执行或创建交易时,并发交易会使数据库状态保持与顺序执行每个请求时相同的状态。
  • **持久性:**任何执行并提交到数据库的交易都将持久保存到数据库中,无论系统停机或失败。

Redwood.js

由于我已经多次使用 Fauna,我可以亲身体验 Fauna 数据库,在所有我喜欢它的功能中,我最喜欢的是它使用起来多么简单易懂!不仅如此,Fauna 也很适合与 GraphQL 和 GraphQL 工具(如 Apollo Client 和 Apollo Server)配对!但是,我们不会直接使用 Apollo Client 和 Apollo Server。相反,我们将使用 Redwood.js,一个预先打包了 Apollo Client/Server 的完整堆栈 JavaScript/TypeScript(尚未投入生产)无服务器框架!

你可以在其网站GitHub 页面上查看 Redwood.js。

Redwood.js 是一个从幕后诞生的新框架(哈哈),由 Tom Preston-Werner(GitHub 的创始人之一)创建。尽管如此,请注意,这是一个有意见的 Web 应用程序框架,为你做出了许多开发环境决策。虽然有些人可能不喜欢这种方法,但它确实为我们提供了一种更快的构建以太坊应用程序的方法,而这正是这篇文章的重点。

Superfluid

使用以太坊应用程序的挑战之一是区块确认。区块确认的对应物是交易确认(即数据),而确认需要时间,这意味着用户必须等待一段时间(通常为几分钟),直到他们发起的计算(无论是通过 UI 直接发起还是通过其他智能合约间接发起)被认为是真实或可信的。Superfluid 是一种协议,旨在通过引入现金流或交易流来解决这个问题,从而实现实时金融应用程序;也就是说,用户不再需要等待交易确认,可以立即执行下一组计算操作。

阅读 Superfluid 的文档以了解更多关于 Superfluid 的信息。

Emanator

Patrick 的团队做了一些非常酷的事情,将 Superfluid 的流式功能应用于 NFT,允许用户“铸造持续供应的 NFT”。然后可以通过拍卖出售这些 NFT 流。Emanator 应用程序另一个有趣的部分是,这些 NFT 是为创作者、艺术家 👩‍🎨 或音乐家 🎼 设计的。

关于该应用程序的运作方式还有更多技术细节,例如使用 Superfluid 即时分配协议 (IDA)、每次拍卖的收益分配、拍卖流程以及智能合约本身;但是,由于这是一个“如何”而不是“如何 [… ]”教程,我将为你提供原始Emanator `monorepo` 的 README.md 链接,如果你想了解更多信息。

最后,让我们来写一些代码!

设置

1. 从 `redwood-eth-with-fauna` 下载仓库

在你的终端、喜欢的文本编辑器或 IDE 中使用 Git 克隆`redwood-eth-with-fauna` 仓库。为了更好地理解,我将在这个教程中使用 VSCode。

2. 安装应用程序依赖项并设置环境变量 🔐

克隆仓库后,要安装该项目的依赖项,只需运行

yarn

…在目录的根目录下。然后,我们需要从我们的 `.env.example` 文件中获取我们的 `.env` 文件。要做到这一点,请运行

cp .env.example .env

在你的 `.env` 文件中,你还需要提供 `INFURA_ENDPOINT_KEY`。与你最初的想法可能不同,该变量实际上是你 Infura 应用程序的 `PROJECT ID`。

如果你没有 Infura 帐户,可以免费创建一个!🆓 🕺

这是我的 `redwood-eth-with-fauna` 应用程序的 Infura 仪表板示例视图。复制 `PROJECT ID` 并将其粘贴到你的 `.env` 文件中,用于 `INFURA_ENDPOINT_KEY`

3. 更新 GraphQL 模式并运行数据库迁移

在位于

api/prisma/schema.prisma 

…处的模式文件中,我们需要在 Auction 模型中添加一个字段。这是由于代码中的一个错误,导致该字段实际上在单体仓库中丢失了。因此,我们必须添加它才能使应用程序正常工作!

我们在第 33 行添加了一个名为 `contentHash` 的字段,类型为 `String`,这样我们的拍卖就可以添加到我们的数据库中,然后显示给用户。

接下来,我们需要使用 Redwood.js 命令运行数据库迁移,该命令将自动更新我们项目中的一些代码。(Redwood 开发人员为我们抽象了这项责任,真是太慷慨了;这个命令非常有效!)为此,请运行

yarn rw db save redwood-eth-with-fauna && yarn rw db up

如果此过程成功,您应该会看到类似以下内容。

此时,您可以通过运行以下命令启动应用程序

yarn rw dev

…并创建第一个 NFT,然后铸造它!🎉 🎉

注意:铸造新的 NFT 时,您可能会遇到以下错误

如果出现这种情况,只需刷新页面即可在右侧看到您的新 NFT!

您还可以点击新 NFT 的名称以查看其拍卖详细信息,如下所示

您还可以在终端中注意到,当您导航到此页面时,Redwood 会更新 API 解析器。

设置就完成了!不幸的是,我不会讲解如何使用 UI 的这一部分,但欢迎您访问 Emanator 的 monorepo 以了解更多信息。

现在,我们要将 Fauna 添加到应用程序中。

添加 Fauna

在将 Fauna 添加到 Redwood 应用程序之前,请确保通过按下 CTL+C(在 macOS 上)将其关闭。Redwood 为我们处理热重载,并将自动重新渲染页面,以便我们进行编辑,这在进行调整时可能会很烦人。因此,现在我们将关闭应用程序,直到完成添加 Fauna。

接下来,我们需要确保从 Fauna 仪表板创建的 Fauna 数据库中获取一个 Fauna 秘密 API 密钥(我不会讲解如何操作,但这篇 有用的文章很好地介绍了这一过程)。复制密钥秘密后,将其粘贴到 .env 文件中,替换 <FAUNA_SECRET_KEY>

请确保保留引号!

将 GraphQL 架构导入 Fauna

要将项目的 GraphQL 架构导入 Fauna,我们需要先手动将 3 个独立的架构缝合在一起。创建一个新的文件 api/src/graphql/fauna-schema-to-import.gql。在此文件中,我们将添加以下内容

type Query {
 bids: [Bid!]!
  auctions: [Auction!]!
 auction(address: String!): Auction
  web3Auction(address: String!): Web3Auction!
 web3User(address: String!, auctionAddress: String!): Web3User!
}
 
# ------ Auction schema ------
type Auction {
 id: Int!
 owner: String!
 address: String!
 name: String!
 winLength: Int!
 description: String
 contentHash: String
 createdAt: String!
 status: String!
 highBid: Int!
 generation: Int!
 revenue: Int!
 bids: [Bid]!
}
 
input CreateAuctionInput {
 address: String!
 name: String!
 owner: String!
 winLength: Int!
 description: String!
 contentHash: String!
 status: String
 highBid: Int
 generation: Int
}
 
# Comment out to bypass Fauna `Import your GraphQL schema' error
# type Mutation {
#   createAuction(input: CreateAuctionInput!): Auction
# }

# ------ Bids ------
type Bid {
 id: Int!
 amount: Int!
 auction: Auction!
 auctionAddress: String!
}
 
 
input CreateBidInput {
 amount: Int!
 auctionAddress: String!
}
 
input UpdateBidInput {
 amount: Int
 auctionAddress: String
}
 
# ------ Web3 ------
type Web3Auction {
 address: String!
 highBidder: String!
 status: String!
 highBid: Int!
 currentGeneration: Int!
 auctionBalance: Int!
 endTime: String!
 lastBidTime: String!
 # Unfortunately, the Fauna GraphQL API does not support custom scalars.
 # So, we'll this field from the app.
 # pastAuctions: JSON!
 revenue: Int!
}
 
type Web3User {
 address: String!
 auctionAddress: String!
 superTokenBalance: String!
 isSubscribed: Boolean!
}

使用此架构,我们现在可以将其导入 Fauna 数据库。

此外,不要忘记对 3 个独立的架构文件 api/src/graphql/auctions.sdl.jsapi/src/graphql/bids.sdl.jsapi/src/graphql/web3.sdl.js 进行必要的更改,使其与新的 Fauna GraphQL 架构相对应!这对于保持应用程序的 GraphQL 架构和 Fauna 的 GraphQL 架构之间的一致性非常重要。

查看完整项目差异 - 快速入门部分

如果您想深入了解并学习使该项目正常运行所需的必要更改,那太棒了!请转到下一部分!

否则,如果您只想快速上手,本部分适合您。

您可以在项目的存储库根目录中签出 integrating-fauna 分支。为此,请运行以下命令

git checkout integrating-fauna

然后,再次运行 yarn,以进行完整性检查

yarn

要启动应用程序,您可以运行以下命令

yarn rw dev

添加 Fauna 的步骤

现在,我们还需要执行一些其他步骤才能启动项目!

1. 安装 faunadbgraphql-request

首先,让我们安装 Fauna JavaScript 驱动程序 faunadbgraphql-request。我们将在这两个驱动程序的帮助下,对数据库脚本文件夹进行主要的修改,以便添加 Fauna。

要进行安装,请运行

yarn workspace api add faunadb graphql-request

2. 编辑 api/src/lib/db.jsapi/src/functions/graphql.js

现在,我们将使用 Fauna 实例替换 api/src/lib/db.js 中的 PrismaClient 实例。您可以删除文件中的所有内容,并用以下内容替换:

然后,我们需要对 api/src/functions/graphql.js 文件进行一些小更新,如下所示:

3. 创建 api/src/lib/fauna-client.js

在这个简单的文件中,我们将使用两个变量实例化 Fauna 数据库的客户端实例,这些变量将在下一步中使用。此文件最终应如下所示:

4. 更新 api/src/services/auctions/auctions.js 下的第一个服务

现在是比较困难的部分。为了使服务正常运行,我们需要将所有与 Prisma 相关的命令替换为使用 Fauna 客户端实例的命令,该实例是我们刚刚创建的 fauna-client.js 文件中的实例。这部分一开始看起来并不直观,但经过仔细思考,所有必要的更改都归结为理解 Fauna 的 FQL 命令的工作原理。

FQL(Fauna 查询语言)是 Fauna 用于查询 Fauna 的本机 API。由于 FQL 是“表达式导向的”,因此使用它就像链接几个函数命令一样简单。因此,对于 api/services/auctions/auctions.js 中的第一个更改,我们将执行以下操作:

简单解释一下,首先,我们从正确的项目文件路径中导入客户端变量和 db 实例。然后,我们删除第 11 行,并用第 13-28 行替换(目前可以忽略注释,但如果您真的想查看这些注释的其余部分,可以签出该项目的存储库中的 integrating-fauna 分支,以查看完整的差异)。在这里,我们所做的只是使用 FQL 查询 Fauna 索引中的拍卖索引,以从 Fauna 数据库中获取所有拍卖数据。您可以通过运行 console.log(auctionsRaw) 来测试这一点。

通过运行该 console.log(),我们发现我们需要进行一些对象解构,才能获取更新先前第 18 行所需的数据

const auctions = await auctionsRaw.map(async (auction, i) => {

由于我们正在处理对象,但想要一个数组,因此我们将添加以下内容,在完成 const auctionsRaw 的声明后将其添加到下一行

现在,我们可以看到我们获得了正确的数据格式。

接下来,让我们将 auctionsRaw 的调用实例更新为新的 auctionsDataObjects

现在是更新此文件中最具挑战性的部分。我们想要更新 auctioncreateAuction 函数的简单 return 语句。实际上,我们所做的更改非常相似。因此,让我们更新 auction 函数,如下所示:

同样,您可以忽略注释,因为此注释只是为了说明在更改之前存在的首选 return 命令语句。

这个查询的意思是,“在拍卖集合中,查找一个具有此地址的特定拍卖”。

完成此 createAuctin 函数的下一步相当hacky。在制作本教程时,我意识到 Fauna 的 GraphQL API 不支持自定义标量(您可以在 其 GraphQL 文档的“限制”部分 中阅读有关此问题的更多信息)。不幸的是,这意味着 Emanator 的 monorepo 的 GraphQL 架构无法开箱即用。最终,这导致不得不进行许多细微的更改,才能使应用程序能够正确运行拍卖的创建。因此,与其详细讲解这一部分,我将首先向您展示差异,然后简要总结更改的目的。

查看第 100 和 101 行的绿色行,我们可以看到这里使用的函数命令并没有太大区别;这里,我们只是在拍卖集合中创建了一个新文档,而不是从索引中读取数据。

回到此 createAuction 函数的数据字段,我们可以看到我们得到了一个 input 作为参数,实际上指的是主页上新 NFT 拍卖表单的 UI 输入字段。因此,input 是一个包含六个字段的对象,即 addressnameownerwinLengthdescriptioncontentHash。但是,完成拍卖类型的 GraphQL 架构所需的另外四个字段仍然缺失!因此,我创建的其他变量 iddateTimestatushighBid 是我或多或少硬编码的变量,以便此函数能够成功完成。

最后,我们需要完成 Auction 常量的导出。为此,我们将再次使用 Fauna 客户端进行以下更改

我们终于完成了第一个服务 🎊 ,真是太棒了!

完成 GraphQL 服务

到目前为止,您可能已经厌倦了更新 GraphQL 服务的这些更改(我知道我在尝试学习必要的更改时感到很疲倦!)。因此,为了节省您将此应用程序正常运行的时间,我将与您分享 integrating-fauna 分支中的 git 差异,我已经在存储库中对其进行了正常运行。分享完后,我将总结所做的更改。

要更新的第一个文件是 api/src/services/bids/bids.js

最后,更新我们最后的 GraphQL 服务

最后,web/src/components/AuctionCell/AuctionCell.js 中还需要进行一项更改

因此,回到 Fauna 不支持自定义标量的问题。由于 Fauna 不支持自定义标量,因此我们不得不从 web3.js 服务查询中注释掉 pastAuctions 字段(以及将其从我们的 GraphQL 架构中注释掉)。

web/src/components/AuctionCell/AuctionCell.js 中进行的最后一次更改是另一个权宜之计,目的是使新创建的 NFT 地址域名(您可以在创建新的 NFT 后,点击主页右侧 NFT 名称的超链接,导航到这些域名)可点击,而不会抛出错误。😄 

结论

最后,当您运行

yarn rw dev

...并创建新的代币时,您现在可以使用 Fauna 来完成!🎉🎉🎉🎉

最终说明

有两个注意事项。首先,您将在创建 NFT 并使用 MetaMask 确认交易后,在创建 NFT 表单上方看到此烦人的错误消息。

不幸的是,除了刷新页面,我找不到解决此问题的方法。因此,我们将像我们最初的 Emanator 单体仓库版本一样进行操作。

但是,当您刷新页面时,您应该会看到您的新代币显示在右侧!👏 

 而且,这是使用从 Fauna 获取的 NFT 代币数据!🙌 🕺 🙌🙌

第二个注意事项是,由于 web/src/components/AuctionCell/AuctionCell.js 中的错误,新 NFT 的页面仍然无法渲染。

这是我无法解决的另一个问题。但是,您可以在这里发挥作用!此仓库 redwood-eth-with-fauna 在 GitHub 上公开提供,以及(目前)最终的 integrating-fauna 分支,其中包含一个工作(如目前工作😅)的 Emanator 应用程序版本。因此,如果您对该应用程序真的很感兴趣,并且想探索如何使用 Fauna 进一步利用该应用程序,请随时分叉该项目并进行探索或更改!您随时可以在 GitHub 上联系我,我很乐意帮助您!😊

本教程就到这里,希望您喜欢!如果您有任何问题,请随时在 GitHub 上联系我!