浅尝GraphQL

600 阅读6分钟

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

前言

本文并非对GraphQL的详细介绍,也不会过多描述GraphQL与restful架构的优缺点对比。尽量以最简单直白的表达,说明GraphQL是什么及在java中的基本使用。

官方文档:graphql.org/

GraphQL是什么

由来

在 2012 年由Facebook创立的一门开源的API查询语言,用于服务器端执行按已定义类型系统的查询,无关特定的数据库或存储引擎,由自己的代码支持。

目的

可以作为Restful架构的替代方案。

对比

关于“由来”和“目的”上的几行简单描述,看完之后依然让人迷惑GraphQL是什么,为什么可以替代Restful。下面作一个简单直白的对比说明:

作为web开发,restful风格/规范这个词,应该是比较熟悉的。REST描述了客户端与服务端的一种交互形式,restful规范约定了我们平常暴露资源的API的一种格式,比如:

  • url描述唯一的资源
  • url各部分应该是描述资源的名词,原则上尽量不使用动词
  • url表示的资源变更的结果而不应该是个过程
  • Http Method表示不同的动作
  • 协议和数据格式一般采用的是http+json
  • ...

当然了,我们日常作java开发的时候,大多是使用的spring boot(spring mvc),暴露api的Url也不一定完全遵守restful规范(除非公司强制要求),Url里就可能使用动词,HTTP的method只用GET或POST就表达了所有含义等等。

restful不是本文重点,主要是想说其中的一些问题点,比如我开发了一个查询项目信息的接口(item/all),可以检索所有的项目信息,前端会用一个table来展示。

如果在另一个页面,有个下拉框展示项目列表,而我作为后端开发,为了省事,我可能会复用item/all这个接口给前端,不会再提供一个新的API。但是这个接口的每条数据都包含了项目的所有字段(比如,id,项目编码,项目名称,创建时间,描述等等)。但对于前端的这个下拉框来说,只需要id和项目名称可能就足够了,这也就是过度获取了。

如果前端获取项目信息时还需要获取该项目相关的人员信息,可能还能需要向后端再发一个请求来获取人员信息,就会出现这种频繁多次请求(当然,实际情况我们开发的时候,有可能会提供这样一个接口,返回一个复合数据结构包括项目和相关的人员信息等)。

还有一个问题,restful中一个url标识一个资源,新增项目是一个url,查询是一个url,修改也是一个url,新增用户信息又是一个url,等等。

GraphQL就可以解决上面描述的这些问题。

GraphQL并不限定必须在WEB中使用,但如果在web中使用,一般暴露一个API就够了,前端可以按需获取哪些字段的数据。可以这样认为,使用GraphQL的后端就像是一个数据源,而前端只需要指定哪些字段(像我们写sql去查询数据库一样),就可以获取哪些数据,由前端来驱动数据。

GraphQL Java

GraphQL提供多种语言的服务器端库。因为本文是以java来说明,所以我会以一下最基本的示例来描述一下GraphQL的使用。

GraphQL在java中的实现的github地址:github.com/graphql-jav…

下面我会以一下最基本的示例简单说明(非web应用):

代码示例

示例代码中,我不会详细说明,每一步为什么这么写,因为GraphQL的内容虽然不多,但也不是一两句全部说得清的,如果感兴趣,建议查看官方文档,系统的了解下。

示例代码仅作示例参考,并非固定的写法范式,所以实际项目中不一定是按照这样的实现流程编码的。

下面示例的业务场景是:获取项目信息

引入依赖

        <dependency>
            <groupId>com.graphql-java-kickstart</groupId>
            <artifactId>graphql-java-tools</artifactId>
            <version>11.0.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>

关于lombok,并非graphql-java需要,而是示例代码中使用了,如果实际开发的时候,不使用lombok并不需要引入这个依赖。

定义GraphQL的schema

在类路径下创建一个item.graphqls

schema {
    # 查询
    query: Query
    # 更新
#    mutation: Mutation
}

# 定义一个查询类型
type Query {
    queryItemList: ItemList  # 定义查询项目列表,为了示例,这里就不传入参数了
    getMale: Personnel
    item: Item
}

# 定义人员信息类型
type Personnel {
    id: ID!   #人员id,指定是一个ID类型,且不为空 ID表示一个唯一字段,可以是string或integer
    name: String! #人员姓名
    age: Int # 人员年龄,可以为空
    isMale: Boolean! #是否为男姓
}

# 定义项目字段
type Item {
    id: ID!
    code: String!
    name: String!
    people: [Personnel!]! #人员信息,是一个数组/列表
}

type ItemList {
    itemList: [Item!]!  #获取项目列表
    total: Int!      # 获取项目总数
}

创建相关bean实例

@Data
public class Personnel {

    private long id;

    private String name;

    private int age;

    private boolean isMale;

}


@Data
public class Item {

    private long id;

    private String name;

    private String code;

    private List<Personnel> people;
}


@Data
public class ItemList {

    private List<Item> itemList;

    private int total;
}

定义一个Resolver

public class QueryResolver implements GraphQLQueryResolver {

    // 对应item.graphqls里的queryItemList
    public ItemList queryItemList() {
        ItemList itemList = new ItemList();
        itemList.setItemList(new ArrayList<Item>(){{
            add(item());
        }});
        itemList.setTotal(1);

        return itemList;
    }


    public Item item() {
        Item item = new Item();
        item.setId(1);
        item.setCode("graphql");
        item.setName("示例");
        item.setPeople(people());
        return item;
    }

    public List<Personnel> people() {
        return new ArrayList<Personnel>(){{
            add(getFemale());
            add(getMale());
        }};
    }

    // 返回一个female Personnel
    public Personnel getFemale() {
        Personnel personnel = new Personnel();
        personnel.setId(1);
        personnel.setName("xiao hong");
        personnel.setAge(18);
        personnel.setMale(false);
        return personnel;
    }

    // 返回一个male Personnel
    public Personnel getMale() {
        Personnel personnel = new Personnel();
        personnel.setId(2);
        personnel.setName("xiao ming");
        personnel.setAge(20);
        personnel.setMale(true);
        return personnel;
    }
}

创建并执行

public class GraphQLExample {

    public static void main(String[] args) {
        GraphQLSchema graphQLSchema = SchemaParser.newParser()
            .file("item.graphqls")
            .resolvers(new QueryResolver())
//            .file("book.graphqls")
//            .resolvers(new BookResolver())  //其它定义继续增加
            .build().makeExecutableSchema();

        GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();

        // 查询项目列表,获取项目的编码和姓名字段,各字段不一定非要逗号分隔,空格,换行都行
        String query = "{queryItemList{itemList{code,name}}}";
        System.out.println("查询项目列表,获取项目的编码和姓名字段,查询语句:" + query);
        ExecutionResult executionResult = graphQL.execute(query);
        System.out.println("查询结果:" + executionResult.getData().toString());
        System.out.println();

        query = "{queryItemList{itemList{id, people{name}}}}";
        System.out.println("查询项目列表,获取项目的id和相关人员的姓名字段,查询语句:" + query);
        executionResult = graphQL.execute(query);
        System.out.println("查询结果:" + executionResult.getData().toString());
        System.out.println();

        query = "{getMale{name,age}}";
        System.out.println("查询男用户的名字和年龄,查询语句:" + query);
        executionResult = graphQL.execute(query);
        System.out.println("查询结果:" + executionResult.getData().toString());
        System.out.println();
    }
}

执行结果如下:

根据截图可以看到,结果是按需查询。这只是一个很基本的示例,也并非web应用,但在实际开发的时候可以只提供一个web接口,由前端请求该接口传入查询语句。

上面的示例都比较简单,也不包含参数,关于带参数的定义,我贴一个我实际开发中的写法,仅作参考,就不再提供相关demo了:

哪些项目在使用它

其实我知道的只有skywalking,因为最近在skywalking上开发一些功能的时候,它的后端是采用GraphQL,便顺便把相关的官方文档看了遍,研究了一下,如果想参考skywalking的相关用法,这里是它的github地址:GitHub - apache/skywalking: APM, Application Performance Monitoring System

末语

如果不了解GraphQL,只是看上面的示例代码,应该还是比较头晕的。如果感兴趣想要系统地了解应用在自己项目中,这里有一些官方文档,示例还比较清楚: