上一篇我们在说网关的时候,遗留了一个问题,就是通过 serviceList 配置子服务无法实现动态更新。同时,各个子服务的 Schema 在合成的时候可能会出错,比如类型重复定义等。

因此,我们需要一个独立的 Schema 合成管理系统来处理 Schema 的合成校验、不同环境的隔离、CI/CD 等工程问题。

首先要说明的是,Apollo 官方提供了一个 Apollo Studioopen in new window 解决方案,可以解决我们本文讨论的问题。缺点是没有提供自托管(self host)方案,需要将 schema 上传到 Apollo 的服务中。

本文来探讨实现一个 Schema 合成管理系统。

在我们这个系统中,存储各个子服务的 Schema,同时提供 Schema 的合并校验能力,将合并后的 Schema 提供给网关使用。

通常来说,服务部署都会依赖 CI/CD 来实现。因此,我们的 Schema 合成管理系统应该与 CI/CD 对接,而不是手动上传 Schema。

Schema 的合成校验

在子服务中,我们可以通过如何 GraphQL 查询获取子服务的 Schema 信息。

query SDL {
  _service {
    sdl
  }
}
1
2
3
4
5

在上传到 Schema 合成管理系统中后,可以通过如下方法校验各个子服务的 Schema 是否合法。

import { parse } from 'graphql';
import { composeAndValidate } from '@apollo/federation';

// serviceList 为查到的所有子服务的 Schema 信息
const serviceDefinitions = [
  {
    typeDefs: parse(service1.sdl),
    name: service1.name,
  },
  {
    typeDefs: parse(service2.sdl),
    name: service2.name,
  }
];

// 获取合成结果
const validateResult = composeAndValidate(serviceDefinitions);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

数据字段设计

知道如何获取子服务的 Schema 以及如何进行校验之后,我们来定义系统的数据库表。

首先定义一张存储各个子服务信息的表。

uml diagram

tag 字段可以给每个服务打上不同的标签,方便后续管理。

再来定义每个服务的 Schema 信息表。

uml diagram

在 SDL 表中,除了记录 Schema 的 content 字段以外,我们使用 git_commit_hash 来作为每次发布的版本号,同时记录提交人,方便后续问题的追踪。

为了方便网关拉取合成后的数据,我们单独定义一张表记录合成后的各服务 Schema 信息。

uml diagram

不同的环境都会有网关,因此通过 env 字段记录环境。

与 CI/CD 系统的交互

前面说到,Schema 合成管理系统应该与 CI/CD 对接,杜绝个人上传 Schema 的操作。因此,我们需要定义与 CI/CD 的交过流程。

子服务构建时推送 Schema

如下是子服务构建时与 Schema 合成管理系统的交互时序图。

uml diagram

子服务启动时确定 Schema 是否合法

如下是子服务启动时与 Schema 合成管理系统的交互时序图。

uml diagram

之所以要在子服务启动时才校验 Schema 是否合法,是因为服务部署需要考虑环境,而 CI 在构建打包的时候应该与环境无关,因此,我们在服务启动时做校验。

网关拉取 Schema

网关是对外提供服务的,需要拉取最新可用的全部 Schema。前面的设计中,Schema 合成管理系统的 Running_Config 表记录了当前可用的所有子服务的 Schema。网关只需要从 Schema 合成管理系统中拉取这个信息就可以了。

uml diagram

在拉取到配置以后,可以通过如下方式实现自动更新网关的 Schema 信息。

const graphqlGatewayModule = GraphQLGatewayModule.forRootAsync({
  useFactory: async () => ({
    server: {
      // 其他配置项
      path: '/bff/graphql',
    },
    gateway: {
      // 一分钟轮询时间
      experimental_pollInterval: 60 * 1000,
      experimental_updateServiceDefinitions: updateServicesDefinitions,
    }
  }),
});
1
2
3
4
5
6
7
8
9
10
11
12
13

我们将原来的 serviceList 换成 updateServicesDefinitions,而 updateServicesDefinitions 负责从 Schema 合成管理系统拉取最新的 Schema 信息返回给网关。

async function updateServicesDefinitions() {
  const data = await fetch('https://gateway-running-config-endpoint')
  const serviceDefinitions = data.map(({ name, url, sdl }) => ({
    typeDefs: parse(sdl),
    name,
    url,
  }));

  return {
    serviceDefinitions,
    isNewSchema: true,
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13

通过这个方法就可以实现网关自动拉取最新的 Schema 信息了。

总结

我们先探索了获取子服务 Schema,并进行合成校验的方法,这是构建 Schema 合成管理系统的技术基石。然后我们设计了实现最简功能所需的数据库表。

完成了 Schema 合成管理系统的内部设计以后,我们对 CI/CD 以及网关如何与 Schema 合成管理系统交互做了定义和说明。

关注微信公众号,获取最新推送~