你不知道的 Graphql

一起入门 Graphql

[toc]

介绍

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。如何理解呢?GraphQL 作为通用的 REST 架构的替代方案而被开发出来 ,通俗的讲,在架构中他属于和REST处于同一个层次的东西。对于REST,GraphQL的优势在于:

  • REST接口的数据由后端定义,如果返回了前端不期望的数据结构就需要和后端沟通修改或者自己适配;
  • GraphQL向你的 API 发出一个 GraphQL 请求,客户端就能准确获得你想要的数据,不多不少;
  • GraphQL 可以通过一次请求就获取你应用所需的所有数据,而 REST API 则需要请求多个URL;
  • GraphQL 查询的结构和结果非常相似,因此即便不知道服务器的情况,你也能预测查询会返回什么结果。

Facebook 开源了 GraphQL 标准和其 JavaScript 版本的实现。后来主要编程语言也实现了标准。

Hello World

const Koa = require('koa');
const { ApolloServer, gql } = require('apollo-server-koa');

// 定义一个查询
const typeDefs = gql`
  type Query {
    hello: String
  }
`;

// 为上面定义的查询提供数据
const resolvers = {
    Query: {
        hello: () => 'Hello world!',
    },
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = new Koa();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
    console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`),
);

image-20200102135121639

标量类型(Scalar Types)

上面例子中,我们给查询定义的返回参数为String,GraphQL支持的标量类型还有StringIntFloatBooleanID

如果类型是一个列表,我们可以这个[Int],表示一个整数的列表,如果是自定义类型也可以这样使用。

同时作为参数的时候,我们可以通过String!来表示该参数为必传非空的。

指定查询字段

const Koa = require('koa');
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync');

const { ApolloServer, gql } = require('apollo-server-koa');

const adapter = new FileSync('./db/data.json');
const db = low(adapter);

// 定义两个查询方法
const typeDefs = gql`
  type Query {
    hero: Hero
    heroList: [Hero]
  }
  type Hero {
      name: String,
      age: Int
  }
`;

// 为各自的方法提供数据
const resolvers = {
    Query: {
        hero: async (parent, args, context, info) => {
            return db.get('hero').value()[0];
        },
        heroList: async (parent, args, context, info) => {
            return db.get('hero').value();
        },
    },
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = new Koa();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
    console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`),
);
  • 简单查询

    如果查询返回的一个对象的时候,你需要为查询执行需要返回的字段,只会返回指定的字段

    image-20200102135801695

  • 只需要 hero 的 name 属性
    image-20200102135916318
  • 一次请求,可以同时进行多次查询,比如同时查询 hero 和 heroList 列表

    image-20200102140307687

参数(Arguments)

类似 REST API ,Graphql也支持给每一个查询添加查询参数,我们只需要做如下修改:

...
const typeDefs = gql`
  type Query {
    hero(id :Int): Hero
  }
`;

// Provide resolver functions for your schema fields
const resolvers = {
    Query: {
        hero: async (parent, args, context, info) => {
            const { id } = args;
            return db.get('hero').find({ id }).value();
        }
    },
};
...

下面演示了查询结果:我们通过不同的查询ID,查询出来不同的 hreo:

image-20200102141454328

细心的你会发现一个细节,返回的字段名为first,second。这里我使用了别名的方式,因为两个hero字段相同,所以存在冲突,我们使用别名来进行区分。

别名(Aliases)

通过上图,又接触了一个新的概念 别名。主要作用是使得返回字段使用另外一个名字,避免对相同方法不同参数下返回数据冲突的问题(如上)。只要在字段前面使用 别名:就可以把返回数据的key自动替换为该名称。

片段(Fragments)

如果你追求严谨的代码规范和工程能力,你肯会对 firstsecond中两处冗余同样的 nameage感到难受。一旦以后需需要增减或者修改字段的时候,需要多出地方,容易造成bug。Graphql 为已经考虑到这一点,我们可以使用片段来优化:

image-20200102142023923

通过片段我们很好的提取了可服用单元,然后再需要他们的地方引入。

变量(Variables)

我我们现在查询,变量是写在查询字符串中,而实际情况,我们的变量是多态的,每次都去拼接查询字符串不是一明智的方式。Graphql提供了很好的方式来帮我我们来解决这个问题。我们可以定义一个 变量

  • 使用 $variableName 声明一个变量
  • 使用 $variableName:value 通过变量字典来传输

image-20200102144501429

如上图我们通过变量 的方式,解决了变量多态的问题。

指令(Directives)

虽然变量帮我们解决了多态的问题,但还是不够完美,比如有一个用户信息列表,我们想针对不同权限的人显示不同的信息。用户收入只允许人力部门查看,用户的学历只允许直属领导查看等,因此我们需要针对不同的权限来过滤。

Graphql核心规范包含两个指令

  • @include(if: Boolean) 仅在参数为 true 时,包含此字段。
  • @skip(if: Boolean) 如果参数为 true,跳过此字段(不查询此字段)。

image-20200102151221865

image-20200102151336517

通过以上查询,我们对isAdmin 变量赋值truefalse进行了两次查询,从结果可以看到我们控制到了查询字段的展示。

变更(Mutations)

如同REST一样,我们不仅需要查询,还需要修改数据:

操作流程如下

  1. typeDefs 中使用在 Mutation块中定义一个 修改的方法
  2. resolvers 中使用在Mutation块中去实现
...
const typeDefs = gql`
    type Mutation {
      createHero(name: String, age: Int): [Hero]
    }
    type Hero {
      id: Int
      name: String,
      age: Int
    }
  `;
  
const resolvers = {
    Mutation: {
      createHero: async (parent, args, context, info) => {
        const model = db.get('hero');
        const len = model.value().length;
        model
          .push({ id: len + 1, ...args })
          .write();
        return db.get('hero').value();
      }
    }
  };
  ...

最后我们在 mutation中进行操作,如下图:

image-20200102154654184

从结果中,我们看到成功的创建了英雄长孙长雪 。注意我们创建英雄后,我们返回了heroList。和查询一样,我们也可以同时进行多个变更操作,不一样的是:

查询是并行执行,而变更操作是线性执行的一个接着一个

如下图:

image-20200102191208313

输入类型(Input Types

在前面的例子中,我们传入的类型都为标量类型,如果我们想传入一个复杂的结构数据可以使用 input关键字。其用法和type一样,服务端定义完后,客户端查询的时候也可以使用该类型了。

const typeDefs = gql`
    type Query {
      hero(id :Int): Hero
    }

    ## 这里对 AttrInput 类型进行使用
    type Mutation {
      createHero(name: String, age: Int, attr: AttrInput): [Hero]
    }

    ## 这边定义一个 input 参数 ##
    input AttrInput {
      shoes: String
      clothes: String
      hat: String
    }

    type Hero {
      id: Int
      name: String
      age: Int
      attr: Attr
    }
    type Attr {
      shoes: String
      clothes: String
      hat: String
    }
  `;

然后我们进行一个 变更操作:

image-20200103101832985

根据定义我们的 attr 参数为:

"attr": {
   "shoes":"Boot",
   "hat":"Peaked",
   "clothes":"shirt"
  }

对象类型(Object Types)

有时候我们不仅仅希望API返回的只是一个数字或者字符串,我们希望可以返回一个对象,而且是带有行为的对象,GraphQL可以很完美的契合这个需求。我们只需要设置返回值为对象

const Koa = require('koa');
const { ApolloServer, gql } = require('apollo-server-koa');
// 定义一个查询,并且返回值为一个对象
const typeDefs = gql`
  type Query {
    getDie(numSides: Int): RandomDie
  }
  type RandomDie {
    numSides: Int!
    rollOnce: Int!
    roll(numRolls: Int!): [Int]
  }
`;
...
// 实现一个 对象
class RandomDie {
    constructor(numSides) {
        this.numSides = numSides;
    }

    rollOnce() {
        return 1 + Math.floor(Math.random() * this.numSides);
    }

    roll({ numRolls }) {
        var output = [];
        for (var i = 0; i < numRolls; i++) {
            output.push(this.rollOnce());
        }
        return output;
    }
}
// 查询实现中,返回该对象的实例
const resolvers = {
    Query: {
        getDie: async (parent, args, context, info) => {
            return new RandomDie(args.numSides || 6);
        }
    }
}

const server = new ApolloServer({ typeDefs, resolvers });
const app = new Koa();

server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
    console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`),
);

然后我们就可以调用了,而且可以调用对象的行为(对象的方法):

image-20200103153042500

从上图中我们可以看出,我们调用 查询getDie,获得对象后,可以直接继续执行对象的行为,这样为后端服务的抽象和复用提供了非常大的帮助。

接口(Interfaces)

GraphQL 也支持接口,它包含某些字段:

  • 对象类型必须包含这些字段,才能算实现了这个接口;
  • 因为 GraphQL 是强类型的,所以当返回类型为接口的时候,需要 实现__resolveType来告诉GraphQL返回的具体类型。
const Koa = require('koa');
const { ApolloServer, gql } = require('apollo-server-koa');

// 定义一个查询,并且返回值为一个对象
const typeDefs = gql`
    interface Book {
        title: String
        author: String
    }
    type TextBook implements Book {
        title: String
        author: String
        classes: [String]
    }
  
    type Ebook implements Book {
        title: String
        author: String
        format: String
    }
  
    type Query {
        schoolBooks(type: String): [Book]
    }
`;

// 查询实现中,返回该对象的实例
const resolvers = {
    Book: {
        // 当你定义一个查询的返回类型是 union 或者是 interface 的时候,
        // 必须定义这个解析器告诉 graphql 返回的具体类型
        __resolveType(book, context, info) {
            if (book.classes) {
                return 'TextBook';
            }

            if (book.format) {
                return 'Ebook';
            }
            return null;
        },
    },
    Query: {
        schoolBooks: (parent, args) => {
            const { type } = args;
            if (type === 'TextBook') {
                return [{
                    title: '红楼梦', author: '曹雪芹', classes: ['名著', '文学']
                }]
            }

            if (type === 'Ebook') {
                return [{
                    title: '红楼梦', author: '曹雪芹', format: 'pdf'
                }]
            }
        }
    }
}

const server = new ApolloServer({ typeDefs, resolvers });
const app = new Koa();

server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
    console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`),
);

image-20200106151006649

内联片段(Inline Fragments

在上图的查询中,schoolBooks字段返回Book类型,取决于参数返回具体的TextBookEbook。此时如果你需要请求具体类型上的字段就需要使用内联片段。标注为:... on TextBook,仅在schoolBooks 返回TextBook类型的时候才会被执行,同理适用于Ebook类型。

元字段(Meta fields)

继续上面的案例,某些情况下,你并不知道你将从 GraphQL 服务获得什么类型。但是你又需要根据类型来决定如何处理数据,这时候就可以使用__typename

  • 允许放在查询的任何位置请求__typename

image-20200106162041937

GraphQL 服务提供了不少元字段,剩下的部分用于描述 内省 系统。

联合类型(Union Types)

联合类型和接口十分相似,但是它并不指定类型之间的任何共同字段。他只的是一个类型可以支持多种不同的类型返回:union Result = Book | Author。任何一个返回Result的地方都可能得到BookAuthor`。

  • 联合类型的成员需要是具体对象类型;你不能使用接口或者其他联合类型来创造一个联合类型。
  • 查询同样需要使用条件片段
const Koa = require('koa');
const { ApolloServer, gql } = require('apollo-server-koa');

// 定义一个查询,并且返回值为一个对象
const typeDefs = gql`
  union Result = Book | Author

  type Book {
    title: String
  }

  type Author {
    name: String
  }

  type Query {
    search(type:String): [Result]
  }
`;

// 查询实现中,返回该对象的实例
const resolvers = {
    Result: {
        // 当你定义一个查询的返回类型是 union 或者是 interface 的时候,
        // 必须定义这个解析器告诉 graphql 返回的具体类型
        __resolveType(obj, context, info) {
            if (obj.title) {
                return 'Book';
            }

            if (obj.name) {
                return 'Author';
            }
            return null;
        },
    },
    Query: {
        search: (parent, args) => {
            console.log(args)
            const { type } = args;
            if (type === 'Book') {
                return [{
                    title: '红楼梦'
                }]
            }

            if (type === 'Author') {
                return [{
                    name: '曹雪芹'
                }]
            }
        }
    }
}

const server = new ApolloServer({ typeDefs, resolvers });
const app = new Koa();

server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
    console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`),
);

image-20200106164357290

Mock数据

  • 以上案例使用如下的数据进行演示

    • db 中的数据
  • lowdb 提供一个本地的数据查询引擎服务
{
    "hreo": [
        {
            "id": 1,
            "name": "007",
            "age": 18,
            "family": {
                "mother": "Madhh",
                "father": "Father"
            }
        },
        {
            "id": 2,
            "name": "R2-D2",
            "age": 20,
            "family": {
                "mother": "Madhh",
                "father": "Father"
            }
        }
    ]
}

参考

GraphQL 官网

Unions and interfaces

Image placeholder
kjcx
未设置
  92人点赞

没有讨论,发表一下自己的看法吧

推荐文章
你不知道的 PHP 缓存技巧

前言 相信有一定开发经验的PHP程序员都有缓存的使用经验,包括但不限于redis缓存,模板缓存,路由缓存,配置缓存,数据库缓存,还有opcache(真的很有用),但是今天我不准备炒冷饭了,我要讲点你不

R语言有多强大?十个你不知道的功能

大数据文摘出品编译:邬亮有些业界从业人士对R语言的价值并不认可,他们认为R语言只针对统计分析。R语言的确提供了很全面的统计分析的软件包,比如CRAN,Bioconductor,Neuroconduct

你不知道的 CSS : Next-generation web styling

最近看了ChromeDevSummit2019大会视频,了解到了很多之前不知道的CSS新特性,挺有意思的。下面我就介绍几个激动人心的特性。特性总览:StickyStickeyStackSticySli

科普 | 关于联盟区块链你不知道的事

在分布账本技术发明十年后的今天,我们仍然处于大规模应用落地的早期。目前多数流行的公有区块链仍然无法落地,因此近年来越来越多的技术团队将目光集中在那类有使用场景的方向--联盟区块链。第1部分:行业分类从

分享 10 个你可能不知道的 Laravel Eloquent 小技巧

Laravel是一个功能丰富的框架。但是,你无法从官方文档中找到所有可用的功能。以下是一些你可能不知道的功能。 1.获取原始属性 当修改一条Eloquent模型记录的时候你可以通过调用getOrig

14个你可能不知道的JavaScript调试技巧

以更快的速度和更高的效率来调试JavaScript熟悉工具可以让工具在工作中发挥出更大的作用。尽管江湖传言JavaScript很难调试,但如果你掌握了几个技巧,就能用很少的时间来解决错误和bug.文中

如何创建安全的 Node.js GraphQL API?

本文的目标是提供关于如何创建安全的Node.jsGraphQLAPI的快速指南。你可能会想到一些问题:使用GraphQLAPI的目的是什么?什么是GraphQLAPI?什么是GraphQL查询?Gra

你应该知道的RocketMQ

1.概述在很久之前写过一篇Kafka相关的文章,你需要知道的Kafka,那个时候在业务上更多的是使用的是Kafka,而现在换了公司之后,更多的使用的是Rocketmq,本篇文章会尽力全面的介绍Rock

Github一天标星1k+,程序员需要知道的那些定理和法则

大数据文摘出品编译:蒋宝尚、曹培信摩尔定律知道么?帕金森定律讲的又是啥?作为一名合格的开发人员,除了本身码力超强外,或多或少要知道几条“”潜规则”,例如依赖倒置原则、鲁棒性原则……关于开发人员必须要知

关于网络安全防御,每个中小企业应该知道的5件事

根据美国商业促进局(BBB)的数据显示,你可能会惊讶地发现,小型企业占北美所有企业的97%以上。根据这个统计数字,我认为好消息是,在所有网络攻击中,针对小企业的攻击不到一半。坏消息是,当中小企业受到攻

看完秒变5G专家!关于5G,你必须知道的事儿……

本文转自|鲜枣课堂   什么是5G    5G,就是5thGenerationMobileNetworks(第五代移动通信网络),也可以称为5thGenerationWirelessSystems(第

关于强化学习你应该知道的三件事

如果您有在关注科技相关的新闻,可能读过有关人工智能(AI)应用程序如何通过强化学习训练,在围棋、国际象棋等棋类游戏以及电子游戏中击败人类玩家的报道。作为一名工程师、科学家或研究人员,您可能会希望利用这

企业需要知道的 6个AI/ML关键点

由于人工智能(AI)和机器学习(ML)的迅速发展与应用落地,世界各地的公司正在积极利用AI和ML发展业务,甚至投资数百亿美元。这些技术能够对业务与产生深刻的影响,所以Gartner报告预测,未来将“进

我们不知道要花多少时间解决问题

分享链接:http://kyleprifogle.com/dear-startup/ 我在IT行业工作,发现一个奇怪的现象。职业生涯的早期,我从事Web开发,比如开发CRUD应用、构建API等。我注意

93.7% 的程序员!竟然都不知道 Redis 为什么默认16个数据库?

▍导读在实际项目中Redis常被应用于做缓存,分布式锁、消息队列等。但是在搭建配置好Redis服务器后很多朋友应该会发现和有这样的疑问,为什么Redis默认建立了16个数据库,如下图所示。椐调查发现:

微软张若非:搜索引擎和广告系统,那些你所不知的AI落地技术

这两年,被誉为“ 皇冠上的明珠”的自然语言处理领域发展愈发火热,成为了业内新宠,而 搜索和广告这两大老牌技术领域似乎已被大家遗忘。其实,这两大接地气的工程领域仍是各企业竞相抢夺的市场之一。近日,AI科

前端工程师不可不知的时间和地理坐标🔥

关于时间GMT和UTCGMT,即格林尼治标准时间,也就是世界时。GMT的正午是指当太阳横穿格林尼治子午线(本初子午线)时的时间。但由于地球自转不均匀不规则,导致GMT不精确,现在已经不再作为世界标准时

GoWeb教程_05.4. 使用 PostgreSQL 数据库

PostgreSQL是一个自由的对象-关系数据库服务器(数据库管理系统),它在灵活的BSD-风格许可证下发行。它提供了相对其他开放源代码数据库系统(比如MySQL和Firebird),和对专有系统比如

中兴陈河堆:PostgreSQL在5G网管中的应用

2019年是中国5G试商用元年,各大电信运营商正在进行紧张的5G实验局测试。5G网络基于SDN/NFV虚拟化技术进行构建,组网架构非常复杂,由无线接入网、承载网、核心网等部分组成,包含了大量的网元设备

从Oracle到PostgreSQL,某保险公司迁移实践

摘要:去O一直是金融保险行业永恒的话题,但去O的难度之大也只有真正经历过的人才知其中的艰辛。此次笔者结合实际去O工作,对去O过程中碰到的DBLINK、SEQUENCE最大值、空串、SQL语句中的别名等

为什么你应当选择 PostgreSQL 而不是 Oracle?

本文转自| PostgreSQL中文社区 作者简介 Jan Karremans,EnterpriseDB的高级销售工程师。 译者简介 KevinZhan,深圳联友科技SA,目前负责公司部分核心系统应用

最稳定可靠,PostgreSQL 12.1版本正式发布!

1.PG12.1Beta发布了!PostgreSQL全球开发组宣布,PostgreSQL12的第一个测试版(PG12.1Beta)现已开放下载。该版本中可预览的所有特性都将延续至PG12的最终版本中,

2019全球PostgreSQL生态报告出炉,PG为何从RDBMS中脱颖而出?

墨墨导读:本文是近期ScaleGrid发布的2019PG趋势报告,从不同的角度解读了PostgreSQL如何在众多优秀的RDBMS中脱颖而出,原文:https://scalegrid.io/blog/

PostgreSQL 12 正式发布:全面的性能提升

PostgreSQL12已经发布,该版本在各方面都得到了加强,包括显著地提升查询性能,特别是对大数据集,总的空间利用率方面。这个版本为应用程序开发人员提供了更多的功能,比如对SQL/JSON路径表达式

从 Oracle 到 PostgreSQL ,某保险公司迁移实践

作者 |章晨曦编辑 | 老鱼摘要:去O一直是金融保险行业永恒的话题,但去O的难度之大也只有真正经历过的人才知其中的艰辛。此次笔者结合实际去O工作,对去O过程中碰到的DBLINK、SEQUENCE最大值