TheGraph 一: 架构解析

TheGraph 一: 架构解析
Photo by Shubham Dhage / Unsplash

The Graph 的整体架构图:

The Graph 与 SubGraph 交互流程的时序图:

接下来,我们结合架构图和时序图,对其中的关键节点和源码进行详细解释,并说明 subgraph 中的关键定义数据是如何串联整个流程的。

一、Subgraph Instance Manager 加载和管理 Subgraph Instance

  1. load_subgraph_manifests() 方法加载所有已部署的 subgraph 配置信息。
fn load_subgraph_manifests(&self) -> Result<Vec<SubgraphManifest>, Error> {
    // ...
    let mut manifests = vec![];
    for entry in fs::read_dir(&self.subgraph_manifest_dir)? {
        // ...
        let manifest = yaml::from_reader(file).map_err(|e| {
            format_err!("invalid subgraph manifest in {:?}: {}", path, e)
        })?;
        manifests.push(manifest);
    }
    Ok(manifests)
}

这里的 SubgraphManifest 就是 subgraph 的关键定义数据之一,它包含了 subgraph 的 schema、数据源、mapping 函数等信息。

  1. create_instance() 方法根据 SubgraphManifest 创建 SubgraphInstance
fn create_instance(
    &self,
    manifest: SubgraphManifest,
    host_metrics: Arc<HostMetrics>,
) -> Result<SubgraphInstance, Error> {
    // ...
    let required_capabilities = manifest.required_ethereum_capabilities();
    let network_name = manifest.network_name();
    let instance = SubgraphInstance::from_manifest(
        &manifest,
        self.host_builder.clone(),
        host_metrics.cheap_clone(),
        self.link_resolver.cheap_clone(),
        required_capabilities,
    )?;
    Ok(instance)
}

可以看到,SubgraphInstance 是根据 SubgraphManifest 创建的,SubgraphManifest 中定义的信息会被传递到 SubgraphInstance 中。

二、BlockIngestor 获取区块数据并分发给 SubgraphInstance

  1. process_block() 方法根据 SubgraphManifest 中定义的 DataSource,将区块数据拆分为一个或多个 TriggerData
fn process_block(&mut self, block_ptr: BlockPtr) -> Result<bool, Error> {
    // ...
    for data_source in self.ctx.manifest.data_sources.iter() {
        let data_source_name = data_source.name.as_str();
        let triggers = data_source.triggers_in_block(&block_ptr)?;
        // ...
        for trigger in triggers {
            // ...
            self.process_data_trigger(trigger, block_ptr)?;
            // ...
        }
    }
    Ok(true)
}

这里的 data_source 就是 subgraph 的另一个关键定义数据,它定义了如何从区块链上获取数据,以及如何将原始数据转换为 TriggerData

  1. process_trigger() 方法将 TriggerData 发送给对应的 SubgraphInstance
pub fn process_trigger(
    &self,
    logger: &Logger,
    manifest_id: &SubgraphDeploymentId,
    trigger: &TriggerData,
    state: BlockState,
    proof_of_indexing: SharedProofOfIndexing,
    causality_region: &str,
) -> Result<BlockState, MappingError> {
    let instance = self.instances.read().unwrap().get(manifest_id).ok_or_else(|| {
        format_err!("no instance for subgraph `{}` when processing trigger", manifest_id)
    })?;

    instance.process_trigger(logger, trigger, state, proof_of_indexing, causality_region)
}

这里的 manifest_id 就是用来标识和匹配 SubgraphInstance 的,确保 TriggerData 被发送到正确的 SubgraphInstance

三、SubgraphInstance 处理 TriggerData 并将结果写入数据库

  1. process_trigger() 方法处理 TriggerData,调用 mapping 函数生成 EntityOperation
pub fn process_trigger(
    &mut self,
    logger: &Logger,
    trigger: &TriggerData,
    mut state: BlockState,
    proof_of_indexing: SharedProofOfIndexing,
    causality_region: &str,
) -> Result<BlockState, MappingError> {
    // ...
    let trigger = Arc::new(trigger.to_asc_ptr());

    let result = self.ctx.host_exports.ethereum_call(
        logger.clone(),
        block_ptr,
        trigger,
        state.deref_mut(),
        proof_of_indexing,
        causality_region,
    );
    // ...
}

这里的 mapping 函数就是 subgraph 的第三个关键定义数据,它定义了如何将 TriggerData 转换为 EntityOperation,也就是如何将区块链数据转换为 The Graph 的数据模型。

  1. write_entity_operations() 方法将 EntityOperation 写入本地的 PostgreSQL 数据库。
fn write_entity_operations(
    &self,
    logger: &Logger,
    entity_operations: Vec<EntityOperation>,
) -> Result<(), StoreError> {
    // ...
    let entity_count = self.store.transact_block_operations(
        entity_operations,
        None,
        &self.subgraph_id,
        block_ptr,
        store_event,
    )?;
    // ...
}

这里的 entity_operations 就是 mapping 函数的输出,代表了对数据库的写入操作。store 则是 The Graph 的本地数据库组件,负责将数据持久化存储。

四、GraphQL API 提供数据查询服务

GraphQL API 会根据 subgraph 的 schema 定义生成相应的查询接口,供 Dapp 调用。当收到查询请求时,会将请求转换为相应的数据库查询,从 PostgreSQL 中获取数据并返回。

这里的 schema 定义就是 subgraph 的第四个关键定义数据,它定义了 The Graph 数据模型的结构,以及暴露给 Dapp 的 GraphQL 查询接口。

综上所述,subgraph 的四个关键定义数据:

  1. SubgraphManifest: 定义了 subgraph 的基本信息,如 schema、数据源、mapping 函数等。
  2. DataSource: 定义了如何从区块链获取数据,以及如何将原始数据转换为 TriggerData
  3. Mapping 函数: 定义了如何将 TriggerData 转换为 EntityOperation,即如何将区块链数据转换为 The Graph 的数据模型。
  4. Schema: 定义了 The Graph 数据模型的结构,以及暴露给 Dapp 的 GraphQL 查询接口。

这四个部分串联了整个 The Graph 的工作流程,从区块链数据的获取、到数据的转换和存储、再到数据的查询和服务,形成了一个完整的数据处理管道。

通过这种方式,The Graph 实现了区块链数据的高效索引和查询,为 Dapp 提供了一个方便、快捷、灵活的数据服务层。Dapp 开发者可以通过定义 subgraph,将复杂的区块链数据映射为简单的 GraphQL 模型,大大降低了开发复杂度和门槛。

同时,这种架构也具有很好的可扩展性和可维护性。新的 Dapp 可以方便地接入 The Graph 网络,只需定义自己的 subgraph 即可。而 The Graph 网络可以通过增加节点来横向扩展,支持更大的数据量和查询吞吐。

希望通过这个结合架构图和关键源码的分析,能够帮助你更全面、更深入地理解 The Graph 的工作原理,以及 subgraph 在其中扮演的重要角色。如果还有任何问题或建议,欢迎随时交流探讨。

Read more

Vue.js异步更新与nextTick机制深度解析(上篇)

Vue.js异步更新与nextTick机制深度解析(上篇)

本文目标 学完本文,你将能够: * 理解Vue.js为什么采用异步更新策略 * 掌握更新队列的设计思想和实现机制 * 深入理解Event Loop在Vue中的应用 * 了解nextTick的多种实现方式 系列导航 上一篇: Diff算法深度剖析 | 下一篇: Vue.js异步更新与nextTick机制(下篇) | 组件系统架构 引言:为什么DOM更新是异步的? 在Vue.js开发中,你可能遇到过这样的场景: // 场景1:连续修改数据 export default { data() { return { count: 0 } }, methods: { increment() { // 如果每次修改都立即更新DOM,会触发3次DOM更新 this.count++ // 触发一次? this.count++ // 触发一次? this.count++ // 触发一次? // 实际上:Vue只会触发一次DOM更新!

Vue.js组件系统架构深度解析

本文目标 学完本文,你将能够: * 理解Vue.js组件从创建到销毁的完整生命周期 * 掌握组件实例化和初始化的内部流程 * 深入理解父子组件通信的底层机制 * 学会实现完整的插槽系统(包括作用域插槽) * 掌握动态组件和异步组件的实现原理 * 应用组件级别的性能优化技巧 系列导航 上一篇: 异步更新与nextTick(下篇) | 下一篇: 状态管理模式 引言:组件是如何工作的? 在Vue.js开发中,我们每天都在使用组件。但你是否想过: // 当我们这样定义一个组件 const MyComponent = { data() { return { count: 0 } }, template: '<button @click="count++">{{ count }}</button>' } // 并使用它时 new Vue({ components: { MyComponent }, template:

Vue.js状态管理模式:构建可扩展的应用架构

本文目标 学完本文,你将能够: * 理解为什么大型应用需要状态管理 * 掌握Vuex的核心设计模式和实现原理 * 实现一个简化版的状态管理库 * 理解模块化和命名空间的设计思想 * 掌握大型应用的状态管理最佳实践 * 了解现代状态管理方案的演进 系列导航 上一篇: 组件系统架构 | 下一篇: 性能优化实践 1. 为什么需要状态管理? 1.1 组件通信的困境 在大型Vue.js应用中,组件间的通信会变得异常复杂: // 问题场景:多层级组件的状态共享 // GrandParent.vue <template> <Parent :user="user" @update-user="updateUser" /> </template> // Parent.vue <template> <Child

Vue.js依赖收集与追踪机制深度剖析

本文目标 学完本文,你将能够: * 理解Vue.js如何精确知道哪些组件需要更新 * 掌握Dep、Watcher、Observer三大核心类的协作机制 * 深入理解依赖收集的时机和完整过程 * 能够手写一个完整的依赖收集系统 * 解决实际开发中的依赖追踪问题 系列导航 上一篇: 响应式系统核心原理 | 下一篇: Virtual DOM实现详解 引言:为什么Vue知道哪些组件需要更新? 在使用Vue.js时,你是否想过这样一个问题:当我们修改一个数据时,Vue是如何精确地知道哪些组件用到了这个数据,并只更新这些组件的? // 假设有这样的场景 const app = new Vue({ data: { user: { name: 'John', age: 25 } } }); // 组件A只用到了user.name // 组件B只用到了user.age // 组件C同时用到了name和age // 当我们修改user.name时 app.user.name = 'Jane&