GraphQL 是一种 API 查询语言,核心特性是数据聚合和裁减。目前被广泛应用在前后端之间,解决客户端灵活使用数据问题。

BFF 的遇到的问题

快速理解 BFF 中,我们知道构建 BFF 的主要目的是为了应对前端页面的高频变化,同时我们还探讨几种构建 BFF 的方案。

在实践过程中,我们发现,不管是按那种方式构建 BFF 我们都会遇到一个棘手的问题。那就是,随着业务场景越来越多,API 呈爆炸趋势,业务支持效率与人力成线性关系,系统难以支撑业务场景的规模化拓展。同时,核心功能不停迭代,内部逻辑充斥着各种 if...else... 判断,系统复杂度极度上升,慢慢的难以维护。

因此,在这种场景多、场景间存在差异的背景下,如何满足场景拓展效率同时能够控制系统的复杂性,就成了我们构建 BFF 索要面临的核心问题

什么是 GraphQL

GraphQL 最早由 Facebook 在 2012 年提出,它是一种查询语言,用于描述客户端-服务器应用中数据模型的功能和要求。同时 GraphQL 还是一个服务端运行时,基于一个事先定义好的类型系统来为客户端的查询提供符合要求的数据。

从物理上看,GraphQL 分为服务端运行时和查询客户端。从逻辑上看,GraphQL 是一套由类型系统定义的 schema 或者说类型。我们通过Schema 定义语言open in new window(Schema Definition Language, SDL)来定义系统中的 schema。

比如我们定义一个 Persion 类型:

type Person {
  name: String!
  age: Int!
}
1
2
3
4

这个类型有两个字段,nameage,分别是字符串和整型,感叹号表示该字段是必须的。定义类型间的依赖关系也非常简单。

比如再定义一个 Post 类型:

type Post {
  title: String!
  author: Person!
}
1
2
3
4

Postauthor 字段是 Persion 类型。同时,Persion 中也可以添加 posts 信息:

type Person {
  name: String!
  age: Int!
  posts: [Post!]!
}
1
2
3
4
5

上面的例子,我们定义了两个类型,并建立了这两个类型间的关联关系。

通过上面的方法,我们可以在系统中定义任意业务类型,同时建立类型间的关系。

有了这些类型,我们就可以定义查询(Query)和修改(Mutation)来操作这些类型。

GraphQL 的查询非常直接,所查即所得。比如我们要查 Person 列表信息,则可以进行如下查询:

{
  personList {
    name
  }
}
1
2
3
4
5

则系统会返回:

{
  "personList": [
    {
      "name": "张三"
    }
  ]
}
1
2
3
4
5
6
7

如果我们要查询更多的 Person 信息,只需要在查询语句中添加要查询的字段即可:

{
  personList {
    name
    age
    posts {
      title
    }
  }
}
1
2
3
4
5
6
7
8
9

系统会返回:

{
  "personList": [
    {
      "name": "张三",
      "age": 21,
      "posts": [
        {
          "title": "什么是 GraphQL?"
        }
      ]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13

按照 GraphQL 的定义,我们查询任何我们需要的信息,同时系统也只会返回这些信息,没有任何冗余。同时,我们应该注意到,Post 类型中还有 Person 类型的字段,理论上我们可以无限递归查询下去。

上面的例子展示了如何查询数据,要实现这个查询能力,我们还需要定义查询类型:

type Query {
  personList: [Person!]!
}
1
2
3

有查询就会有修改,我们也需要定义修改类型:

type Mutation {
  createPersion(name: String!, age: Int!): Person!
}
1
2
3

括号内的是参数,查询同理也支持传入参数。

QueryMutation 这两个类型比较特殊,是 GraphQL 的保留类型,用来定义系统内有哪些查询和修改操作。

规整一下,在我们这个例子中,我们的 schema 为:

type Query {
  personList: [Person!]!
}

type Mutation {
  createPersion(name: String!, age: Int!): Person!
}

type Person {
  name: String!
  age: Int!
  posts: [Post!]!
}

type Post {
  title: String!
  author: Person!
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

定义好了 schema 以后,我们就可以通过客户端和运行时来进行数据的聚合和裁减了。

除了最基本的 QueryMutation 以外,GraphQL 还提供了Directive (指令)open in new window ,用来实现动态查询。即通过指令可以实现同一个 Query,在不同场景下查询不同的数据,用于精细化的场景控制。

GraphQL 的精髓

GraphQL 通过类型定义的方式,构建了系统内各个模型之间的关系,形成了一张关系图。这也是 GraphQL 的精髓。

通常,我们一次数据请求返回的数据都是树形结构,JSON 就是典型的树形结构。那为什么我们还需要定义一张图呢?

在传统 REST 场景下,一次数据请求返回的数据,后端服务都需要构建一棵树。从根节点到叶子节点,所有信息都需要按照页面的要求来。如下图所以,如果页面发生变化,则需要需改接口,生成新的树。

uml diagram

在 GraphQL 中,系统里预先已经有了一张图,这张图里描述了任意模型间的关系,理论上我们可以从任何节点触发构建出来任意棵各种各样的树。换句话说,页面可以在这张图里构建任何它需要的数据。

如下图所示,我们不需要后端接口改动,就可以实现各种数据查询。

uml diagram

前面的例子我们并不能体会到图的巨大价值。如果在社交应用中,比如 Facebook,我们需要查询用户的好友的好友的好友的好友的好友,在传统 REST API 中,我们可以说没有什么好的解法。在 GraphQL 中,我们却跟普通查询一样简单。

query PersonWithFriends {
  persion {
    name
    friends {
      name {
        friends {
          name
          friends {
            name
            friends {
              name
              friends {
                name
              }
            }
          }
        }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

小结

本文主要对 GraphQL 进行概念上的介绍。GraphQL 主要帮助我们解决页面的高频变化给 BFF 层带来的挑战问题。

通过类型系统,将系统中模型之间的关系预先定义清楚,形成一张关系图。再根据页面的实际需要,从这张图中查找生成数据。

使用了 GraphQL 以后,数据结构化清晰。所见即所得,输入和输出结构一致,前端需要什么数据字段,就在查询上填写什么字段,同时支持多层级结构,也可以平级展现,由调用方根据业务决定合适的输出形式。

同时,BFF 还可以进行精细化场景控制。即便是类似的场景,需要的数据也可能不完全相同,指令可以实现这样的精细化控制。graphql 中没有一个数据是多余的

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