本文主要简单分析一下DApp的前后端架构
如果想成为一名[DApp开发](https://learnblockchain.cn/article/1046)者,那么最好对DApp的整个前服务端架构都有一个基本的理解。与传统的App(包括Web App、Mobile App和Client App)最大的不同点在于, DApp的核心功能需要与智能合约直接交互的。所以理解DApp中前端、服务端、数据存储和智能合约之间的通信关系,就能很好的理解DApp的整体架构。
现在市面上大部分Dapp都是Web App,而且不提供Mobile App版本,一部分原因是因为构建一个跨平台钱包方案较为复杂,并且大部分用户都使用的例如MetaMask这样的浏览器插件,在Web平台上目前开发DApp的开发效率更高而且体验更好。所以本文的架构分析也将主要以Web App去讲解。
在谈论DApp架构前,我们先来看传统Web2.0的App的架构一般是怎么样的。
# 传统的App架构

不考虑较为复杂的微服务架构和其他RPC等调用, Web2.0时代传统的单体App架构一般如上图所示,由前端、服务端和数据库三部分组成:
* 前端/客户端 :用户界面的代码,如果B/S架构,那么前端代码由HTML/CSS/JavaScript编写运行在浏览器端,通常使用流行的React或Vue编写。如果是C/S架构,那么相应的客户端代码运行在客户端,当然你可以可以使用Flutter或RN等跨平台方案同时编写前端/客户端程序。
* 服务端 :业务的服务端逻辑代码,可以由Node.js、Java或者Python等任意一个或多个服务端语言编写。对前端HTTP接口请求提供响应,并对数据进行CRUD和业务处理操作,整个App的核心业务逻辑都会在服务端运行。
* 数据库 :保存用户状态和业务数据,可以使用一个或多个数据库,充分利用不同数据库存储数据的优点。
# DApp架构
在前文中,我们有提到DApp和传统的App最大的不同点在于部署到区块链上的智能合约。所以在讲解DApp架构前,我们先来理清DApp的几个与智能合约相关的概念。
## 几个核心概念
### 智能合约
通常指运行在EVM兼容网络中的Solidity或其他合约语言代码,它们负责与用户交易我们发行的数字资产、存储DApp的链上状态。 智能合约中还有两个比较重要的概念:
* 合约地址:智能合约在区块链上的唯一地址。
* ABI:应用程序二进制接口,是一个JSON格式的文件,其中包含合约中函数的输入参数、输出和数据类型。
### Provider/Signer
Provider/Signer是DApp开发的重要组成部分。智能合约可以在区块链上读取数据和写入数据(即执行改变区块链状态的交易),要从区块链中读取数据,你需要Provider(提供者);要写入数据,你需要Signer(签名者)。
如果你有使用过[web3.js](https://web3js.readthedocs.io/en/v1.7.4/)或者[ethers.js](https://docs.ethers.io/v5/)这类库的经历,那么肯定对于Provider/Signer的概念不会陌生,因为你首先就得设置Provider:
“`javascript
const Web3 = require(‘web3’);
const web3 = new Web3(Web3.givenProvider || “ws://localhost:8545”);
“`
### MetaMask和服务提供商
MetaMask是以太坊中最流行的钱包,同时也是目前开发DApp最流行的InjectProvider。
但MetaMask其实是依赖于[Infrua](https://infura.io/)这个服务提供商,这类服务提供商也被称为API 提供商 或 IaaS,它们提供了全节点和一些开发套件来帮助开发者更方便的访问区块链,你不用运行自己的区块链节点。
你可以在ethers.js中直接使用`InfruaProvider`:
“`
const { ethers } = require(“ethers”);
const provider = new ethers
.providers
.InfuraProvider(‘rinkeby’,INFURA_PROJECT_ID);
“`
如果你想了解Infrua和Metamask的关系?以及什么是服务提供商?你可以参考Metamask官网的这篇文章[《What is Infura, and why does MetaMask use it?》](https://metamask.zendesk.com/hc/en-us/articles/4417315392795-What-is-Infura-and-why-does-MetaMask-use-it-)。市面上流行的服务提供商除了[Infura](https://infura.io/),还有[Alchemy](https://www.alchemy.com/)、[Quicknode](https://www.quicknode.com/)、[Moralis](https://moralis.io/) 或者 [Pocket](https://www.pokt.network/) 等,它们通过RPC协议与以太坊节点进行通信。
## 架构概览

上图是一个DApp的基础架构图,你图中可以看到,我们可以在前端直接通过Provider的区块链节点访问区块链网络,也可以在服务端进行访问。因为我们构建的是DApp(去中心化应用),链下的数据(视频、图片、元数据和其他业务数据)最好以去中心化存储方案存储,下文我们会对DApp的数据存储进行介绍。
### 前端
作为DApp的前端开发,[web3.js](https://web3js.readthedocs.io/en/v1.7.4/)或者[ethers.js](https://docs.ethers.io/v5/)这两个基础类库是你应该熟悉。在项目的脚手架,你可以参考[scaffold-eth](https://github.com/scaffold-eth/scaffold-eth)这个项目。
至于前端框架的选择,你可以选择你熟悉的Web开发框架。例如Vue或React,但是这边我更加推荐React或Next.js,因为React的社区中有更多活跃的Web3开发者和依赖库,例如[web3-react](https://github.com/NoahZinsmeister/web3-react)、[wagmi](https://github.com/tmm/wagmi),它们提供了方便的hook来让你与Provider和合约进行交互。
DApp的前端开发与web2的前端开发不同的主要在于以下几点:
* 认证: 传统的web2前端开发通常使用cookie或token的传统验证方式。在DApp中,首选会要求用户将钱包与站点连接。上面提到的类库都能够实现这一环节
* 读取数据: 传统的web2的请求读取数据是通过API`fetch`或者`axios`。在DApp中,你如果直接通过前端读取区块链网络的数据,需要基于Provider,你同样通过上面的类库完成。需要注意的的是,当你尝试从智能合约中读取数字时,你获取的值是`BigNumber`而不是`number`,你需要进行类型转换。
* 写入数据: 写入数据和读取数据的语法基本相同,在[Provider](https://docs.ethers.io/v5/api/providers/provider/)的基础上你需要提供[Signer](https://docs.ethers.io/v5/api/signer/)。你需要清楚的是,你与之通信的是智能合约的写操作函数,并且需要做好错误处理。
### 服务端
如果你是一个前端开发者或者一个刚入行的web3从业者,你可能倾向于绕过服务端,仅依赖前端和智能合约交互。但是目前市面上大部分的DApp都有服务端参与,只有少数产品仅仅只有前端和智能合约两个部分,例如[Uniswap](https://app.uniswap.org/#/swap)。
如果你期望你的DApp有着更好的用户体验和响应速度,通常来说引入服务端是一个更好的方式。原因之一是链上存储的成本过高,反复的签名对用户来说体验是不好的。如果你构建的一个大型DApp,例如像[Odysee](https://odysee.com/),你绝大多数的数据(视频、图片、主页信息等)不需要在链上进行存储,而是通过传统的HTTP交互,并将核心的hash记录存储在链上即可。
这边服务端语言推荐使用Node.js,这样可以复用一部分的代码,而且在和区块链服务提供商交互的逻辑也基本一致,你还可以使用类似Firebase等FaaS服务去编写你的服务端业务。
DApp的服务端和传统的Web2.0服务端大致的区别在于:
* 认证: 如果你引入了服务端,在钱包的连接上推荐基于Siwe([EIP-4361](https://docs.login.xyz/general-information/siwe-overview/eip-4361))的方式。该草案目的在于标准化开发者使用钱包登录授权Off-chain产品的逻辑。这是一份基于express和siwe的[示例代码](https://github.com/spruceid/siwe-quickstart/blob/main/02_backend/src/index.js)。
* 区块链通信: 相比传统的服务端程序,DApp的服务端增加了与区块链的通信。你需要了解你所使用的服务提供商的API接口调用方式,并且做好错误处理。
* 写操作延时问题: 通常来说与智能合约的写操作耗时都比较长,你不可能等待几十秒再给前端返回数据,所以你需要做好相应写操作的状态扭转的处理,在交易状态发生改变后,通过前端轮询或者websocket的方式告知前端。
### 智能合约
智能合约是整个DApp核心中的核心,智能合约的编写推荐使用[Hardhat](https://hardhat.org/)或[Truffle](https://trufflesuite.com/)框架,他们提供了智能合约的开发、测试、部署的整套开发环境,同时还有丰富的插件系统。
在智能合约中除了业务逻辑的编写外,有两点是你需要注意的:
* Gas成本: 合约改变和存储状态都需要消耗Gas,所以合约的运行成本优化是一个重要的测试环节。在编写合约时,你需要对数据结构声明存储空间位置,例如`storage`、`memoery`,每种位置产生的费用有很大的不同。合约的函数也需要有对应的函数类型声明,`view`函数 与`pure` 函数在外部调用时不需要承担 gas 费用。你可以考虑在合适的时间引入MerkleProof,这是它的[JavaScript实现](https://github.com/miguelmota/merkletreejs)。
* 安全性: 合约的安全性是无论怎么强调都不为过的,常见的EIP实现你可以引入[OpenZeppelin库](https://github.com/OpenZeppelin)实现,并进行完整的测试和较高的测试覆盖率。对于安全性请务必阅读[Ethereum Smart Contract Security Best Practices](https://consensys.github.io/smart-contract-best-practices/)。
### 数据存储
除了引入服务端的DApp经常被诟病说不是完全的去中心化外,存储方案也是经常被同时提及的。笔者看来,并不是什么功能都应该“分布式”和“去中心化”,对于数据的存储,还是应该更多样化,提供最符合数据存储需求的解决方案。
例如你想创建一个像[Odysee](https://odysee.com/)这样的去中心化视频网站,在链上存储的状态应该是媒体文件和内容文本的hash值,原始的视频、图片、视频标题、描述等数据,不应该上链存储。
对于链下存储,如果你想更加的去中心化,你可以将其存储在去中心化存储网络中,流行的去中心化存储方案有:
1. [IPFS](https://ipfs.io/):最早和最流行的去中心化储存网络。
2. [Filecoin](https://filecoin.io/zh-cn/):以 IPFS 为基础的储存网络。
3. [Arweave](https://www.arweave.org/)(AR):去中心化的永存网络,一次写入付费,读数据免费。
# 参考资料
1. [Web3 DApp 最佳编程实践指南 – 郭宇](https://learnblockchain.cn/article/4072)
2. [What is Infura, and why does MetaMask use it? – MetaMask](https://metamask.zendesk.com/hc/en-us/articles/4417315392795-What-is-Infura-and-why-does-MetaMask-use-it-)
3. [A guide to Web3 for Web2 frontend devs](https://mirror.xyz/dhaiwat.eth/O5CK6Tjfv8uhl6FPbjT0yZ8LUwViDPWGYHdu9khRWpM)
4. [Everything I learnt building my first DApp — a frontend perspective](https://coinsbench.com/everything-i-learnt-building-my-first-dapp-a-frontend-perspective-ba810be1493f)
5. [智能合约开发的最佳实践 – 强烈推荐](https://learnblockchain.cn/article/1717)
> 欢迎关注我的同名vx公众号:熠辉Web3。了解更多区块链DApp开发教程
前言
如果想成为一名DApp开发者,那么最好对DApp的整个前服务端架构都有一个基本的理解。与传统的App(包括Web App、Mobile App和Client App)最大的不同点在于, DApp的核心功能需要与智能合约直接交互的。所以理解DApp中前端、服务端、数据存储和智能合约之间的通信关系,就能很好的理解DApp的整体架构。
现在市面上大部分Dapp都是Web App,而且不提供Mobile App版本,一部分原因是因为构建一个跨平台钱包方案较为复杂,并且大部分用户都使用的例如MetaMask这样的浏览器插件,在Web平台上目前开发DApp的开发效率更高而且体验更好。所以本文的架构分析也将主要以Web App去讲解。
在谈论DApp架构前,我们先来看传统Web2.0的App的架构一般是怎么样的。
传统的App架构
不考虑较为复杂的微服务架构和其他RPC等调用, Web2.0时代传统的单体App架构一般如上图所示,由前端、服务端和数据库三部分组成:
- 前端/客户端 :用户界面的代码,如果B/S架构,那么前端代码由HTML/CSS/JavaScript编写运行在浏览器端,通常使用流行的React或Vue编写。如果是C/S架构,那么相应的客户端代码运行在客户端,当然你可以可以使用Flutter或RN等跨平台方案同时编写前端/客户端程序。
- 服务端 :业务的服务端逻辑代码,可以由Node.js、Java或者Python等任意一个或多个服务端语言编写。对前端HTTP接口请求提供响应,并对数据进行CRUD和业务处理操作,整个App的核心业务逻辑都会在服务端运行。
- 数据库 :保存用户状态和业务数据,可以使用一个或多个数据库,充分利用不同数据库存储数据的优点。
DApp架构
在前文中,我们有提到DApp和传统的App最大的不同点在于部署到区块链上的智能合约。所以在讲解DApp架构前,我们先来理清DApp的几个与智能合约相关的概念。
几个核心概念
智能合约
通常指运行在EVM兼容网络中的Solidity或其他合约语言代码,它们负责与用户交易我们发行的数字资产、存储DApp的链上状态。 智能合约中还有两个比较重要的概念:
- 合约地址:智能合约在区块链上的唯一地址。
- ABI:应用程序二进制接口,是一个JSON格式的文件,其中包含合约中函数的输入参数、输出和数据类型。
Provider/Signer
Provider/Signer是DApp开发的重要组成部分。智能合约可以在区块链上读取数据和写入数据(即执行改变区块链状态的交易),要从区块链中读取数据,你需要Provider(提供者);要写入数据,你需要Signer(签名者)。
如果你有使用过web3.js或者ethers.js这类库的经历,那么肯定对于Provider/Signer的概念不会陌生,因为你首先就得设置Provider:
const Web3 = require('web3');
const web3 = new Web3(Web3.givenProvider || "ws://localhost:8545");
MetaMask和服务提供商
MetaMask是以太坊中最流行的钱包,同时也是目前开发DApp最流行的InjectProvider。
但MetaMask其实是依赖于Infrua这个服务提供商,这类服务提供商也被称为API 提供商 或 IaaS,它们提供了全节点和一些开发套件来帮助开发者更方便的访问区块链,你不用运行自己的区块链节点。
你可以在ethers.js中直接使用InfruaProvider
:
const { ethers } = require("ethers");
const provider = new ethers
.providers
.InfuraProvider('rinkeby',INFURA_PROJECT_ID);
如果你想了解Infrua和Metamask的关系?以及什么是服务提供商?你可以参考Metamask官网的这篇文章《What is Infura, and why does MetaMask use it?》。市面上流行的服务提供商除了Infura,还有Alchemy、Quicknode、Moralis 或者 Pocket 等,它们通过RPC协议与以太坊节点进行通信。
架构概览
上图是一个DApp的基础架构图,你图中可以看到,我们可以在前端直接通过Provider的区块链节点访问区块链网络,也可以在服务端进行访问。因为我们构建的是DApp(去中心化应用),链下的数据(视频、图片、元数据和其他业务数据)最好以去中心化存储方案存储,下文我们会对DApp的数据存储进行介绍。
前端
作为DApp的前端开发,web3.js或者ethers.js这两个基础类库是你应该熟悉。在项目的脚手架,你可以参考scaffold-eth这个项目。
至于前端框架的选择,你可以选择你熟悉的Web开发框架。例如Vue或React,但是这边我更加推荐React或Next.js,因为React的社区中有更多活跃的Web3开发者和依赖库,例如web3-react、wagmi,它们提供了方便的hook来让你与Provider和合约进行交互。
DApp的前端开发与web2的前端开发不同的主要在于以下几点:
- 认证: 传统的web2前端开发通常使用cookie或token的传统验证方式。在DApp中,首选会要求用户将钱包与站点连接。上面提到的类库都能够实现这一环节
- 读取数据: 传统的web2的请求读取数据是通过API
fetch
或者axios
。在DApp中,你如果直接通过前端读取区块链网络的数据,需要基于Provider,你同样通过上面的类库完成。需要注意的的是,当你尝试从智能合约中读取数字时,你获取的值是BigNumber
而不是number
,你需要进行类型转换。 - 写入数据: 写入数据和读取数据的语法基本相同,在Provider的基础上你需要提供Signer。你需要清楚的是,你与之通信的是智能合约的写操作函数,并且需要做好错误处理。
服务端
如果你是一个前端开发者或者一个刚入行的web3从业者,你可能倾向于绕过服务端,仅依赖前端和智能合约交互。但是目前市面上大部分的DApp都有服务端参与,只有少数产品仅仅只有前端和智能合约两个部分,例如Uniswap。
如果你期望你的DApp有着更好的用户体验和响应速度,通常来说引入服务端是一个更好的方式。原因之一是链上存储的成本过高,反复的签名对用户来说体验是不好的。如果你构建的一个大型DApp,例如像Odysee,你绝大多数的数据(视频、图片、主页信息等)不需要在链上进行存储,而是通过传统的HTTP交互,并将核心的hash记录存储在链上即可。
这边服务端语言推荐使用Node.js,这样可以复用一部分的代码,而且在和区块链服务提供商交互的逻辑也基本一致,你还可以使用类似Firebase等FaaS服务去编写你的服务端业务。
DApp的服务端和传统的Web2.0服务端大致的区别在于:
- 认证: 如果你引入了服务端,在钱包的连接上推荐基于Siwe(EIP-4361)的方式。该草案目的在于标准化开发者使用钱包登录授权Off-chain产品的逻辑。这是一份基于express和siwe的示例代码。
- 区块链通信: 相比传统的服务端程序,DApp的服务端增加了与区块链的通信。你需要了解你所使用的服务提供商的API接口调用方式,并且做好错误处理。
- 写操作延时问题: 通常来说与智能合约的写操作耗时都比较长,你不可能等待几十秒再给前端返回数据,所以你需要做好相应写操作的状态扭转的处理,在交易状态发生改变后,通过前端轮询或者websocket的方式告知前端。
智能合约
智能合约是整个DApp核心中的核心,智能合约的编写推荐使用Hardhat或Truffle框架,他们提供了智能合约的开发、测试、部署的整套开发环境,同时还有丰富的插件系统。
在智能合约中除了业务逻辑的编写外,有两点是你需要注意的:
- Gas成本: 合约改变和存储状态都需要消耗Gas,所以合约的运行成本优化是一个重要的测试环节。在编写合约时,你需要对数据结构声明存储空间位置,例如
storage
、memoery
,每种位置产生的费用有很大的不同。合约的函数也需要有对应的函数类型声明,view
函数 与pure
函数在外部调用时不需要承担 gas 费用。你可以考虑在合适的时间引入MerkleProof,这是它的JavaScript实现。 - 安全性: 合约的安全性是无论怎么强调都不为过的,常见的EIP实现你可以引入OpenZeppelin库实现,并进行完整的测试和较高的测试覆盖率。对于安全性请务必阅读Ethereum Smart Contract Security Best Practices。
数据存储
除了引入服务端的DApp经常被诟病说不是完全的去中心化外,存储方案也是经常被同时提及的。笔者看来,并不是什么功能都应该“分布式”和“去中心化”,对于数据的存储,还是应该更多样化,提供最符合数据存储需求的解决方案。
例如你想创建一个像Odysee这样的去中心化视频网站,在链上存储的状态应该是媒体文件和内容文本的hash值,原始的视频、图片、视频标题、描述等数据,不应该上链存储。
对于链下存储,如果你想更加的去中心化,你可以将其存储在去中心化存储网络中,流行的去中心化存储方案有:
- IPFS:最早和最流行的去中心化储存网络。
- Filecoin:以 IPFS 为基础的储存网络。
- Arweave(AR):去中心化的永存网络,一次写入付费,读数据免费。
参考资料
- Web3 DApp 最佳编程实践指南 – 郭宇
- What is Infura, and why does MetaMask use it? – MetaMask
- A guide to Web3 for Web2 frontend devs
- Everything I learnt building my first DApp — a frontend perspective
- 智能合约开发的最佳实践 – 强烈推荐
欢迎关注我的同名vx公众号:熠辉Web3。了解更多区块链DApp开发教程
本文参与区块链开发网写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
- 发表于 2022-07-05 16:30
- 阅读 ( 1742 )
- 学分 ( 87 )
- 分类:DApp