Here’s the problem

In the DT era, all kinds of businesses rely on powerful basic data platform for rapid growth. How to provide data support for all kinds of businesses efficiently is the concern of everyone.

The typical existing business scenario is that the business side makes requirements, then looks for development resources, and the back end provides the data, allowing the front end to implement various business views. There is a lot of repetitive labor in this approach. If the general content can be extracted and provided to each business side for repeated use, precious development time and development manpower will be saved.

The solution to the front end is to componentize the view so that each line of business can be either the consumer or producer of the component. So the question is, how to improve the development efficiency of the back-end interface when the front-end implements cross-business reuse through components?

Let’s assume that a business needs the following data content a:

{
  user(id: 3500401) {
    id,
    name,
    isViewerFriend
  }
}Copy the code

Yes, this is not JSON, but we can still see that it represents the id, name, and isViewerFriend information of the user with ID 3500401. User information is common to each business. Suppose another business needs such user information B:

{
  user(id: 3500401) {
    name,
    profilePicture(size: 50)  {
      uri,
      width,
      height
    }
  }
}Copy the code

By comparison, we find that there are only two fields missing and one more field. If we want to achieve our goal of reusing the same interface to support both services, we can do the following:

  1. Use the same interface that provides all the data. The advantage of this is that it is easy to implement, but the disadvantage is that there is more logic to make decisions about the business, and some of the data in the response content is not needed at all for the business.
  2. Parameters are used to distinguish between different business parties and return corresponding data. The advantage is still simple implementation, although not useful data return, but still need to increase the business logic judgment, will cause later maintenance difficulties.

In addition, this creates strong dependencies between different lines of business, with each release requiring testing and regression across all lines of business. Is there a “better” solution to the problem of not reusing interfaces without improving development efficiency?

This is a consideration that we often face when dealing with complex anterior and posterior end separations.

1.GraphQL, a new idea

As we know, the data model corresponding to user information is fixed, and each request actually filters and filters these data. Corresponding to the database operation, is the data query operation. If the client could also send requests as queries, wouldn’t it be possible to filter the data needed by the business from a big “big database” of back-end interfaces?

GraphQL was designed with this in mind. The data structures of type (a) and type (b) mentioned above are the query content of GraphQL. Using the query above, the GraphQL server returns the following response, respectively.

A Query the corresponding response:

{
  "user" : {
    "id": 3500401,
    "name": "Jing Chen",
    "isViewerFriend": true
  }
}Copy the code

B Query the corresponding response:

{ "user" : { "name": "Jing Chen", "profilePicture": { "uri": "http: //someurl.cdn/pic.jpg", "width": 50, "height": 50}}}Copy the code

The GraphQL Client Specified Queries can be used to customize the response returned by the server by simply changing the query content. If we could make the basic data platform into a GraphQL server, wouldn’t we be able to provide a unified reusable data interface for all the businesses on this platform?

With that in mind, let’s put GraphQL to the test.

2. Implement GraphQL server with Node.js

Let’s build a GraphQL server according to the official document:

$ mkdir graphql-intro && cd ./graphql-intro
$ npm install express --save
$ npm install babel --save
$ touch ./server.js
$ touch ./index.jsCopy the code

The contents of index.js are as follows:

//index.js
//require `babel/register` to handle JavaScript code
require('babel/register');
require('./server.js');Copy the code

The contents of server.js are as follows:

//server.js import express from 'express'; let app = express(); let PORT = 3000; app.post('/graphql', (req, res) => { res.send('Hello! '); }); let server = app.listen(PORT, function() { let host = server.address().address; let port = server.address().port; console.log('GraphQL listening at http://%s:%s', host, port); });Copy the code

Then execute the code: nodemon index.js:

If nodemon is not installed, NPM install -g nodemon is also recommendedNode – dev module.

Whether the test is valid:

curl -XPOST http://localhost:3000/graphqlCopy the code

Then write GraphQL Schema

Schema is the entry point to the GraphQL request. The user’s GraphQL request is mapped to a specific Schema.

query getHightScore { score }Copy the code

The above request is to get the score value of getHightScore. You can also add query conditions, for example:

query getHightScore(limit: 10) { score }Copy the code

Such a request format is the schema in GraphQL. The schema allows you to define the response content of the server.

Next we use graphQL in our project:

npm install graphql --saveCopy the code

Use body-parser to handle request content: NPM install body-parser –save. The GRAPHQL NPM package is responsible for assembling the server schema and processing graphQL requests.

Create schema: touch./schema.js.

//schema.js import { GraphQLObjectType, GraphQLSchema, GraphQLInt } from 'graphql'; let count = 0; let schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { count: { type: GraphQLInt, resolve: function() { return count; }}}})}); export default schema;Copy the code

This code creates a GraphQLSchema instance. The top-level query object of this schema returns a RootQueryType object that has a count field of type integer. GraphQL supports Interger, String, List, and other types of data.

Connection schema

To connect the GraphQL Schema to the server, we need to modify server.js as follows:

//server.js import express from 'express'; import schema from './schema'; import { graphql } from 'graphql'; import bodyParser from 'body-parser'; let app = express(); let PORT = 3000; //Parse post content as text app.use(bodyParser.text({ type: 'application/graphql' })); app.post('/graphql', (req, res) => { //GraphQL executor graphql(schema, req.body) .then((result) => { res.send(JSON.stringify(result, null, 2)); })}); let server = app.listen(PORT, function() { let host = server.address().address; let port = server.address().port; console.log('GraphQL listening at http://%s:%s', host, port); });Copy the code

Verify the effect:

curl -v -XPOST -H "Content-Type:application/graphql"  -d 'query RootQueryType { count }' http://localhost:3000/graphqlCopy the code

The results are shown below:

GraphQL queries can also omit the query RootQueryType prefix, which is:

Checking the Server

The most interesting thing about GraphQL is that you can write GraphQL queries to have the GraphQL server tell us which queries it supports, which the official documentation refers to as introspection.

Such as:

curl -XPOST -H 'Content-Type:application/graphql'  -d '{__schema { queryType { name, fields { name, description} }}}' http://localhost:3000/graphqlCopy the code

Our actual GraphQL query request reads:

{
  __schema {
    queryType {
      name,
      fields {
        name,
        description
      }
    }
  }
}Copy the code

Basically every GraphQL root field is automatically appended with a __schema field that has a subfield called queryTyp. We can query these fields to see what queries the GraphQL server supports. We can modify schema.js to add description to the count field:

let schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { count: { type: GraphQLInt, //Add description description: 'The count! ', resolve: function() { return count; }}}})});Copy the code

Verify:

curl -XPOST -H 'Content-Type:application/graphql'  -d '{__schema { queryType { name, fields { name, description} }}}' http://localhost:3000/graphqlCopy the code

Mutation (modification of data)

The modification of data in GraphQL is called mutation. Mutation is defined in GraphQL Schema as follows:

let schema = new GraphQLSchema({
  query: ...
  mutation: //TODO
});Copy the code

The important difference between a mutation query and a normal query request (Query) is that the mutation operation is executed serially. For example, given in the GraphQL specification, the server must serialize the following mutation requests:

{
  first: changeTheNumber(newNumber: 1) {
    theNumber
  },
  second: changeTheNumber(newNumber: 3) {
    theNumber
  },
  third: changeTheNumber(newNumber: 2) {
    theNumber
  }
}Copy the code

TheNumber will be 2 at the end of the request. Let’s add a mutation query for our server and modify schema.js as follows:

//schema.js import { GraphQLObjectType, GraphQLSchema, GraphQLInt } from 'graphql'; let count = 0; let schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { count: { type: GraphQLInt, //Add description description: 'The count! ', resolve: function() { return count; }}}}), //Note: This is the newly added mutation query mutation: new GraphQLObjectType({name: 'RootMutationType', fields: { updateCount: { type: GraphQLInt, description: 'Update the count', resolve: function() { count += 1; return count; }}}})}); export default schema;Copy the code

Validation:

curl -XPOST -H 'Content-Type:application/graphql' -d 'mutation RootMutationType { updateCount }' http://localhost:3000/graphqlCopy the code

After setting up the GraphQL server, we simulated the actual requirements of business scenarios. For e-commerce platforms, commodity information is the most commonly used, assuming that the current commodity data model can be represented by the following GraphQLObject:

var ItemType =  new GraphQLObjectType({
  name: "item",
  description: "item",
  fields: {
    id: {
      type: GraphQLString,
      description: "item id"
    },
    title: {
      type: GraphQLString,
      description: "item title"
    },
    price: {
      type: GraphQLString,
      description: "item price",
      resolve: function(root, param, context) {
        return (root.price/100).toFixed(2);
      }
    },
    pic: {
      type: GraphQLString,
      description: "item pic url"
    }
  }
});Copy the code

The schema for the query goods is as follows:

var ItemSchema = new GraphQLSchema({ query: { name: "ItemQuery", description: "query item", fields: { item: { type: ItemType, description: "item", args: { id: { type: GraphQLInt, required: true //itemId required for query } }, resolve: function(root, obj, ctx) { return yield ItemService(obj['id']); }}}}});Copy the code

You can run the following query to query information about the product whose ID is 12345:

query ItemQuery(id: 12345){
  id
  title
  price
  pic
}Copy the code

We need to add the preferential price information to the product details page display. We can modify ItemType and add a promotion field to it:

var ItemType =  new GraphQLObjectType({
  name: "item",
  description: "item",
  fields: {
    id: {
      type: GraphQLString,
      description: "item id"
    },
    title: {
      type: GraphQLString,
      description: "item title"
    },
    price: {
      type: GraphQLString,
      description: "item price",
      resolve: function(root, param, context) {
        return (root.price/100).toFixed(2);
      }
    },
    pic: {
      type: GraphQLString,
      description: "item pic url"
    },
    promotion: {
      type: GraphQLInt,
      description: "promotion price"
    }
  }
});Copy the code

Enquiries on the product details page are:

query ItemQuery(id: 12345){
  id
  title
  price
  pic
  promotion
}Copy the code

The ItemSchema does not need to be modified, just add promotion to the return result of the ItemService. In this way, interface changes are transparent to the existing business, and new business can be developed and iterated quickly based on existing code.

Suppose there is a new page that only needs the picture information of the baby, the business side can use the following query:

query ItemQuery(id: 12345){
  id
  pic
}Copy the code

No changes are made to the server code.

4. To summarize

So far we have implemented a GraphQL base server. Data models are certainly more complex in real business, and GraphQL provides a powerful Type System that allows you to easily describe various data models. It provides an abstraction layer that provides flexible data support for different business parties that rely on the same data model. For more production practices of GraphQL on Taobao, please stay tuned for future series of articles on our blog.

The resources

  • GraphQL Introduction
  • Introducing Relay and GraphQL
  • GraphQL Specification
  • Introducing Relay and GraphQL
  • GraphQL Overview – Getting Started with GraphQL and Node.js
  • what is relay
  • facebook engineer answers about relay, graphql
  • Your First GraphQL Server
  • medium.com/@clayallsop…
  • Blog.risingstack.com/graphql-ove…
  • Github.com/davidchang/…
  • Nginx.com/blog/introd…
  • Code.facebook.com/posts/16914…
  • graphql.org/blog/
  • Github.com/chentsulin/…

This article from: http://taobaofed.org/blog/2015/11/26/graphql-basics-server-implementation/ author: He clouds