数据网关系统设计

  • 项目背景
  • 问题发现
  • 解决方案
  • 技术设计
  • 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的使用