Author: A word mahu reprint logo [2017-11-03]

Update log

The date of Update the content note
2017-11-03 New articles ).

I met GraphQL

GraphQL is a powerful DSQL and an abstract framework for providing data query services, which is open source by Facebook. In server-side API development, the data returned by defining an interface is relatively fixed in most cases. If you want to obtain more information, or only need a certain information of a certain interface, Interfaces based on restful apis are less flexible. For these needs, the server must either define a new interface to return the appropriate data, or the client must use a large interface to retrieve a small part of the information. GraphQL was created to solve these problems. GraphQL is not a framework implemented by a specific language. It is a project consisting of a series of protocol documents. GraphQL is language independent, and there are many languages implemented so far. For a detailed GraphQL definition, see GraphQL. This article and this GraphQL series will focus only on the Implementation of the Java version of GraphQL. For the Java version of GraphQL, see GraphQL-Java. Here’s the official GraphQL description, which is succinct, but intuitive:

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.

The following image shows the working model of GraphQL:





As can be seen from the picture, GraphQL is located between the Client and DataSource. This layer can be understood as the API layer of the server side. The so-called API layer is to aggregate multiple data sources, process some business logic, and then provide some interfaces for the Client to call. The GraphQL works at this layer, which is equivalent to a layer of abstraction for the DataSource. It can accept the request of the Client, obtain data from the DataSource according to the execution engine of GraphQL, and then process the data and return the JSON result to the Client. This is not different from Restful mode, but the power of GraphQL is that GraphQL is similar to MySql, and the requests sent by clients are similar to Sql statements. These Sql statements are parsed and executed by GraphQL and return specific data, so GraphQL is very dynamic. The Client can use different Sql statements to request the server based on different requirements, and GraphQL parses these Sql statements and returns accurate results. This perfectly solves the problem mentioned at the beginning of the article. Using GraphQL to develop the server API layer will undoubtedly relieve a lot of pressure on the server developer, and it is also very friendly to the Client, because the Client does not need to request Restful interfaces only to obtain relatively fixed data. The Client can use different query statements to request GraphQL according to its own requirements. Using GraphQL can reduce a lot of redundant data transmission and reduce a lot of interface development work at the API layer of the server. The API layer only needs to develop the GraphQL server and then tell the Client the organization structure of these data. The Client can then assemble the appropriate query to request the data. Use GraphQL to further separate the front and back ends (Restful makes the front and back ends separate). Back-end development and front end development can be carried out separately. Most of the time using GraphQL, the server is enriching the available data, or optimizing the aggregate DataSource to improve the response speed. There are many other advantages to using GraphQL. You can explore GraphQL and use it to develop a server-side API to experience it. The rest of this article will implement a simple application based on GraphQL-Java and Spring-Boot to illustrate how to use GraphQL and the benefits of using it.

As a bonus, the GraphQL query statement mentioned above (Sql is used instead, but not Sql) is a kind of structured data similar to JSON, which can be easily understood. This is one of the benefits of GraphQL. The query statement is very engineer-friendly. This will be analyzed below.

GraphQL of actual combat

Note that this GraphQL series of articles is based on the Java language and graphQL-Java. The GraphQL example in this paper is developed by spring-boot and uses IDEA 17 as the IDE. It is strongly recommended that Javaer be developed by IDEA, which can significantly improve the development efficiency.

To get started, here is the code structure for the examples used in this article:





The package management classes can be understood by the package name. For example, a Service manages a set of services, while a View package contains render views that need to be returned to the Client. The process of creating a Spring-Boot project is beyond the scope of this article (the only thing that is explained is that Web module support is required), so here are some key steps to guide you through implementing a GraphQL demo.

Create a Model class

This step is very simple, you need to create a Model class in the Model under the package, such as the example to implement a scene is that there are some authors, each author may wrote many articles, each article is only a writer, and each article may not have the comment below, or have a comment, any number of comments, here are a few key types of information:

public class AuthorModel {

    private int authorId; // the author id
    private int authorAge; // the age
    private int authorLevel; // the level
    private String authorAddr; // the address

    private List<Integer> friends; // the friends of the author
    
}

public class ContentModel {

    private int contentId; // the content id
    private int authorId; // the author id
    private int commentSize; // the comment size of this content

    private String text; // the text
    private List<Integer> commentIds; // the Comment id list    
}

public class CommentModel {

    private int commentId; // the comment id
    private int authorId; // the author of this comment
    private int ofContentId; // the content id

    private String content; // the content of this comment
}

Copy the code

To experiment with GraphQL’s complex queries, here are two enhanced classes: the AuthorModel class and the ContentModel class.


public class CompletableAuthorModel extends AuthorModel{

    private List<AuthorModel> friendsCompletableInfo;
    private List<CompletableContentModel> contentModelList; 
}

public class CompletableContentModel extends ContentModel{

    private List<CommentModel> commentModelList; // the comment info list of this content
}

Copy the code

All of the code presented in this article is available on Github, so this article is not a complete presentation of all the code.

The Mock data

To test GraphQL, you need to have some data, this paper in order to quickly test GraphQL, so the Mock data is simpler, not interact with the database, actually in the real server API layer development, most of the time is not need to interact with the database, more is to use RPC to obtain the data we need from some service, An RPC service is actually a data source. The work of the API layer is to aggregate these data sources and then process some business logic to provide interfaces for clients to access. The Mock code can be found in the DataMock class.

Of course, after the data source is available, some business logic processing needs to be carried out. In this paper, some services are used to simulate such processing, mainly connecting Author, Content and Comment, which is quite understandable.

Define GraphQLOutputType

Now that you’ve defined your Model class and you’ve got your data and business logic handlers, let’s define some graphQLOutputTypes. These graphQLOutputTypes are the output that the server can provide. The AuthorModel GraphQLOutputType is shown first, and then its enhanced output, CompletableAuthor, is shown for reference:

/* basic outPutType */ private GraphQLOutputType author; /* richness & completable outPutType */ private GraphQLOutputType completableAuthor; /* The Author */ author = newObject().name("AuthorModel") .field(GraphQLFieldDefinition.newFieldDefinition().name("authorId").type(Scalars.GraphQLInt)) .field(GraphQLFieldDefinition.newFieldDefinition().name("authorAge").type(Scalars.GraphQLInt)) .field(GraphQLFieldDefinition.newFieldDefinition().name("authorLevel").type(Scalars.GraphQLInt)) .field(GraphQLFieldDefinition.newFieldDefinition().name("authorAddr").type(Scalars.GraphQLString)) .field(GraphQLFieldDefinition.newFieldDefinition().name("friends").type(GraphQLList.list(Scalars.GraphQLInt))) .build();  /* the completable author information */ completableAuthor = newObject().name("CompletableAuthor") .field(GraphQLFieldDefinition.newFieldDefinition().name("authorId").type(Scalars.GraphQLInt)) .field(GraphQLFieldDefinition.newFieldDefinition().name("authorAge").type(Scalars.GraphQLInt)) .field(GraphQLFieldDefinition.newFieldDefinition().name("authorLevel").type(Scalars.GraphQLInt)) .field(GraphQLFieldDefinition.newFieldDefinition().name("authorAddr").type(Scalars.GraphQLString)) .field(GraphQLFieldDefinition.newFieldDefinition().name("friends").type(GraphQLList.list(Scalars.GraphQLInt))) .field(GraphQLFieldDefinition.newFieldDefinition().name("friendsCompletableInfo").type(GraphQLList.list(author))) .field(GraphQLFieldDefinition.newFieldDefinition().name("contentModelList").type(GraphQLList.list(completableContent))) .build();Copy the code

The complete GraphQLOutputType definition can be found in the project (end of article). There are a lot of “. Type “operations, and GraphQL provides a lot of types that you can interface with type systems in a variety of languages. Scalars.graphqlint can interface with Integer in Java, Scalars.GraphQLString supports GraphList, Objects, Unions, and Enums. For a complete Type System, see GraphQL Type System, which uses only Scalars and GraphList.

To define the Schema

Once you’ve defined some GraphQLOutputTypes, you’re ready to define the GraphQL Schema. Here’s the Schema definition for the example used in this article:


        /* set up the schema */
        schema = GraphQLSchema.newSchema()
                .query(newObject()
                        .name("graphqlQuery")
                        .field(createAuthorField())
                        .field(createContentField())
                        .field(createCommentField())
                        .field(createCompletableContentField())
                        .field(createCompletableAuthorField()))
                .build();
                
    /**
     * query single author
     * @return the single author's information
     */
    private GraphQLFieldDefinition createAuthorField() {
        return GraphQLFieldDefinition.newFieldDefinition()
                .name("author")
                .argument(newArgument().name("authorId").type(Scalars.GraphQLInt).build())
                .type(author)
                .dataFetcher((DataFetchingEnvironment environment) -> {

                    //get the author id here
                    int authorId = environment.getArgument("authorId");

                    return this.authorService.getAuthorByAuthorId(authorId);
                }).build();

    }                

    /**
     * completable author information
     * @return the author
     */
    private GraphQLFieldDefinition createCompletableAuthorField() {
        return GraphQLFieldDefinition.newFieldDefinition()
                .name("completableAuthor")
                .argument(newArgument().name("authorId").type(Scalars.GraphQLInt).build())
                .type(completableAuthor)
                .dataFetcher((DataFetchingEnvironment environment) -> {
                    int authorId = environment.getArgument("authorId");

                    //get the completable info of author by authorId
                    //System.out.println("request for createCompletableAuthorField:" + authorId);

                    return authorService.getCompletableAuthorByAuthorId(authorId);
                }).build();
    }

Copy the code

The definitions of author and completableAuthor are shown above. The actual aggregate data source operations of the server need to be written in these GraphqlFieldDefinitions. Each GraphQLFieldDefinition is like a collection of server-side apis, and it can have some input parameters, equivalent to restful parameters, that you need to aggregate the DataSource to return the appropriate data.

Provide query interface

The following code shows how to use GraphQl to accept a query request from a Client:

package io.hujian.graphql; import graphql.GraphQL; import java.util.Collections; import java.util.Map; /** * Created by hujian06 on 2017/11/2. * * the facade of the graphQl */ public class GraphqlFacade { private static final GraphqlProvider PROVIDER = new GraphqlProvider(); private static final GraphQL GRAPH_QL = GraphQL.newGraphQL(PROVIDER.getSchema()).build(); /** * query by the Graphql * @param ghql the query * @return the result */ public static Map<String, Object> query(String ghql) { if (ghql == null || ghql.isEmpty()) { return Collections.emptyMap(); } return GRAPH_QL.execute(ghql).getData(); }}Copy the code

Provide the interface

To test GraphQL, you need to provide a query interface, and the following code shows how to use Spring-Boot to provide the interface:


package io.hujian.controller;

import com.alibaba.fastjson.JSON;
import io.hujian.graphql.GraphqlFacade;
import io.hujian.view.CheckView;
import io.hujian.view.MockerDataView;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created by hujian06 on 2017/11/2.
 *
 * the graphql controller
 */
@Controller
@RequestMapping(value = "dsql/api/")
public class GraphqlController {

    /**
     * query the hsql by the graphql
     * @param ghql the query string like:->
     *             "{
     *               author(authorId:2)
     *                {
     *                authorId,
     *                authorAge,
     *                authorAddr,
     *                friends
     *                }
     *               }"
     *             the response like:->
     *              "{
     *                "author": {
     *                           "authorId": 2,
     *                           "authorAge": 32,
     *                           "authorAddr": "Ty-0021",
     *                           "friends": [1]
     *                          }
     *               }"
     *
     * @param request r
     * @param response r
     * @throws IOException e
     */
    @RequestMapping(value = "query/{ghql}")
    public void graphqlQuery(@PathVariable("ghql") String ghql, HttpServletRequest request, HttpServletResponse response)
            throws IOException {

        String result = JSON.toJSONString(GraphqlFacade.query(ghql));

        System.out.println("request query:" + ghql + " \nresult:" + result);

        //query the result.
        response.getOutputStream().write(result.getBytes());
    }

}

Copy the code

For example, if you want to query the AuthorAge and AuthorLevel of Author whose id is 1, you want to query the AuthorAge and AuthorLevel of Author.


{
  author(authorId:1) {
     authorAge,
     authorLevel
   }
}

Copy the code

The query results are as follows:


{
    "author": {
        "authorAge": 24,
        "authorLevel": 10
    }
}

Copy the code

The server does not need to change anything. The Client only needs to change the Query. The new Query is:


{
  author(authorId:1) {
     authorAge,
     authorLevel,
     authorAddr
   }
}

Copy the code

The query returns the following:


{
    "author": {
        "authorAge": 24,
        "authorLevel": 10,
        "authorAddr": "Fib-301"
    }
}

Copy the code

To illustrate the power of GraphQL, here’s a rich and complex query and its output, starting with the response to the request:

{
    "completableAuthor": {
        "authorId": 1,
        "authorLevel": 10,
        "authorAge": 24,
        "authorAddr": "Fib-301",
        "friends": [
            2,
            3
        ],
        "contentModelList": [
            {
                "contentId": 1,
                "authorId": 1,
                "text": "This is a test content!",
                "commentModelList": [
                    {
                        "commentId": 2,
                        "authorId": 1,
                        "content": "i thing so."
                    }
                ]
            }
        ],
        "friendsCompletableInfo": [
            {
                "authorId": 2,
                "authorAge": 32,
                "authorLevel": 4,
                "friends": [
                    1
                ]
            },
            {
                "authorId": 3,
                "authorAge": 14,
                "authorLevel": 2,
                "friends": [
                    2
                ]
            }
        ]
    }
}

Copy the code

The corresponding request is:


{
     completableAuthor(authorId:1) {
     authorId,
     authorLevel,
     authorAge,
     authorAddr,
     friends,
     contentModelList {
     contentId,
     authorId,
     text,
     commentModelList {
       commentId,
       authorId,
       content
     }
   },
     friendsCompletableInfo {
       authorId,
       authorAge,
       authorLevel,
       friends
     }
   }
}

Copy the code

conclusion

GraphQL supports not only Query but also write operations. However, considering that most of the server API is used to aggregate data sources rather than write data, this article does not cover relevant content. However, all operations supported by GraphQL will be covered in the subsequent GraphQL series, and the specific implementation details of these operations will be analyzed. Finally, share the project address of the project involved in this article, which can be successfully executed if nothing goes wrong. Note the Settings of application.properties, such as log output level, server startup port, etc. This project has a startup port of 8600, so if you want to experiment, You need to enter the following address in the browser after starting the project:

http://127.0.0.1:8080/dsql/api/query/ {your_query}

Project address: GraphQL-starter