数据网关系统设计
- 项目背景
- 问题发现
- 解决方案
- 技术设计
- GraphQL介绍
- 设计框架
- 功能与使用
- 数据查询
- 数据聚合
- 数据计算
- 字段鉴权
- 数据灰度
- 接入方式
- 方法说明
- 后续
项目背景
随着业务的发展,可以预期到平台的相关子系统,会出现大量依赖外部系统的API服务以及内部系统之间数据流转的情况。如何高效维护这些内部和外部的API,低成本接入外部的服务成了一个棘手的问题
问题发现
- 平台内子系统在对接外部系统数据源时,可能有需求重复,导致重复接入增加各子系统开发成本成N*M倍增长;
- 系统中维护大量的数据API和相关的聚合逻辑,增加系统后期的维护和开发成本;
- 和前端交互中,前端页面需要不同的数据聚合,需要服务端提供大量的API和相关聚合逻辑,提升开发成本;
解决方案
- 数据中心,作为在对接外部系统是作为总的数据接入方统一接入,减少重复接入;
- 数据中心将接入的数据进行拆解、进行多层数据关联聚合,减少重复数据聚合逻辑;
- 使用GraphQL查询引擎,借助DSL查询语言减少提供的API,一次接入;
- 使用GraphQL查询引擎,聚合查询数据,减少子业务聚合逻辑开发导致的成本;
技术设计
GraphQL介绍
- GraphQL是一种新的API标准,它提供了一种更高效、强大和灵活的数据提供方式,本质上是一种基于api的查询语言
它是由Facebook开发和开源.
现在大多数应用程序都需要从服务器中获取数据,这些数据存储可能存储在数据库中,API的职责是提供与应用程序需求相匹配的存储数据的接口。 - GraphQL是api的查询语言,而不是数据库
从这个意义上说,它是数据库无关的,而且可以在使用API的任何环境中有效使用,我们可以理解为GraphQL是基于API之上的一层封装,目的是为了更好,更灵活的适用于业务的需求变化。 - 区别于Restful,客户端可以通过GraphQL的DSL自定义返回的数据和相关的聚合
设计框架
消费方通过数据中心的Thrift接口接入,传入要查询的DSL语句到数据中心,数据中心将各方接入的数据聚合后返回结果
功能与使用
- 支持通用GraphQL的Query查询\聚合等功能
- 支持GraphQL的DSL配置功能
- 支持接入方\字段等维度的鉴权功能
- 支持自定义的数据计算\转换功能
- 支持接入方维度的限流
基础的请求语法可以参考
举例有如下待查数据结构Student:
public class Student {
private String name;
private Integer id;
private Integer teacherId;
}
数据查询
基本的GraphQL的查询功能,传入Query+Variables,得到返回结果
请求语句:
query GetStudent($req:GetStudentRequest){
getStudents(request:$req){
students{
id,
name
}
}
}
请求参数:
{
"req":{
"name":"liuzhuan"
}
}
返回结果
{
"data": {
"getStudents": {
"students": [
{
"id": "102",
"name": "S2"
},
{
"id": "103",
"name": "S3"
},
{
"id": "104",
"name": "S4"
},
{
"id": "105",
"name": "S5"
}
]
}
}
}
数据聚合
基本的GraphQL的聚合功能,传入Query+Variables,得到返回结果
请求语句:
query GetStudent($req:GetStudentRequest){
getStudents(request:$req){
students{
id,
name,
teacher{
name
}
}
}
}
请求参数:
{
"req":{
"name":"liuzhuan"
}
}
返回结果
{
"data": {
"getStudents": {
"students": [
{
"id": "102",
"name": "S2",
"teacher":{
"name":"teacherA"
}
},
{
"id": "103",
"name": "S3",
"teacher":{
"name":"teacherA"
}
},
{
"id": "104",
"name": "S4",
"teacher":{
"name":"teacherA"
}
},
{
"id": "105",
"name": "S5"
"teacher":{
"name":"teacherA"
}
}
]
}
}
}
数据计算
支持在传入Query时,对指定的字段,按照其携带的groovy的语句进行处理
如下面的groovy字段和teacher.name字段的处理
该功能可以在需要对一些字段做展示处理时,减少业务层的展示逻辑
请求语句:
query GetStudent($req:GetStudentRequest){
getStudents(request:$req){
students{
id,
name,
groovy @groovy(patterns:["first=${if(id>103) return 'id大于103' else return 'id小于103'}","second=${'名字='+name}"]),
teacher {
id,
name @groovy(pattern:"${'姓名是'+name}")
}
}
}
}
请求参数:
{
"req":{
"name":"liuzhuan"
}
}
返回结果:
{
"data": {
"getStudents": {
"students": [
{
"id": "102",
"name": "S2",
"groovy": {
"first": "id小于103",
"second": "名字=s2"
},
"teacher": {
"id": "2",
"name": "姓名是teacher"
}
},
{
"id": "103",
"name": "S3",
"groovy": {
"first": "id小于103",
"second": "名字=s3"
},
"teacher": {
"id": "3",
"name": "姓名是teacher"
}
},
{
"id": "104",
"name": "S4",
"groovy": {
"first": "id大于103",
"second": "名字=s4"
},
"teacher": {
"id": "4",
"name": "姓名是teacher"
}
},
{
"id": "105",
"name": "S5",
"groovy": {
"first": "id大于103",
"second": "名字=s5"
},
"teacher": {
"id": "5",
"name": "姓名是teacher"
}
}
]
}
}
}
字段鉴权
可以指定对某些字段(Field\Object)的限制请求,该功能目前只能在数据中心的服务端实现配置
type Student @auth(psm:["test"]) { //对Student的访问,只有psm为test
id: ID
name: String @uppercase
sex: Int
teacher: Teacher @auth(psm:["test","$auth_psm"]) //对teacher的访问,只有psm为test或者auth_psm中的来源才能通过
groovy: JSON
}
数据灰度
对接入的数据源,可以根据下游传来的请求的灰度标识,判断其调用上游的灰度服务
接入方式
[Thrift文件]
[Thrift接口定义]
Query方法说明
- 不携带参数的请求使用方法
{
"Query":"query {getStudents(request:{name:\"liuzhuan\"}){students{id,name}}}",
"Psm":"test",
"Token":"abcdefghijk123124"
}
- 携带参数请求方法
建议使用该方法,将query语句和具体的业务动态变量分开,便于业务方动态调整参数
{
"Query":"query GetStudents($req:GetStudentRequest){getStudents(request:$req){students{id,name}}}", //query语句
"Variables":"{\"req\":{\"name\":\"liuzhuan\"}}", //入参
"Psm":"test",
"Token":"token"
}
- 请求实现配置好的DSL的请求方法:
部分情况下,query语句可以在数据中心进行配置,使用方只需要传入对应的queryId以及动态参数即可
{
"QueryId":"queryId_1",
"Variables":"{\"req\":{\"name\":\"liuzhuan\"}}",
"Psm":"test",
"Token":"token"
}
- 返回结果说明:
正常情况下,数据会以json字符串的形式返回在Data中
{
"Data": "{\"getStudents\":{\"students\":[{\"id\":\"102\",\"name\":\"S2\"},{\"id\":\"103\",\"name\":\"S3\"},{\"id\":\"104\",\"name\":\"S4\"},{\"id\":\"105\",\"name\":\"S5\"}]}}",
"Errors": null,
"BaseResp": {
"StatusMessage": "success",
"StatusCode": 200,
"statusCode": 200,
"statusMessage": "success",
"setStatusMessage": true,
"setStatusCode": true
}
}
- 异常描述:
{
"Data": null,
"Errors": [
"cannot find query"
],
"Code": 0,
"BaseResp": {
"StatusMessage": "system fail",
"StatusCode": 502,
"statusCode": 502,
"statusMessage": "system fail",
"setStatusMessage": true,
"setStatusCode": true
}
}
后续
配置化
新接入数据源通过配置实现,提供对外的配置页面供业务接入方灵活接入
业务场景扩展
新功能开发
系统稳定性和响应指标
datafetcher的使用